The importance of writing unit tests have been heavily discussed. However, reality is cruel. Many developers do not write UT, and even larger amount of developers write funny tests just to pass the code coverage bar, and some developers with ambition are not quite sure about how to write good unit tests. Most good UT are written by top developers.
I thought I am that kind of ambitious developer, so I spend two weekends to start to learn it. Most cases listed below are originated from or revised from the book “Practical Unit Testing with Testng and Mockito” by Tomek Kaczanowski. A really good book worth reading.
Concepts
Before we start, let’s visit the concepts of SUT and DOC.
SUT & DOC
SUT, or System Under Test, are understood as the part of the system being tested. Depending on the type of test, SUT may be of very different granularity – from a single class to a whole application.
DOC, or Depended On Component, is any entity that is required by an SUT to fulfill its duties.
For example, recently I am working with Spring, and I am implementing a Filter class, which take in a ServletRequest
object, a ServletResponse
object and a FilterChain
object. Then when writing unit test, SUT is the Filter
class I am working on. DOC are consist of ServletRequest
and a ServletResponse
, and a FilterChain
.
Test Type
There’s many types of tests(and names) in software development, but the most important tests are:
- unit test which help to ensure high-quality code
- integration test which verify that different modules are cooperating effectively
- end to end test which put the system through its paces in ways that reflect the standpoint of users
Simple. Let’s start to discuss UT (Unit Test).
Unit Test
Structure
The structure of writing a UT is:
- Arrange (Setup environment / foundation of the test)
- Act (run the test)
- Assert (compare expect result with actual result)
Frequency of running UT
UT are supposed to be run repeatedly and frequently. Even someone is not using TDD, after we have implemented any feature, all the UT under this project should be run.
Bad smells
A test is not a unit test if:
- It talks to the database
- It communicates across the network
- It touches the file system
- It can’t run at the same time as any of your other unit tests
- It might fail even the code is correct, or it might pass even the code is not correct.
The above points make writing UT not quite straight forward. To make our UT clean, some knowledge such as Mocking is needed. Also we need to master are some skills to utilize the power of our test framework.
Framework and tools
Base on some investigation, I choose TestNG and Mockito and finally spot this book. This is just personal preferences. JUnit provide mostly the similar function with TestNG. Something we desperately need should be available for a mature framework.
Let’s start coding to demonstrate what we have listed above. Here is a Money
class, with two properties, amount and currency.
Compatability
1 |
|
Now let’s write a very simple test to test whether the constructor work as expect. Here our test are with TestNG and JUnit style (with support in TestNG).
1 | import org.testng.annotations.Test; |
Parameter Tests
Now the first important trick. For many cases, one set of input/output are not sufficient when writing UT.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void Test{
TestObject to1 = new TestObject();
InputParam input1 = new InputParam();
OutputParam output1 = new Output();
assertEquals(to1.calc(input1), output1);
TestObject to2 = new TestObject();
InputParam input2 = new InputParam();
OutputParam output2 = new Output();
assertEquals(to2.calc(input2), output2);
}
If we don’t want to write code like this, then Parameter Test is our friend. Use a DataProvider annotation to mark a method as supplying data for a test method. It return a 2D array, contains a list of Object[], each Object will be passed as input to the test function.
1 | import org.testng.annotations.DataProvider; |
See? A DataProvider notation help you define a list of objects. When linked with the dataprovider function, it will automatically pass the object to the test function as parameters. Previous code generate 3 tests.
There are some advance usage of Parameter Tests:
DataProvider could live in another Class so they could be reused (But nee modification to test function, use @Test(dataProvider = "getMoney", dataProviderClass = DataProviders.class)
annotation).
DataProvider could do lazy initialization, so when you are going to generate many test cases (say 10000+), they don’t need to be initialized before the test, incase any cases fail in the middle and it waste a lot of resources. (need further implement an iterator based on DataProvider class)
1 | @DataProvider(name="colors") |
Testing Exceptions
Here is a simple case about how to write UT to test the exception. Some developer might actually include 4 stuffs in a single test (just to improve coverage):
- start the SUT
- pass an invalid value
- catch the exceptions
- handle it
However, TestNG provide an much cleaner way to test it. expectedExceptions
notation are to help.
(Why bother to handle it? Because if we don’t handle the exception, then this test will fail…)
1 | public class MoneyIAETest { |
Testing Concurrent Codes
Testing concurrency, to some extent is nightmare. If not correctly implemented, the quality of test might become a problem of a UT. Let’s introduce two more attributes which is helpful to test concurrent codes.
- threadPoolSize, which sets the number of threads that are to execute a test method
- invocationCount, which sets the total number of test method executions.
There is no need for us to implement threads ourselves.
1 |
|
1 | public class SystemIdGeneratorTest { |
I get 307 fail cases in my laptop for the above code. It failed because the unary operator is not atomic and there might be two thread reading the same value of id.
In addition to what we have done, you can also use the timeOut and invocationTimeOut attributes of @Test annotation. Their role is to break the test execution and fail the test if it takes too long (e.g. if your code has caused a deadlock or entered some infinite loop).
Collection Testing
- Unitils
- Hamcrest
- FEST Fluent Assertion
Unitils
Unitils could help customize the equal definition
1 | import org.testng.annotations.BeforeMethod; |
Fest Fluent Assertion
FEST Fluent Assertions, which is a part of FEST library, offers many assertions which can simplify collections testing. It also provides a fluent interface, which allows for the chaining together of assertions.
1 | import org.fest.assertions.api.MapAssert; |
FEST provides a fluent interface, which allows for the chaining together of assertions. In this case verification of emptiness, size and the absence of duplicates all gets fitted into a single line of code, but the code is still readable.
Dependency between tests
Now think about a test which want to make sure that user account management is correct. It have 2 functions: adding and deleting user accounts. In traditional UT scope, test are independent so the test code might looks like testAddition
and testDeletion
. Note that the addition actually run for 2 times, which actually break the “DRY” rules.
However, testNG use another attribute in @Test notation to solve this problem. This time, an explicit dependency is established between the tests.
1 | import org.testng.annotations.Test; |
However, it’s not always a good thing to have dependency. Think of a test suite with dozens of test, adding a new test will be a nightmare because you might need to find a correct spot to place it into the dependency tree. An alternative is try to initialize the state before each test. And use the dependency in integration test and E2E test.
Private Method Testing
Should we test private method? Of course. But from the best practice we should test it via public method test. However, when facing legacy code, we might face a dilemma: we need test to make sure it work as expect, but writing test need refactor on code, without test we don’t know whether our refactor is correct.
So when facing legacy code, we compromise on private method testing.
Built-in support in Java
Let’s examine some native support from Java.
1 |
|
Using PowerMock
PowerMock give us a cleaner approach to work on testing private methods. The WhiteBox class provide various utilities for accessing internals of a class. Basically it’s a simplified reflection utility intended for tests.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.powermock.reflect.Whitebox;
public class ClassWithPrivateMethodTest {
@Test
public void testingPrivateMethodWithReflectionByPowerMock()
throws Exception, IllegalAccessException {
ClassWithPrivateMethod sut = new ClassWithPrivateMethod();
Boolean result = Whitebox
.invokeMethod(sut, "privateMethod", 302483L);
assertTrue(result);
}
}
Testing non dependency injection code
Think about the following production code (which is less test-able), how can we mock the MyCollaborator
class?
1 | public class MySut { |
PS. the correct way with dependency injection should be1
2
3
4
5
6public class MySut {
public void myMethod(MyCollaborator collaborator) {
// some behaviour worth testing here which uses collaborator
}
}
PowerMock to Rescue
1 |
|
Here some new attribute and method are introduced:
- the
@PrepareForTest
annotation informs PowerMock that theNotDOCInjectedSUT
class will create a new instance of some other class. In general, this is how PowerMock learns, about which classes it should perform some bytecode manipulation. - In order to use PowerMock with TestNG, we need to make PowerMock responsible for the creation of all of the test instances. So we use the
@ObjectFactory
notation to assign PowerMockObjectFactory as the Factory class to generate test instances. - The test double is created as usual - with the static mock() method of Mockito.
whenNew()
is the place magic happens: whenever a new object of the MyCollaborator class gets created, our test double object (collaborator) will be used instead. Two of PowerMock’s methods - whenNew() and withNoArguments() - are used to control the execution of a no-arguments constructor of the MyCollaborator class.- Note that
static
methods could also be mocked as thenew
operator.
ArgumentCaptor
But if the collaborator
class have some input parameters for its constructor?
Here we have a not quite good class – PIM
(Personal Information Manager?), and related class Calendar
and Meeting
.
1 | public interface Calendar { |
As we could see that the meeting inside the addMeeting
method is hard to mock. However, Mockito provide a ArgumentCaptor
function, which we could use to get information from the type Meeting
.
1 | import org.mockito.ArgumentCaptor; |
Writing Testable Code
Code that wasn’t designed to be testable is not testable.
Rely on Dependency Injection
So every dependency could be mocked.
Never hide a TUF within TUC
TUF, or Test UnFriendly Feature, include following examples:
- Database access
- FileSystem access
- Network access
- Affect of side effect access
- lengthy / inscrutable computations
- static variable usage
TUC, or Test Unfriendly Constructor, include following examples:
- Final methods
- Final classes
- Static methods
- Private methods
- Static InitializationExpressions
- Constructors
- ObjectInitialization Blocks
- New-Expressions
Handling existing issues
- subclass and override
1 | public class EventResponder |
1 | public class TestingEventResponder extends EventResponder |
Let’s think about when we could not use this “subclass and override” way to test?
- Final method (we could not override)
- Final class (so we could not inherit)
- Static method (we could not override)
- Private method (we could not override)
- Constructor (they have to run and we have no way to override it)
Strictly speaking, final classes and final methods are only a problem in testing when they hide a TUF, but it’s nice to have a carefully considered reason for using them rather than just using them by default.
References & Further Reading
- http://martinfowler.com/bliki/FluentInterface.html
- Testable Java by Michael Feathers
- Next Generation Java Testing: TestNG and Advanced Concepts By Cédric Beust, Hani Suleiman
- Working Effectively With Legacy Code by Michael Feathers
- Refactoring: Improving the Design of Existing Code by Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts
- Growing Object-Oriented Software, Guided by Tests by Steve Freeman , Nat Pryce
- Java Concurrency in Practice
- Clean Code: A Handbook of Agile Software Craftsmanship By Robert C. Martin