Stories about Software


Test Readability: Best of All Worlds

When it comes to writing tests, I’ve been on sort of a mild, ongoing quest to increase readability. Generally speaking, I follow a pattern of setup, action, verification in all tests. I’ve seen this called other things: given-when-then, etc. But when describing the basic nature of unit tests (especially as compared to integration tests) to people, I explain it by saying “you set the stage, poke it, and see if what happens is what you thought would happen.” This rather inelegant description really captures the spirit of unit testing and why asserts per unit test probably ought to be capped at one as opposed to the common sentiment among first time test writers, often expressed by numbering the tests and having dozens of asserts intermixed with executing code:

I think that was actually the name of a test I saw once: Test_All_The_Things(). I don’t recall whether it included an excited cartoon guy. Point is, that’s sort of the natural desire of the unit testing initiate — big, monolithic tests that are really designed to be end-to-end integration kinds of things where they want to tell in one giant method whether or not everything’s okay. From there, a natural progression occurs toward readability and even requirements documentation.

In my own personal journey, I’ll pick up further along that path. For a long time, my test code was always a monument to isolation, historically. Each method in the test class would handle all of its own setup logic and there would be no common, shared state among the tests. You could pack up the class under test (CUT) and the test method, ship them to Pluto and they would still work perfectly, assuming Pluto had the right version of the .NET runtime. For instance:

There are opportunities for optimization though, and I took them. A long time back I read a blog post (I would link if I remember whose) that inspired me to change the structure a little. The test above looks fine, but what happens when you have 10 or 20 tests that verify behaviors of DoSomething() in different circumstances? You wind up with a region and a lot of tests that start with Do_Something. So, I optimized my layout:

Now you get rid of regioning, which is a plus in my book, and you still have collapsible areas of the code on which you can focus. In addition, you no longer need to redundantly type the name of the code element that you’re exercising in each test method name. A final advantage is that similar tests are naturally organized together making it easier to, say, hunt down and blow away all tests if you remove a method. That’s all well and good, but it fit poorly with another practice that I liked, which was defining a single point of construction for a class under test:

Now, if we decide to add a constructor parameter to our class as we’re doing TDD, it’s a simple change in on place. However, you’ll notice that I got rid of the nested test classes. The reason for that is there’s now a scoping issue — if I want all tests of this class to have access, I have to put it in the outer class, elevate its visibility, and access it by calling MyTestClass.BuildCut(). And for a while, I did that.

But more recently, I had been sold on making tests even more readable by having a simple property called Target that all of the test classes could use. I had always shied away from this because of seeing people who would do horrible, ghastly things in test class state in vain attempts to force the unit test runner to execute their tests sequentially so that some unholy Singleton somewhere would be appeased with blood sacrifice. I tossed the baby with the bathwater — I was too hasty. Look how nicely this cleans up:

Instantiating the CUT, even when abstracted into a method, is really just noise. After doing this for a few days, I never looked back. You really could condense the first test down to a single line, provided everyone agrees on the convention that Target will return a minimally initialized instance of the CUT at the start of each test method. If you need access to constructor-injected dependencies, you can expose those as properties as well and manipulate them as needed.

But we’ve now lost all the nesting progress. Let me tell you, you can try, but things get weird when you try to define the test initialize method in the outer class. What I mean by “weird” is that I couldn’t get it to work and eventually abandoned trying in favor of my eventual solution:

So at the moment, that is my unit test writing approach in .NET. I have not yet incorporated that refinement into my Java work, so I may post later if that turns out to have substantial differences for any reason. This is by no means a one size fits all approach. I realize that there are as many different schemes for writing tests as test writers, but if you like some or all of the organization here, by all means, use the parts that you like in good health.