Tips for TDD and unit tests

Posted on March 6, 2017

Test driven development is something so elementary for me I can't imagine writing a code without at least some TDD. Learning TDD was very eye opening for me in many ways. Since then I try to help others "open the eyes". Once I was asked to do a brief intro to a big team of developers so I am sharing it online so in future I can just link this post.

T for Testing

T in TDD stands for test. So you write tests. To be exact unit tests. But I must mention that TDD is not just about covering your code with verification of business correctness. It is about the design that will emerge from the specifics of this developement process. This is why it must be unit test, not end to end test, big integration tests or spring context tests.

D for Driven

If test should drive our code implementation we have to write our tests before our implementation. This is well known image which describe process of TDD.

TDD Loop

You work in short iterations and repeat these steps over and over - Write a failing test. Make it pass quickly. Refactor the solution.

I wont go into details here. The best is to read Kent Becks book Test Driven Development: By Example. What I want to do is give a set of tips how to write good tests that will drive your work and code in the right direction.

Listen to your tests

This is most important of all. Listen what test is telling you. If it is painful to write a setup code, you have to mock a lot, or you even have to use some workarounds to write the test - the test is telling you that there must be different way how to write the code, better one.

// don’t do this
// or this
ReflectionUtils.setField(service, "fieldName", fieldValue);

Sometimes I see making some methods protected to be reachable from the unit test. This happens usually when setup for calling the public method is so complicated that it is untestable and author instead of listening decided to fight the test.

Naming, naming, naming

When I try to understand some code I look at tests. I try to read test names in order to see what the code does. This can be true only when test names are meaningful.
Consider this standard names of test for a class Calculator

void testAdd() {...}
void testCalculation() {...}
void throwsException() {...}
void testRemove() {...}

and now guess what the code does? Does add operation takes two arguments? Does it take negative values? The exception is thrown under which conditions? Now look at these names. Taken from real code.

void variablesInInputStringAreReplacedByTheirValues() {...}
void exceptionIsThrownWhenVariableIsNotFound() {...}
void variableCanBeOverwritenInChildContext() {...}
void parametersCannotBeOverwritenInChildContext() {...}
void builderThrowExceptionWhenRegisteringVariableWithSameNameAsParameter() {...}
// ... etc

I think you can make at least some picture of the code and what it does. This style of naming has other advantages as well. If you don't know how to name it you don't understand the problem you are trying to solve. Also when the test fails later on you can tell exactly what is wrong with the code. If variablesInInputStringAreReplacedByTheirValues fails you can be sure that variables are not replaced. Prefixes such as test, should, verify are boilerplate, get rid of them. If you prefix each test method with the same prefix what is the point of it then? Just state a fact about the code. The fact is then proved simple by running a test. As you can tell from the example test names - there is no one-to-one relationship with test methods and production code methods. We are not testing methods, we are testing features of the code. Same applies for test class names. It does not have to be one class, one test.

No logic in tests

Test should be very simple. It should follow three blocks - Arrange, Act, Assert. With least possible lines per block. If you write some logic in your test how it will be tested? Avoid even things like string concatenation. There is a good example in this post from google why not. Can you spot an error in this code?

public void shouldNavigateToPhotosPage() {
  String baseUrl = "";
  Navigator nav = new Navigator(baseUrl);
  assertEquals(baseUrl + "/u/0/photos", nav.getCurrentUrl());

It will pass for url (double slash).

Apart from this it is so much more readable to have the result written as is in the assert part because you can quickly know what the code returns without reading some other lines. Consider this example

assertThat(value).isEqualTo(object.getValue() * 2 * MINUTES);
// vs.

Single responsibility

You are sticking to SRP in your code, aren't you? Apply it also on tests. In test this means that each test have only one reason to fail. As mentioned before variablesInInputStringAreReplacedByTheirValues can fail only when variables cannot be replaced. This is often interpreted as rule of single assert.

void moreAssert() {

This test will fail either if our tax calculations or our vip status detection is not correct. If you follow tip for naming the tests and listeneing them this should be obvious even before writing the asserts. Because how do you name it - taxIsCalculatedAsPercentageAndVipStatusIsDetected? That does not sounds right to me.
But do not follow the single assert rule too strictly. Often it is completely ok to have more asserts, but they all have to verify single aspect of the feature. But be aware that when using JUnit the first assert will cause the test method to fail and not execute the rest of the asserts!

Start with an assert

How do you validate that your method does what it should do? That is maybe the hardest part of writing the test. Following the life-pro-tip rule of starting with the hardest thing - start with an assert. When you have an assert create the missing variable. Then fill the variable from some code that you are testing. Now create missing parameters and you have a complete test.

Use custom exceptions

This is not TDD specific tip but I have to mention it. When testing code for exceptions throw custom exceptions from code. Otherwise it wont be testable.

@Test(expected = IllegalArgumentException.class)
void someTest() {...}

This test will fail even if you are not the one throwing an exception. It might be some framework you are using and you actually have a bug in the code, but the test passes. This rule apply only for business code, use standard exceptions otherwise. When writing file reading code it is far better to throw standard java exceptions than custom one.

Write fast tests

You should run your tests very often. If you test takes a long time to execute you will have tendency to open hackernews or do some other distractive activity that will ruin your TDD cycle.

Master your tools

As I mentioned in the intro. Writing test-code-refactor way is an iterative process. When doing same things again and again it is important to get rid as much litter activities as possible. Learn your IDE shortcuts, learn how to execute tests with single command or a key press.

Isolate your test

Every of your test should be able to run anytime, offline, without any additional setup. They should be executable in any order, all at once or just single test. All inputs should be clear from the test and relationship to the output should be clear.

Don't think about the elephant

When writing the test don't think about the solution. Write the test code as clean and descriptive as possible. Design object to copy your business not your technical solution. Do you handle money? Use Money object, not BigDecimal or any else. It will represent your intentions better and will not tie you with single solution.

Do not use mocks too much

Mock only types you own and mock only when needed. Do not mock by default. Try to use real implementation instead. What you have to usually mock is objects at the boundaries of your system. I/O for example. Instead of using java file api directly use a custom FileReader. This FileReader can implement some DataReader interface and you can have simple readers for test purposes only.

void dataAreStoredInMap() {
  DataReader reader = new DummyReader("key1,value1", "key2,value2");
  // ...

This is definitively better then mocking a file system access. This approach will push the "dirty things" out of your core code which will make it more testable. Then you will end up with code that is free of file/database access or network communication, because those are things that does not matter in the business code. Your code will then be lot more composable and will most likely not violate SRP.

Java is not the king

In Java even simple List declaration and initialization is for several lines. But hey we have JVM we don't have to restrict ourselves to Java. Look for example on groovy.

void "string as a method name, wow"() {
  def list = [1, 2, 3]
  def namedParams = new Complex(name: "John")
  def mock = [ "get" : { -> "John"}] as NameSource
  assert mock.get() != "John"

This will improve readability and joy of writing the test by a magnitude. Then there are frameworks such as Spock. You can also use Kotlin, Scala or any other JVM language.

Reading list