Reputation: 175
I've been writting unit tests for an software for several weeks now, and I'm facing some doubts right now, concerning the design of my UT, their robustness and their maintainability.
I'm writting my UT for a code in Java, with JUnit and EasyMock, because some of the objects called and used in the functions have a great level of complexity.
Here's is my issues :
I'm aware that factorization and the re-use of mocks for every test case of a UT are a good solution to tackle this issues, and I've written some of them with these methodologies, but I've the feeling that it leads to a decrease in the robustness and the readability of the UT.
Do you have any laws or tips you are trying to keep in mind while writing your UT that may be effective to tackle these issues .
For some clarification, here are some code examples :
public class functionsToTest{
public static int getProperQuantity(Object A){
int returnValue = 0;
if(A != null && A.getQuantity() != null && A.getQuantity() > 0){
returnValue = A.getQuantity();
}
return returnValue;
}
}
Here is the class I want to test with UT. The object A is an complex object / object created with datas from a DB, so I have to mock it to write my UT. I have to way to write my UT tests :
1/ Create mocks and set them up for each test case
public void myTest(){
int expectedValue = 0;
int output = 0;
/** Setting up the mocks **/
// Case 1 : A is null
// Case 2 : A.getQuantity() is null
Object A_case2 = createMock(A.class);
EasyMock.expect(A_case2.getQuantity()).andReturn(null).anyTimes();
// Case 3 : A.getQuantity() is negative
Object A_case3 = createMock(A.class);
EasyMock.expect(A_case3.getQuantity()).andReturn(-1).anyTimes();
// Case 4 : nominal case
Object A_case4 = createMock(A.class);
EasyMock.expect(A_case4.getQuantity()).andReturn(5).anyTimes();
// End Set Up
EasyMock.replay(A_case2, A_case3, A_case4);
/** Executing the tests **/
// Case 1 : A is null
expectedValue = 0;
output = functionsToTest.getProperQuantity(null);
assertEquals(expectedValue, output);
// Case 2 : A.getQuantity() is null
expectedValue = 0;
output = functionsToTest.getProperQuantity(A_case2);
assertEquals(expectedValue, output);
// Case 3 : A.getQuantity() is negative
expectedValue = 0;
output = functionsToTest.getProperQuantity(A_case3);
assertEquals(expectedValue, output);
// Case 4 : nominal case
expectedValue = 5;
output = functionsToTest.getProperQuantity(A_case4);
assertEquals(expectedValue, output);
}
If find this solution quite robust and flexible, because if the function getProperQuantity changes in the future, it's quite simple to modify it. Plus, it's readable. The main drawback is the lenght (in my case, the set ups of the mocks are way longer, there are far more tests cases and the class I'm testing has 130 functions),the fact that it is redundant, and that it clearly follows the function I'm testing.
2/ Create a set of mocks for the whole unit test
public void myTest(){
int expectedValue = 0;
int output = 0;
/** Setting up the mocks **/
Object A_default = createMock(A.class);
// Case 1 : A is null
// Case 2 : A.getQuantity() is null
EasyMock.expect(A_default.getQuantity()).andReturn(null).times(1);
// Case 3 : A.getQuantity() is negative
EasyMock.expect(A_default.getQuantity()).andReturn(-1).times(2);
// Case 4 : nominal case
EasyMock.expect(A_default.getQuantity()).andReturn(5).times(3);
// End Set Up
EasyMock.replay(A_default);
/** Executing the tests **/
// Case 1 : A is null
expectedValue = 0;
output = functionsToTest.getProperQuantity(null);
assertEquals(expectedValue, output);
// Case 2 : A.getQuantity() is null
expectedValue = 0;
output = functionsToTest.getProperQuantity(A_default);
assertEquals(expectedValue, output);
// Case 3 : A.getQuantity() is negative
expectedValue = 0;
output = functionsToTest.getProperQuantity(A_default);
assertEquals(expectedValue, output);
// Case 4 : nominal case
expectedValue = 5;
output = functionsToTest.getProperQuantity(A_default);
assertEquals(expectedValue, output);
}
This solution is way shorter, easier, and quite readable too, but I've the feeling that there is a huge loss in robustness and flexibility : if the function I'm testing changes (even a little), the whole tests crashes and it needs to be totally redone. Plus, here again, it follows the code it should test.
Upvotes: 0
Views: 66
Reputation: 1080
First, your tests should not look like production code. Your unit tests should only test the behaviour of a function, that means you should pass an argument and just expect the result.
Secondly, the problem you are having with instantiating a lot of mocks in order to run a single test is usually a code smell. Your design is not testable and the only solution to this is to redesign your software. For example, if you have to instantiate 15 mocks for a single function call on a class then this class has so many dependencies, and that is bad design. Probably your class knows/does too much and violates the Single Responsibility Rule.
In order to deal with that, you have to break the dependencies and extract some classes that do only one thing, so that way these classes are more testable. I know this sounds like a huge refactoring but there is no other way to solve this. If you have high coupling on your software the only way to deal with it is to break the dependencies.
There is a practice called TDD, so you can drive your design through tests, that way your code is always testable. However, the most important thing is whenever you design your application, you should always be careful with the dependencies and the public interfaces, cause that's what you are about to test later.
Upvotes: 2