So, you’re working on a new app, and Rails just generated a test for you:
1 2 3 4 5 6 7
You uncomment it, come up with a name, and you’re ready to write your test, right? But then what? What do you write first? What should your test code look like?
If you follow a simple pattern, you’ll turn those stubbed out lines of code into clear, well-structured test cases.
The three-phase test pattern
Your test cases should work in three phases:
- First, you set some stuff up (“Arrange”)
- Then, you do something (“Act”)
- Then, you make sure that what you expected to happen, actually happened. (“Assert”)
For instance, imagine you were testing a method on an array in Ruby. Following this pattern, your test could look like:
1 2 3 4 5 6 7 8 9 10
Simple enough. But every part of the test has a place to go, and each stage of the test almost tells you how to write it.
Sometimes, you won’t need an Arrange phase, or the Act and Assert phases will be combined. But it still helps to think about all three phases as you write your tests.
The Assert phase gotcha
There’s a trick to the Assert phase: you shouldn’t use the same logic that the Act phase used in the Assert phase. You should always take two paths to the same answer. Otherwise, you won’t notice bugs in the code you’re calling, because it’s just getting called again in the Assert phase.
For example, if you’re doing some math:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1, 2, 3, 4].average again in the Assert phase is bad, because
average could return almost anything and that assertion would still pass.
Here, that’s pretty clear. But even when things get more complicated, make sure you’re not just running the same code twice. Otherwise you’re only verifying that your method was called, not that it worked the way you expect it to.
Usually, the easiest way to take a second path to the answer is to find the answer by hand and hardcode it. It can be brittle, but it’s better than your tests breaking without you realizing it.
Why three phases?
If you split your tests into those three phases, you have simpler questions to answer. Instead of “How should I write this test?”, you can focus on each phase: “How should I set this test up?”, “What am I testing?”, “What should the answer look like?”
These questions still might not have easy answers, but the answers will be a lot easier than thinking about the entire test at once. And if you’re lucky, you can even share phases between related tests, making your next test much less painful to write.