|
From: Noel D. <ma...@mc...> - 2012-10-21 18:57:41
|
Hi > The concepts I have trouble > with are primarily around the differences between a unit test and an > integration test. A unit test has a tight focus on single "units" of behaviour. As you put a class through its paces in a series of tests, you'll most likely be using mocks to represent the object's neighbours. An integration test (probably) doesn't use mocks but instead makes assertions about the behaviour of groups of real objects acting together. You could mock around the edges of the group though. An acceptance test manipulates the user interface and make assertions about what should happen next. That, I guess, is the ultimate integration test, where you're testing the entire application, top to bottom. Testing makes most sense working top to bottom ie describe a behaviour in a (failing) acceptance test and then work your way down through all the classes you need to make the test pass - test first of course :) Always code to a failing test. In this way testing becomes a design tool. It's a really lovely way to work. You'll often hear test-infected programmers say "if you concentrate on writing tests, the code just seems to write itself". The act of writing a clear test for a single behaviour really helps to bring the mind into focus. It makes you take decisions. It breaks a complex task down into lots of little, easy-to-manage steps. If you are careful always to do the simplest thing to make the test work, it can lead you off in design directions you might not have thought of. It's a bit like the process of evolution, lots of little changes guided by fitness for purpose eventually leading to all kinds of strange beasts that no-one could have predicted in the beginning. Refactoring is an important part of the design process. You need to go back and review what you've done from time to time, looking for ways to improve the design. Maybe you can make something simpler and easier to understand (naming is very important here) or maybe you want to open up a space to drop in a new behaviour. When you're refactoring, unit tests can be a bit of a nuisance, in a way. Units define very precise behaviours which you are about to change in the refactoring, and so a whole pile of tests will need to be dumped or redone. In some ways it's easier to refactor to acceptance tests, and integration tests because they don't care how about the details of how things get done, just the results. However, when an acceptance is failing, it won't be able to explain why, unlike a unit test. The main thing I try to do when I'm writing tests is make them read well, as far as I can like I'm writing a manual for the application. I will forget how the app works in a few months or so but I'm bound to be asked to debug a problem or add a new feature somewhere down the line. Good, easy-to-understand tests let me look up all the behaviours again and figure out the details of the component parts. One of the best things you can do for your code, if you haven't already, is move object assembly out into a specialist "object graph" class - ideally a dependency injector like Phemto. Even a custom class, or classes, with a bunch of manually-written functions which do nothing but instantiate objects, wire them in to the graph, and return them can be a big step forward. With good design, this allows you to change behaviours very easily in the assembler class, simply by dropping a different object into the graph. In dependency injection, you would adjust a config file and the injector does all the work for you. Sorry I don't have time to look at your code but I'll try to do that later next week. Noel |