Veni, vidi, fixi. Theoretical part

November 30, 2008 19:29

Unit testing is a hard sell.

It provides a (sort of) living documentation of the system. It facilitates change, and guards the code from being accidentally mangled, and allows you to refactor at a later date.

And
a test sometimes can save you a hundred times the cost of creating it.

And at the same time when some guys are procrastinating the unprocrastinable and frantically coding in order to meet their deadlines, a more longsighted guys (like those reading this blog post) prefer writing both code and unit tests and um, deliver less buggy software to happy customers.

Hmm.

That's what they say – but think about it without sensationalising and mawkishness.
It just cannot be that easy, right? 

Some skills required.

I mean, it's abundantly clear that code without unit tests does not get out of the door completely untested. Unit testing is just a needle in a haystack of testing approaches, and it does not replace any of them.

But if you seriously buy the idea that unit tests can help out, be prepared that you need some skills in order to unleash the power. Writing both code and tests does not require being ambidextrous or übersmart, but it does imply a certain mindset:

        •    You look at a test either to see how to use the code or to see why the test fails.
        •    Thus, tests should be poster children about using the code [1].
        •    And they should be as brief as possibe.
        •    Because if a test fails you want to quickly understand the reason.

In terms of the overall idea, don't forget that:

        •    Tests should never, ever be dependent on the execution order.
        •    And the state after a test should be the same as it was before the test.
        •    So make sure you cleanup everything after the test is completed.
        •    Esspecially if it writes to a database [2].
        •    In which case open a transaction in SetUp and rollback in TearDown.       


Huge amount of tests.

If you would really adhere to the approach,  you'll be creating an inconceivable amount of tests. You will be begetting at least one test for every (non-trivial) routine in the system. Before fixing a bug you'll be writing a test for it: thus, after applying a fix and making sure the test passes, this (particular) bug would no longer appear unnoticed.

So it should be possible to run your tests in parallel. Two Santas give away a pile of presents faster than one Santa (that's how they actually work, don't they?).


Alarm bells, alarm bells.


It's probably obvious but anyway: don't test private methods. If you really need to test one, make that method internal and mark your "test" assembly as a friend of that "code" one.
(You don't store code and tests in a single assembly, do you?)

Another reason for alarm bells to go off might be comments in a test. Comments mean the test is not clear. Comments in tests are evil. If you feel like adding a comment to a test
rewrite the test.

But messages in assertions is a different story. An assertion without a message should be very rare. Just don't put exclamation marks at the end of the messages: a failing test would attract attention without that. And make sure your messages are "use-case based" versus "code based":

Assert.IsEmpty(list, "List is not empty!");                            //bad
Assert.IsEmpty(list, "Logged out user should not see pending posts."); //good


The latter provides much better context for someone who would come for a fix or take a peek on the usage.

And yet another alarm bell is (ab)using of Assert.IsTrue(). You can test everything with it
– but one of the main goals of a test is to provide as many details as possible. Consider using boolean assertions only for testing real booleans but never go for

Assert.IsTrue(list.Count == 0, "<message here>"); //no-no

And, of course, a test with a lot of assertions is a wacky idea.  Sometimes it seems natural to perform several assertions in a row, but the flip side is that if one of them fails you cannot tell anything about the rest.  So, while you can create tests with 5+ assertions please make sure you have a very good reason for that. If you don't
– try to split the test into a bunch of smaller ones.




Footnotes.

[1] As for unit tests readability, Roy Osherove has a nice idea of test reviews vs. code reviews.

[2] One could argue that if a test talks to a database it's not unit test because Bona Fide Unit Tests are isolated from databases, files and networks. While I agree about files and network resources, more often than not the code under test heavily relies on a database.  Users should be set up,  metadata should be available and so on. If your tests are something more than Assert.AreEqual(5, calc.Add(2,3), "2+3 should be five") – then you gonna talk to a database quite often. You can mock the calls if you're lucky. But sometimes you're not.


Comments are closed