Ok, now we know what TDD is all about and how to do TDD. Really? Well, no. In fact we only know how TDD works, but TDD is about writing tests (to be more precise – unit tests). What should our tests look like? Here it comes…
What is the Unit?
Why do we call these tests unit tests? The answer is simple – because we test only a unit. Very good answer, but what is this unit? Method, class, module? Unit is simply the smallest part of your system that you can test. If you think about testing something, think about testing only a part of it. If you can imagine this, it means that was not a unit. Now, think about its part. Can you think of testing only part of it … (I think you’ve got my point and if not – read about recursion)
What is the (Unit) Test?
Ok, we have the smallest thing that we can think of. It’s probably a method, maybe a class, doesn’t matter. Think about a simple, but representative scenario of using it. Let’s play with “log in” service. Let the simple scenario be: correct username and wrong password (I’m not saying that this should be the first scenario, but it should be some). The second thing – expected behavior. What should it do under given circumstances? Let’s assume that it should return the message “Wrong password”. And this is our test. Oh, ok, but how do we get the user who exists in database? We have to connect to the database, file or something else. This is exactly my point: separations of concerns, isolation. We have to test our “system” in isolation. We don’t want to test our database; we want to test only the “log in” service. So, we have to provide fake data (but about fakes, stubs, mocks and isolation I will write another time) to test only our service’s behavior.
First pillar: Trustworthiness
Unit tests should be our parachute, bungee rope, safety net. Therefore, we have to trust them, otherwise they are useless. If your tests pass, are you confident that it means something? Maybe it is always green no matter what or maybe it doesn’t test what you think it is testing. A common problem appears if you change or only refactor something, run your tests and all are passing but you still aren’t sure if you didn’t break anything. But if you follow red, green, refactor cycle, you can be sure. Firstly, you saw your tests red (all of them) at the beginning. Secondly, you know they are only passing because you actually wrote a specific code. So, you are confident that if all tests are green your module is fine. You don’t have to check it in another way.
If you have to modify somebody else’s code (let’s assume that there are tests :)). First, try something like this. Remove one random line (make it compile, if you have to) or change if/for/while condition and run tests. If they are still passing – it’s bad for you. This parachute is leaky, better don’t use it (or do, but don’t be surprised if you fall down).
Second pillar: Maintainability
It is obvious that tests require care in the same way as production code. If you change production code (refactor, fix bug or add new functionality) some of your tests will break (it means that neither will they compile nor fail). It is a great issue. The more tests the more attention they need. We can reduce the time we spend on nurturing by writing test only against public method. Public methods are contracts. If we specify some contract we will rather not change it as often as private methods. If you feel that you have to test private method it is a sign of a bad design (it’s called “code smell”). Probably functionality of this method belongs elsewhere. Next thing we can do is to remove duplication in tests, move parts that can vary (e.g. construction, initialization) into one place. You can use factory method or common “set up” for your test. If some tests in your test class need different “set up” than others, split them into two test classes (yeah, two test classes can test one production class or even one method, why not?). Tests need to be refactored as often as production code.
Third pillar: Readability
Last but not least, readability. Since unit tests are executable specifications, you should be able to read them as specifications. First (and I think the most important) aspect is good naming convention. I can’t say that the convention I will present is the best, but I use it and it works for me. It is the same convention as Roy Osherove recommends. Here is the pattern:
First of all, why is the name of a method so important? Because, in every (as far as I know) test runner this is first (and most visible) information about failing test. If you follow my guidance, you won’t need anything more. You will know what’s wrong right away. Neither asserts messages, nor are comments necessary. Method name is all you need. Don’t worry about a long method name. IntelliSense will help you, but in this case you won’t write it anywhere, so there is no problem at all.
Next aspect is a single assertion in the test. It is strongly connected with the test name. If you have multiple assertions in the test, how do you name it? It’s the first clue that something is wrong with your test, when you cannot name it in a simple way. And if you have a single assertion for the test, what is the purpose of assert message? It’s obvious from its name what the test is asserting. There can be exceptions to this, e.g. when we want to do assertion on an object which doesn’t have overridden Equals() method (but maybe it should have?).
I strongly recommend using AAA (a.k.a. triple A) pattern for the test layout. It stands for Arrange, Act, Assert. It means that the test should be divided into three parts. First part is Arrange. Here are all the possible preparations for the test execution to take place: initialization, stubbing, etc. Second one is Act. This is where method, which we are testing, is invoked. The last one is Assert. This is the place for assertion. These parts could be separated with comments like “//arrange” etc., but I figured it out that these aren’t necessary. I separate these parts using empty lines (each part has no empty lines inside), so every test has only two empty lines.
If you trust your tests you use it more often, so you will write more of them. When you have more tests you trust them more (do you see the cycle?). If tests were easy to maintain you wouldn’t be scared of writing more of them and you wouldn’t waste your time on changing them. If tests were readable as a spec, you’d find them useful as an introduction to somebody else’s code (or reminding your own code). It’s a great power of a good test suite.
If you are interested in topic I recommend watching Roy Osherove video from NDC2009
If you have worked out your own conventions or you want to share your opinion feel free to drop a line below