This exercise will cover how to write good tests by following some good practices.
- Create well named tests
- Keep things DRY
- Make tests easy to diagnose on failure
Using good naming conventions when naming tests creates a form of executable documentation for your application. It makes maintaining the code easier by it easier to look up what test does what, and helps readability.
Here are some examples of bad naming:
@Test
public void test1() {
...
}
- Completely unreadable.
@Test
public void oranges() {
...
}
- Something to do with oranges?
@Test
public void makeJuice() {
...
}
- Probably tests the
makeJuice
-method?
Here are some simple naming rules:
- Use domain terminology
- Use natural language
- Be descriptive
Take a look at some of tests we added in the previous exercises:
canBlendOrangeAndAppleSmoothie
blendingOrangeAndAppleSmoothieConsumesOrangesAndApples
As you can se the names use the domain terms blend
, OrangeAndAppleSmoothie
. They´re also pretty descriptive and uses natural language to convey meaning. When reading those test names, you can be pretty confident you know what they do, without reading any code. The names also explains how the domain works - how the smoothie bar should work.
✏️ See if you can rename some of the other tests you made using these rules
Author Roy Osherove has coined another naming convention popular in C#/.NET programming, read about it here.
You may have noticed that the arrange-part of the tests have some duplicated code. Often the arrange-part of tests for a particular class/method may have overlapping code. To make the arrange code more DRY, we can utilize the @Before
annotation provided by JUnit.
✏️ Refactor the common arrange code into a method.
✏️ Add the @Before
syntax to the method.
✏️ Run the tests to make sure they still pass.
📖 Observe that the @Before
annotated method runs before each test (the tests still passes, as before).
The arrange code we wrote contains some "magic numbers" such as the number of apples and oranges restocked before making a smoothie (smoothieBar.restockApples(2)
). We want to avoid stray numbers and such in our test code because it's uncertain why that number was chosen. Consider how the next person introduced to your codebase would look at it. "Is it critical to always restock 2 apples?". "Why was the number 2 used? I'll better use 2 also, just in case". You should always avoid causes for uncertainty in your code.
✏️ Refactor the code to use properly named constants for all magic numbers.
When diagnosing why tests fail it is very helpful to have tests output detailed information about what caused the test to fail, so that you can avoid having to debug the test using the debugger.
✏️ Update the blendingOrangeAndAppleSmoothieConsumesOrangesAndApples
test to restock the wrong amount of apples.
✏️ Rerun the test.
📖 Notice that the test outputs an AssertionError
, but it's not very helpful.
To make tests easier to diagnose, we can use the assertEquals
assertion. assertEquals
can be used to compare expected values to actual values:
Syntax:
assertEquals(1, someNumber);
Test failure output:
Expected :1
Actual :0
Supplying an identifying message makes the assertion error more readable:
Syntax:
assertEquals(“Wrong quantity of stuff!”, 1, stuffQuantity);
Test failure output:
Wrong quantity of stuff!
Expected :1
Actual :0
✏️ Replace the assertTrue
assertions in the test with assertEquals
assertions.
✏️ The test output should now be easier to diagnose.