Test development
Brief
Unit testing is a method by which individual units of source code are tested to determine if they are fit for use. A unit is the smallest testable part of an application.
The purpose of writing a test is NOT to cover 100% lines of the production code, or any certain number, it's not about covering the lines. You can write tests, mock everything, don't put assertions, and get 100% coverage.
The purpose of writing a test IS to test CERTAIN BEHAVIOR. You know, you give some input data, and you get some results. If you are not sure what your test is supposed to test i'm 100% positive that it's gonna end up crap.
The first thing you do, and it's either if you write the test before the production code or after, is to think about use cases, certain system behavior you want to test. If you are writing the test after the code, it's very possible you will find very hard to name that behavior and it's very likely caused by low quality code, for ex. methods have side effects or more than single responsibility.
Test Template
Below is a template which you can use to create a new test. All tests should be put inside "src/test/java" source directory of your project.
import static org.mockito.Mockito.mock; // (...) import org.junit.Test; // (...) public class <ClassBeingTestedName>Test { // TESTED CLASS DECLARATION // MOCKED CLASS DECLARATIONS @Before public void init() { // THINGS TO DO BEFORE EACH TEST } @Test public void should<TestDescription> throws Exception { // given // METHOD MOCKS // when // CALLING TESTED CLASS METHOD WHICH YOU WANT TO TEST // then // ASSERTIONS } @After public void flush() { // THINGS TO DO AFTER EACH TEST (optional) } }
Guidelines
- In method with "@Before" annotation you should:
- Initialize tested class. It's always done by using "new ClassName();"
- Mock all classes used by a tested class. It's done by initializing mocked class variables with "mock(ClassName.class);"
- Replace classes used by a tested class with the mocked ones. It's done with "setField(testedClassVariable, "variableInsideTestedClass", mockedClassVariable);"
- During test you need to have full control over mocked class methods used by tested class so you also have to mock them in "given" section
- It's done with "when(mockedClassVariable.methodBeingMocked(arguments)).thenSomething(argument);"
- "thenSomething" mentioned above can be for example "thenReturn"
- Assertions are used to check if method returns value equal to the expected one or if it was called X times
- Checking values: "assertXXX(expectedValue, actualValue);" where "XXX" can be for example replaced by "Equals"
- Cheking amount of method calls: "verify(mockedClassVariable, times(amount)).mockedMethod(arguments);"
- "@Test" annotation has optional parameter "expected" that takes as values subclasses of Throwable
- It's used to verify if class throws the correct exception and annotation above test method looks like that: "@Test(expected=ExceptionName.class)"
- Sometimes method can have some static method calls or database queries that are irrelevant to the logic you want to test, my tip is to separate that into two methods, like put the logic you want to test into separate method with package scope and test it instead.
Running tests
- To run a test just click on the test java file with right mouse button and choose "Run As -> 1 JUnit Test" or click with left mouse button on this file and use "Shift + Alt + X, T" shortcut.
- If you have eCoberture installed in your Eclipse IDE (see "Useful links" section at the end of this article) you can also click on the java file you want to test with right mouse button and choose "Cover As -> Coverage Configurations..." to see the code coverage of your test.
Example
This simple example show how to test single method in the InventoryService class.
InventoryService.java fragment
Tested method clears Entity fields after form copies it.
@Service public class InventoryService { // ... public boolean clearGeneratedOnCopy(final DataDefinition dataDefinition, final Entity entity) { entity.setField("fileName", null); entity.setField("generated", "0"); entity.setField("date", null); return true; } // ... }
InventoryServiceTest.java
In the test we want to check tested method return value and how many times "setField" method was called. We mock only Entity beacase it's the only variable used by the tested method.
public class InventoryServiceTest { private InventoryService inventoryService; private Entity entity; @Autowired private DataDefinition dataDefinition; @Before public void init() { inventoryService = new InventoryService(); entity = mock(Entity.class); } @Test public void shouldClearGeneratedOnCopy() throws Exception { // given // when boolean bool = inventoryService.clearGeneratedOnCopy(dataDefinition, entity); // then assertTrue(bool); verify(entity, Mockito.times(3)).setField(Mockito.anyString(), Mockito.any()); } }
Usefull links
- eCobertura - Eclipse plugin for measuring and visualizing Java code coverage
- Mockito Documentation