# Write You a Unit Test for Great Good!
> [name=Ria Wu] [time=Fri, Mar 01, 2019 13:00]
# Definition of Unit Tests
Unit testing refers to the testing on smallest parts in the software engineering application that are independently from each other. In our case, an unit is defined as a class in our code base.
# Purpose of Unit Tests
Unit testing is one of the quality measurement and evaluation on our software. It documents the design, and makes it easier to refactor and expand the code while retaining a clear overview of each unit’s behaviour. It also facilitates integration and early problem detection. Errors not found in the early stages significantly increase the costs such as analysing the bugs and re-writing source code. As shown in Figure 1, the cost of correcting a bug detected at the last stage of software development lifecycle can be up to 6.5 times higher than the cost of same detected at testing phase.

Figure 1. Cost of Defect Repair. (Hewlett Packard)
With good written unit tests, following common issues can be detected and addressed:
1. Null pointer exceptions
2. Illegal argument exceptions
3. Incorrect population of fields
4. Corner cases that are not handled properly
5. Anti-patterns
6. Code smells
# Who’s Responsible for Writing Unit Tests?
**Write code = write unit tests**
**Every engineer** who writes code is responsible for writing unit tests since we understand the behaviour of our code the best. Downside is that we often only think of covering positive side when writing a test. To ensure good code quality, we need to jump out of the box to cover negative and corner cases as well, so that it helps to reduce potential issues during further integration between each unit.
> “A good programmer is someone who always looks both ways before crossing a one-way street.” [name=Doug Linder]
# What Makes a Good Unit Test?
- **Readable** - An unit test tells us a story about the behaviour of our functions in an application. It should be easy to understand why a test is done and how to address test failures.
- Unit tests should follow a "given-when-then" structure for readability, i.e. given some circumstance `X`, when `Y` occurs, we should see `Z`.
- **Reliable** - Unit test should only fail if bug exists in the system under test. Sometimes, we observe that unit tests are able to pass on our local machines, but fail on the continuous integration server. Good unit tests should be reproducible and independent from external factors such as environment or running order.
- **Deterministic** - Given the same test input(s), tests will pass or fail consistently. Unit tests should be deterministic to ensure everything covered is okay. Flaky unit tests are not acceptable.
- Test code that covers pseudorandom code must have the seed fixed in the test class.
- **Fast** - Since unit tests are run repeatedly during development, fast unit tests make the process of checking for bugs much more efficient.
- **Truly unit, not integration** - Unit and Integration tests serve different purposes in the testing phase. Unit test should not access external resources such as database, file systems and network to eliminate the influence of external factors.
- **Well-Designed** - Good design principles in unit tests makes our life easier by providing the sense of security when changing and refactoring the codes while bad ones make us miserable, waste our time and make the code hard to change.
Footnotes:
^[1]^ ++reliability++ vs ++determinism++: reliability concerns the _accuracy_ of a test while determinism concerns the _precision_ of a test.
# How to Write a Good Unit Test?
## Test Case Design Tips
Test cases have to be designed to make good use of testing resources since every test case adds to the cost of testing. Having effective test cases with **POSITIVE AND NEGATIVE** scenarios covered helps to detect potential bugs at early stage. Below are the tips for test case design:
- **Equivalence partitioning & boundary value analysis**
- ++Equivalence partitioning++ - Since testing every possible input is time consuming, dividing possible inputs into groups are likely to perform similar result with fewer test cases.
> Read: [Equivalence Class Testing](https://experttesters.com/2013/08/16/equivalence-class-testing/)
- ++Boundary value analysis++ - A part of stress and negative testing which test boundary value between partitions. Observation shows bugs often occur at the incorrect handling of boundries of equivalence of partitions such as null or empty value.
> Read: [Boundary Value Analysis Testing Technique](https://www.softwaretestingmaterial.com/boundary-value-analysis-testing-technique/) and [What is Boundary value analysis in software testing?](http://tryqa.com/what-is-boundary-value-analysis-in-software-testing/)
- Design with both techniques
> Read: [Boundary Value Analysis & Equivalence Partitioning with Examples](https://www.guru99.com/equivalence-partitioning-boundary-value-analysis.html)
- **Combining multiple inputs**
A function can take multiple input parameters. Some techniques that can be used:
- Testing **all combinations** has a higher chance of discovering bugs, but the number of combinations can be too high to test.
- With every value included **at least once** might not generate enough possible combinations. For the better design, invalid input values should be tested one at a time, and not combined with the testing of valid input values.
- **All-pairs** creates test cases so that for any given pair of input parameters, all combinations between them are tested.
> Read: [Pairwise Testing or All-Pairs Testing Tutorial with Tools and Examples](https://www.softwaretestinghelp.com/what-is-pairwise-testing/)
- **Others**
> Read: [Test Design Techniques Overview](https://sysgears.com/articles/test-design-techniques-overview/)
## Test Class Name
- **Attach "Test" behind the class name as its unit test class**
Test class name should be clear and obvious for any engineer to understand which class is under the test.
- Source class
```java=
public class Serializer {
// ... omit details
}
```
- Test class
```java=
public class SerializerTest {
// ... omit details
}
```
- **Don't add any condition or irrelvant detail to the class name**
Test class name should be straightforward. Any condition or detail should be listed as part of test case design.
## Test Case Name
A good test case name tells us the information without going through the testing codes. To make it readable, the test case name should follow “given-when-then” structure:
1. **Given**: Function tested
2. **When**: State under test
3. **Then**: Expected outcome
- **Name the test case as test{FunctionName}\_{StateUnderTest}\_{ExpectedBehaviour}**
```java=
public class ResponseValidatorTest {
@Test
public void testIsGood_ResponseOk_True() {
// ... omit details
}
}
```
From above example, it's easy to understand that:
- _Given_: function `isGood(..)`, which determines whether a given response is good
- _When_: the state of an input is OK response
- _Then_: returns true
- **In short, this test indicates OK is a good response**
## Variable Name
- **Name the variable of class under the test as "sut"**
"sut" stands for **S**ystem **U**nder **T**est. In order to distinguish the test class from other variables declared, we use "sut" to represent the class under the test.
```java=
public class TransformerTest {
private Transformer sut = new Transformer();
}
```
- **Name the mock variable as "mock{Variable}"**
Having mock variable means mock behavior is expected. To avoid confusion between real and mock behaviors when writing a unit test, we standardize the naming convention to "mock{Variable}".
- Source class
```java=
public class Serializer {
private final ObjectMapper mapper;
@Inject
public Serializer(ObjectMapper mapper) {
this.mapper = mapper;
}
}
```
- Test class
```java=
@RunWith(SpringRunner.class)
public class SerializerTest {
@Mock
private ObjectMapper mockMapper;
@InjectMocks
private Serializer sut;
}
```
- **Name the returned variable as "output"**
If the function returns an object, standardize the name of variable to "output", so that we can reduce the possibility of validating on the wrong variable.
```java=
public class ResponseValidatorTest {
// ... omit declarations
@Test
public void testIsGood_ResponseOk_True() {
Response response = Response.ok().build();
boolean output = sut.isGood(response);
assertThat(output, is(true));
}
}
```
## Assertions: Non-void Function
- **Perform MEANINGUL validations on the output**
Always remember to do validations on the "output" of function tested. If your function performs any population, ensure the populated fields are validated.
- Source class
```java=
import static javax.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE;
public class ClientHandler {
private final Client client;
@Inject
public ClientHandler(Client client) {
this.client = client;
}
public Response put(ClientRequest request) {
try {
return this.client.put(request);
} catch (Exception) {
return Response.status(SERVICE_UNAVAILABLE).entity(e.getMessage()).build();
}
}
}
```
- Test class
```java=
public class ClientHandlerTest {
@Mock
private Client mockClient;
@InjectMocks
private ClientHandler sut;
@Test
public void testPut_RestClientExceptionThrown_Response503() {
String errorMessage = "error";
when(mockClient.put(any(ClientRequest.class)))
.thenThrow(new RestClientException(errorMessage));
Response output = sut.put(new ClientRequest());
assertThat(output.getStatus(), is(equalTo(503)));
assertThat(output.readEntity(String.class), is(equalTo(errorMessage)));
}
}
```
## Assertions: Void Function
- **Validate the modified object**
If the internal state of the object is modified, verify if the state is changed correctly.
- Source class
```java=
public class Person {
private int steps;
public Person() {
this.steps = 0;
}
public void stepForward() {
this.steps += 2;
}
public int getSteps() {
return this.steps;
}
}
```
- Test class
```java=
public class PersonTest() {
// ... omit declarations
@Test
public void testStepForward_StepForwardOnce_IncreaseTwoStep() {
Person sut = new Person();
sut.stepForward();
assertThat(sut.getSteps(), is(equalTo(2)));
}
}
```
- **Validate number of times that a mock method is invoked**
If the function is interaction-based, verify the number of times that the internal method is invoked.
- Source class
```java=
public class Evaluation {
private final DataLoader dataLoader;
private final RuleEngine ruleEngine;
@Inject
public Evaluation(DataLoader dataLoader
RuleEngine ruleEngine) {
this.dataLoader = dataLoader;
this.ruleEngine = ruleEngine;
}
public void evaluateWithoutDataLoad(Context context) {
this.ruleEngine.run(context);
}
}
```
- Test class
```java=
@RunWith(MockitoJUnitRunner.class)
public class EvaluationTest {
@Mock
private DataLoader mockDataLoader;
@Mock
private RuleEngine mockRuleEngine;
@InjectMocks
private Evaluation sut;
@Test
public void testEvaluateWithoutDataLoad_EvaluateContext_OnlyRunRule() {
Context context = new Context();
sut.evaluateWithoutDataLoad(context);
// Data loader never invokes
verify(mockDataLoader, never()).load(context);
// Rule engine only invokes once
verify(mockRuleEngine, only()).run(context);
}
}
```
## Exception Validation
- **Use "@Test(expected = {ExceptionType}.class)" to verify expected exception**
Instead of using try-catch block to verify exception thrown, make good use of the exception validation tool provided by testing framework.
- Source class
```java=
public class OrderValidator {
public void validate(Order order) throws ValidationException {
if (order == null) {
throw new ValidationException("Order is null.");
}
if (CollectionUtils.isEmpty(order.getItems())) {
throw new ValidationException("No item in an order.");
}
}
}
```
- Test class
```java=
public class OrderValidatorTest {
// ... omit declarations
@Test(expected = ValidationException.class)
public void testValidate_NullOrder_ThrowValidationException() throws Exception {
sut.validate(null);
}
}
```
# How's a Bad Unit Test Like?
## No Assertion or Validation
:::danger
:-1: No assertion or validation to verify whether the function is performed as expected.
:::
```java=
@Test
public void testPopulate_ValidAddressVO_AddressPopulated() {
AddressVO addressVO = new AddressVO();
addressVO.setAddress("111 Queen Road");
addressVO.setPostalCode("123456");
Address output = sut.populate(addressVO);
// Where's the assertion on the populated request?
}
```
:::success
:+1: Add assertion to the test case.
:::
## Vague Assertion
:::danger
:-1: Assertion is too vague and not helpful with verifying whether the tested function meets expected behaviour.
:::
```java=
@Test
public void testPopulate_ValidAddressVO_AddressPopulated() {
AddressVO addressVO = new AddressVO();
addressVO.setAddress("111 Queen Road");
addressVO.setPostalCode("123456");
Address output = sut.populate(addressVO);
assertThat(output, notNullValue());
// Okay... only not null value is check.
// So... how about the population validation?
// We should verify that the destination fields are
// populated correctly from the source fields.
}
```
:::success
:+1: Add **MEANINGFUL** check to the output and ensure expected behaviour is met.
:::
## Access External Resource
:::danger
:-1: If a unit test access external resources such as database, file systems and network, it's influenced by external factors and no longer truly unit.
:::
```java=
@Test
public void testSave_ValidOrder_SavedSuccessfully() {
// The test case relies on connectivity to DB! Huh?
DatabaseInit().init();
Order order = new Order();
order.getItems().add(generateItem("Pen"));
boolean output = sut.save(order);
assertThat(output, is(true));
}
```
:::success
:+1: Mock operation of external resource to de-couple between components.
:::
## Too Many Scenarios in Single Test Case
:::danger
:-1: With too many scenarios tested in a single test case, we may take time to identify what scenario actually fails. Even if one scenario is fixed, the subsequent scenario may also fail and lead to further analysis again.
:::
```java=
public class ResponseValidatorTest {
// ... omit declarations
@Test
public void testIsGood_MultipleResponses_MultipleReturn() {
// Scenario 1 - ok
Response response1 = Response.ok().build();
boolean output1 = sut.isGood(response1);
assertThat(output1, is(true));
// Scenario 2 - server error
Response response2 = Response.serverError().build();
boolean output2 = sut.isGood(response2);
assertThat(output2, is(false));
}
}
```
:::success
:+1: Each test case should only have **ONE** state or scenario covered.
:::
## Try-Catch Block
:::danger
:-1: Misuse of `try-catch` might lead to the test case to fail silently.
:::
- Source class
```java=
public class OrderDAO {
private final OrderManager;
@Inject
public OrderDAO(OrderManager orderManager) {
this.orderManager = orderManager;
}
public boolean save(Order order) throws ProcessingException {
OrderRequest request = new Request();
request.setOrderId(order.getId);
request.setOrderItems(order.getItems());
try {
return orderManager.save(request);
} catch (Exception e) {
throw new ProcessingException("Unable to save the order");
}
}
}
```
- Test class
```java=
public class OrderDAOTest {
// ... omit declarations
@Before
public void setup() {
// ... omit mock setup
}
// The actual exception thrown is NullPointerException (NPE)
// where the error comes from line 12 in source code.
// However, with a generic exception catch and we assume ONLY
// ProcessingException will be thrown, the actual NPE problem
// isn't detected to fail the test case.
@Test
public void testSave_NullOrder_ThrowProcessingException() {
when(mockOrderManager.save(any(OrderRequest.class)))
.thenThrow(new Exception());
try {
boolean output = sut.save(null);
fail();
} catch (Exception e) {
assertTrue(true);
}
}
}
```
:::success
:+1: **NEVER** put `try-catch` in the unit test, so that any unexpected exception will fail the test case immediately.
:::
## Mock Static Object
:::danger
:-1: mockStatic(..) can be thread-unsafe if it's not handled properly, and make tests flaky.
:-1: Mock static method shows it being not deterministic and very complex. It should be simple enough for not mocking it.
:::
- Source class
```java=
public class DownLoad {
public boolean execute(Context context) {
String fileName = (String) context.get("FILE_NAME");
return DatabaseHelper.load(fileName);
}
}
```
- Test class
```java=
@RunWith(PowerMockRunner.class)
@PrepareForTest({DatabaseHelper.class})
public class DownLoadTest {
// ... omit declarations
@Test
public void testExecute_ValidContext_DownloadSuccessfully() {
Context context = new Context();
mockStatic(DatabaseHelper.class);
when(DatabaseHelper.load(anyString())).thenReturn(true);
boolean output = sut.execute(context);
assertThat(output, is(true));
verifyStatic(only());
DatabaseHelper.load(anyString());
}
}
```
:::success
:+1: Re-design the source code and reduce the complexity of static class or method to ensure mocking is not required. Another alternative is to construct singleton utility object and inject into the target class.
:::
# Conclusion
> “If you don’t like testing your product, most likely your customers won’t like to test it either.”
>
Writing robust unit tests can be challenging at the beginning, but let's do it right first, then do it better. I believe none of us wants to use weakly tested and buggy applications. :satisfied:
# References
- https://www.toptal.com/qa/how-to-write-testable-code-and-why-it-matters
- https://xbsoftware.com/blog/why-should-testing-start-early-software-project-development/
- https://dzone.com/articles/10-tips-to-writing-good-unit-tests
- http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/
- https://blog.frankel.ch/initializing-your-mockito-mocks/
- https://experttesters.com/2013/08/16/equivalence-class-testing/
- https://www.softwaretestingmaterial.com/boundary-value-analysis-testing-technique/
- https://www.guru99.com/equivalence-partitioning-boundary-value-analysis.html
- http://tryqa.com/what-is-boundary-value-analysis-in-software-testing/
- http://www.professionalqa.com/equivalence-class-testing
- https://sysgears.com/articles/test-design-techniques-overview/
- https://www.softwaretestinghelp.com/what-is-pairwise-testing/
- https://automationrhapsody.com/powermock-examples-better-not-use/
- NUS CS2103 Software Engineering Handbook