Intro to Unit Testing 9: Tips and Tricks
This, I’ve decided, is going to be the penultimate post in this series. I had originally targeted a 9 part series, finishing up with a “tips and tricks” post, the way you often see with technical books. But I think I’m going to add a 10th post to the series where I make the business case for unit testing. That seems like a more compelling wrap up to the series. So, stay tuned for one more after this.
This post is going to be a collection of tips and tricks for new and veteran unit testers.
Structure the test names for readability beyond just descriptive names.
The first step in making your unit tests true representation of business statements is to give them nice, descriptive names. Some people do this with “given, when, then” verbiage and others are simply verbose and descriptive. I like mine to read like conversational statements, and this creates almost comically long names like “GetCustomerAddress_Returns_Null_When_Customer_Is_Partially_Registered.” I’ve been given flack for things like this during my career and you might get some as well, but, do you know what? No one is ever going to ask you what this test is checking. And if it fails, no one is ever going to say “I wonder why the unit test suite is failing.” They’re going to know that it’s failing because GetCustomerAddress is returning something other than null for a partially registered customer.
When you’re writing unit tests, it’s easy to gloss over names of the tests the way you might with methods in your production code. And, while I would never advocate glossing over naming anywhere, you especially don’t want to do it with unit tests because unit test names are going to wind up in a report generated by your build machine or your IDE where as production methods won’t unless you’re using a static analysis tool with reporting, like NDepend.
But it goes beyond simply giving tests descriptive names. Come up with a good scheme for having your tests be as readable as possible in these reports. This is a scheme from Phil Haack that makes a lot of sense.
I adopted a variant of it after reading the post, and have been using it to eliminate duplication in the names of my unit tests. This consideration of where the test names will be read, how, and by whom is important. I’m not being more specific here simply because how you do this exactly will depend on your language, IDE, testing framework, build technology etc. But the message is the same regardless: make sure that you name your tests in such a way to maximize the value for those who are reading reports of the test names and results.
Create an instance field or property called “Target”
This one took a while to grow on me, but it eventually did and it did big time. Take a look at the code below, originally from a series I did on TDD:
[TestClass]
public class BowlingTest
{
[TestClass]
public class Constructor
{
[TestMethod, Owner("ebd"), TestCategory("Proven"), TestCategory("Unit")]
public void Initializes_Score_To_Zero()
{
var scoreCalculator = new BowlingScoreCalculator();
Assert.AreEqual(0, scoreCalculator.Score);
}
}
[TestClass]
public class BowlFrame
{
private static BowlingScoreCalculator Target { get; set; }
[TestInitialize()]
public void BeforeEachTest()
{
Target = new BowlingScoreCalculator();
}
[TestMethod, Owner("ebd"), TestCategory("Proven"), TestCategory("Unit")]
public void With_Throws_0_And_1_Results_In_Score_1()
{
var frame = new Frame(0, 1);
Target.BowlFrame(frame);
Assert.AreEqual(frame.FirstThrow + frame.SecondThrow, Target.Score);
}
[TestMethod, Owner("ebd"), TestCategory("Proven"), TestCategory("Unit")]
public void With_Throws_2_And_3_Results_In_Score_5()
{
var frame = new Frame(2, 3);
Target.BowlFrame(frame);
Assert.AreEqual(frame.FirstThrow + frame.SecondThrow, Target.Score);
}
[TestMethod, Owner("ebd"), TestCategory("Proven"), TestCategory("Unit")]
public void Sets_Score_To_2_After_2_Frames_With_Score_Of_1_Each()
{
var frame = new Frame(1, 0);
Target.BowlFrame(frame);
Target.BowlFrame(frame);
Assert.AreEqual(frame.Total + frame.Total, Target.Score);
}
[TestMethod, Owner("ebd"), TestCategory("Proven"), TestCategory("Unit")]
public void Sets_Score_To_Twenty_After_Spare_Then_Five_Then_Zero()
{
var firstFrame = new Frame(9, 1);
var secondFrame = new Frame(5, 0);
Target.BowlFrame(firstFrame);
Target.BowlFrame(secondFrame);
Assert.AreEqual(20, Target.Score);
}
[TestMethod, Owner("ebd"), TestCategory("Proven"), TestCategory("Unit")]
public void Sets_Score_To_25_After_Strike_Then_Five_Five()
{
var firstFrame = new Frame(10, 0);
var secondFrame = new Frame(6, 4);
Target.BowlFrame(firstFrame);
Target.BowlFrame(secondFrame);
Assert.AreEqual(30, Target.Score);
}
}
public class BowlingScoreCalculator
{
private readonly Frame[] _frames = new Frame[10];
private int _currentFrame;
private Frame LastFrame { get { return _frames[_currentFrame - 1]; } }
public int Score { get; private set; }
public void BowlFrame(Frame frame)
{
AddMarkBonuses(frame);
Score += frame.Total;
_frames[_currentFrame++] = frame;
}
private void AddMarkBonuses(Frame frame)
{
if (WasLastFrameAStrike()) Score += frame.Total;
else if (WasLastFrameASpare()) Score += frame.FirstThrow;
}
private bool WasLastFrameAStrike()
{
return _currentFrame > 0 && LastFrame.IsStrike;
}
private bool WasLastFrameASpare()
{
return _currentFrame > 0 && LastFrame.IsSpare;
}
}
}
If you look at the nested test class corresponding to the BowlFrame method, you’ll notice that I have a class level property called Target and that I have a method called “BeforeEachTest” that runs at the start of each test and instantiates Target. I used to be more of a purist in wanting all unit test methods to be completely and utterly self-contained, but after a while, I couldn’t deny the readability of this approach.
Using “Target” cuts out at least one line of pointless (and repetitive) instantiation inside each test and it also unifies the naming of the thing you’re testing. In other words, throughout the entire test class, interaction with the class under test is extremely obvious. Another ancillary benefit to this approach is that if you need to change the instantiation logic by, say, adding a constructor parameter, you do it one place only and you don’t have to go limping all over your test class, doing it everywhere.
I highly recommend that you consider adopting this convention for your tests.
Use the test initialize (and tear-down) for intuitive naming and semantics.
Along these same lines, I recommend giving some consideration to test initialization and tear-down, if necessary. I name these methods BeforeEachTest and AfterEachTest for the sake of clarity. In the previous section, I talked about this for instantiating target, but this is also a good place to instantiate other common dependencies such as mock objects or to build friendly instances that you pass to constructors and methods.
This approach also creates a unified and symmetric feel in your test classes and that kind of predictability tends to be invaluable. People often debug production code, but are far more likely to initially contemplate unit tests by inspecting them, so predictability here is as important as it is anywhere.
Keep Your Mind on AAA
AAA. “Arrange, Act, Assert.” Think of your unit tests in these terms at all times and you’ll do well (I once referred to this as “setup, poke, verify.“). The basic anatomy of a unit test is that you setup the world that you’re testing to exist in some situation that matters to you, then you do something, then you verify that what you did produced the result you expect. A real world equivalent might be that you put a metal rod in the freezer for 2 hours (arrange), take it out and stick your tongue on it (act), and verify that you can’t remove your tongue from it (assert).
If you don’t think of your tests this way, they tend to meander a lot. You’ll do things like run through lists of arguments checking for exceptions or calling a rambling series of methods to make sure “nothing bad happens.” This is the unit test equivalent of babbling, and you don’t want to do that. Each test should have some specific, detailed arrangement, some easily describable action, and some clear assertion.
Keep your instantiation logic in one place
In a previous section, I suggested using the test runner’s initialize method to do this, but the important thing is that you do it, somehow. I have lived the pain of having to do find and replace or other, more manual corrections when modifying constructors for classes that I was instantiating in every unit test for dozens or even hundreds of tests.
Your unit test code is no different than production code in that duplication is your enemy. If you’re instantiating your class under test again and again and again, you’re going to suffer when you need to change the instantiation logic or else you’re going to avoid changing it to avoid suffering, and altering your design to make copy and paste programming less painful is like treating an infected cut by drinking alcohol until you black out and forget about your infected cut.
Don’t be exhaustive (you don’t need to test all inputs — just interesting ones)
One thing I’ve seen occasionally with people new to unit testing and especially getting their heads around TDD is a desire to start exhaustively unit testing for all inputs. For instance, let’s say you’re implementing a prime number finder as I described in my Pluralsight course on NCrunch and continuous testing. At what point have you written enough tests for prime finder? Is it when you’ve tested all inputs, 1 through 10? 1 through 100? All 32 bit integers?
I strongly advise against the desire to do any of these things or even to write some test that iterates through a series of values in a loop testing for them. Instead, write as many tests as you need to tease out the algorithm if you’re following TDD and, in the end, have as many tests as you need to cover interesting cases that you can think of. For me, off the top (TDD notwithstanding), I might pick a handful of primes to test and a handful of composite numbers. So, maybe one small prime and composite and one large one of each that I looked up on the internet somewhere. There are other interesting values as well, such as negative numbers, one, and zero. I’d make sure it behaved correctly for each of these cases and then move on.
It might take some practice to fight the feeling that this is insufficient coverage, but you have to think less in terms of the set of all possible inputs and more in terms of the set of paths through your code. Test out corner cases, oddball conditions, potential “off by one” situations, and maybe one or two standard sorts of inputs. And remember, if later some kind of bug or deficiency is discovered, you can always add more test cases to your test suite. Your test suite is an asset, but it’s also code that must be maintained. Don’t overdo it — test as much as necessary to make your intentions clear and guard against regressions, but not more than is needed.
Use a continuous testing tool like NCrunch
If you want to see just how powerful a continuous testing tool is, check out that Pluralsight video I did. Continuous testing is game changer. If you aren’t familiar with continuous testing, you can read about it at the NCrunch website. The gist of it is that you get live, real-time feedback as to whether your unit tests are passing as you type.
Let that sink in for a minute: no running a unit test runner, no executing the tests in the IDE, and not even any building of the code. As you type, from one character to the next, the unit tests execute constantly and give you instantaneous feedback as to whether you’re breaking things or not. So, if you wander into your production code and delete a line, you should expect that you’ll suddenly see red on your screen because you’re breaking things (assuming you don’t have useless lines of code).
I cannot overstate how much this will improve your efficiency. You will never go back once you get used to this.
Unit Tests Instead of the Console
Use unit tests instead of the console or whatever else you might use to do experimentation (get comfortable with the format of the tests). Most developers have some quick way of doing experiments — scratchpads, if you will. If you make yours a unit test project, you’ll get used to having unit tests as your primary feedback mechanism.
In the simplest sense, this is practice with the unit test paradigm, and that never hurts. In a more philosophical sense, you’re starting to think of your code as a series of entry points that you can use for inspection and determining how things interact. And that’s the real, longer term value — an understanding that good design involves seams in the code and unit tests let you access those seems.
Get familiar with all of the keyboard shortcuts
Again, this is going to vary based on your environment, but make sure to learn whatever keyboard shortcuts and things your IDE offers to speed up test management and execution. The faster you are with the tests, the more frequently you’ll execute them, the more you’ll rely on them, and the more you’ll practice with them.
Your unit test suite should be a handy, well-worn tool and a comfortable safety blanket. It should feel right and be convenient and accessible. So anything you can do to wear it in, so to speak, will expedite this feeling. Take the time to learn these shortcuts and practice them — you won’t regret it. Even if you have a continuous testing tool, you can benefit from learning the most efficient way to use it. Improvement is always possible.
General Advice
Cliche as it may sound and be, the best tip I can give you overall is to practice, practice, practice. Testing will be annoying and awkward at first, but it will become increasingly second nature as you practice. I know that it can be hard to get into or easy to leave by the wayside when the chips are down and you’re starting at a deadline, but the practice will mitigate both considerations. You’ll grow less frustrated and watch the barriers to entry get lower and, as you get good, you won’t feel any inclination to stop unit testing when the stakes are high. In fact, with enough practice, that’s when you’ll feel it’s most important. You will get there with enough effort — I promise.
No flack here from naming unit tests like: “GetCustomerAddress_Returns_Null_When_Customer_Is_Partially_Registered” I’m a big advocate for this as well, and tell all the developers I’m working with to follow a similar pattern. I want to be able to look at a unit test and know exactly its purpose by reading its name.
Often at a minimum I’ll using something along like lines of “ThingInFocus_ProcessAction_ExpectedOutcome” like “Person_Using_Bad_Credentials_Should_Not_Authneticate”
Another nice article, thank you.
Glad to hear that I’m not alone in the detailed naming. And I like the emphasis that you have on a more BDD style of naming. I like the concept of having detailed unit tests that discuss class behavior with preconditions and invariants and then more behavior oriented tests that really document system functionality in a way that users would understand (like your example).
[…] Intro to Unit Testing 9: Tips and Tricks, Erik Dietrich […]
It’s a really great series so I am pleased to hear that there’s going to be one more post 😉
I really like the idea of organising tests with nested classes. I was creating classes for each method under test (like “BowlingTest_Constructor”), but this approach seems to be clearer. Another thing I found useful was separating the instantiation logic – that’s a real time saver when modifying constructors. What I’m now dealing with is the habit of trying to test with many inputs 😉
Thanks again for the series and for sharing Your knowledge and experience.
Thanks for the kind words! I’m glad if you like the series.
[…] Intro to Unit Testing 9: Tips and Tricks: Varias recomendaciones que pueden resultar de utilidad cuando se está uno practicando con test unitarios. Especialmente útil es Structuring Unit Tests un esquema para estructurar pruebas unitarias diferente al habitual given/when/then que genera nombres demasiado largos y que creo haber visto ya en algún libro de testing en .Net […]
[…] Intro to Unit Testing 9: Tips and Tricks | DaedTech […]
[…] => Intro to Unit Testing 9: Tips and Tricks […]
[…] • Модульные тесты: несколько полезных советов. […]
[…] • Модульные тесты: несколько полезных советов. […]
[…] Erik Dietrich seeded my mind with the Target idea […]
This is all nonsense. The goal is to deliver working production release software. Yes it has to be tested, but presumably you know what you are meant to be developing and you will have a functional spec for each part that has to be built and what it should do and how it should behave. So you code it and write unit tests ,including extreme conditions and run these tests against the code to see if the functionality is satisfied. You should also have a subsystem that reports errors visually and in a log. Additionally as you test code you… Read more »
I can’t really speak for what others might say, but I can’t imagine what you’re saying here being challenging to anyone’s “ethos.” You start with a lead-in that I assume is meant to be provocative, then say a bunch of pretty boilerplate, common-sense things (you should have unit tests, you should have QA, etc), then close with a standard kind of “people have been doing this well for a bunch of years without these newfangled techniques, so who needs ’em and get off my lawn!” Everything you say in the middle of your comment seems reasonable enough to me, and… Read more »
[…] Intro to Unit Testing 9: Tips and Tricks – A handy list of tips that can make maintaining unit test code a little easier. […]