I'm working with a start-up at the moment and the founder had a working prototype by the time I joined. Utilising Android and Java for the mobile device, Tomcat, MYSQL and Java for the backend and Apache2, PHP, JS and HTML for the front-end. It was impressive because he was able to get his desired response from the system but there was not 1 single test in any of those systems.
I began my work on implementing unit tests for the back-end. Using Eclipse, JUnit and Mockito. Eclipse is my current Java IDE of choice across OSes. JUnit [http://junit.org/] is a simple and effective testing framework and Mockito [https://code.google.com/p/mockito/] is a mocking framework that is also simple yet powerful to use.
I chose these technologies because after a small investigation they seemed the easiest to get running with my existing environment and provided me a simple API to write unit-tests around messy code that I had inherited.
Example of code:
public Boolean doThisThing() throw SomeKindOfException {
variable success;
/*20 lines of dev-code commented out
but still here just-in-case*/
but still here just-in-case*/
getProperties();
openConnection();
success = doAnotherThingWith5SubMethods();
if(success) {
doThisLastThing();
return true;
}
doThisLastThing();
return true;
}
return false;
}
I was not given the opportunity to re-write the code and separate things out a little more nicely. So one method ended up doing half a dozen specific things using a combination of privately declared global variables that get referenced and variables declared in the parent method and passed all the way down by value.
My first angle of attack is to test that the method itself. What happens if getProperties() or openConnection() fails? What is returned on failure? What is returned on an exception being thrown?
This is where my Test.Class comes in. I begin to wrap these methods and mock out the calls which go all over the place and test what happens based on the information that should, could and shouldn't be returned and what effect it has on method doThisThing().
Example Unit-Test:
import static org.junit.Assert.*;
@Mock
private variable myClass;
@Before
public void setUp() {
myClass = mock(ClassIWantToTest.class);
}
@Test
public void testDoThisThing() {
myClass = new ClassIWantToTest() {
@Override
public void getProperties() { return; }
@Override
public void openConnection() { return; }
@Override
public boolean doAnotherThingWith5SubMethods {
return true;
}
@Override
public void doThisLastThing { return; }
@Override
public void openConnection() { return; }
@Override
public boolean doAnotherThingWith5SubMethods {
return true;
}
@Override
public void doThisLastThing { return; }
}
assertEquals(myClass.doThisThing(), true);
//moretests below
}
Now this seems fairly straight forward. Your test is initiating an ephemeral subclass so you can redirect the code when it runs doThisThing() into returning a static result you want so you can test the core of the method and have to also test all the other methods at the same time.
We then check the return to see if it matches what we were expecting. (assertEquals(value, expected);)
However this quickly becomes messy as you re-initialise the subclass with different overrides to get the alternative responses you want. This is where Mockito comes in handy as it's well placed to mock out these extra methods and change the returned result.
Example:
assertEquals(myPrivateVariable.doThisThing(), true);
//moretests below
}
Notice the slight different for when you are mocking a void-return method and one that returns something.
Even these humble beginnings of my testing framework were enough to showcase the dependencies within the code and massive assumptions made about variable state as you jump further into the code. I was then able to use the tests to speed up my own development by making changes and checking that nothing had broken. It also helps when arguing for more time to re-structure code to ward off too much technical debt in the pursuit of business value.
assertEquals(myClass.doThisThing(), true);
//moretests below
}
Now this seems fairly straight forward. Your test is initiating an ephemeral subclass so you can redirect the code when it runs doThisThing() into returning a static result you want so you can test the core of the method and have to also test all the other methods at the same time.
We then check the return to see if it matches what we were expecting. (assertEquals(value, expected);)
However this quickly becomes messy as you re-initialise the subclass with different overrides to get the alternative responses you want. This is where Mockito comes in handy as it's well placed to mock out these extra methods and change the returned result.
Example:
@Test
public void testDoThisThing() {
Mockito.doNothing().when(myClass).getProperties();
Mockito.doNothing().when(myClass).openConnection();
Mockito.when(myClass.doAnotherThingWith5SubMethods()
).thenReturn(true);
Mockito.doNothing().when(myClass).doThisLastThing();
Mockito.doNothing().when(myClass).getProperties();
Mockito.doNothing().when(myClass).openConnection();
Mockito.when(myClass.doAnotherThingWith5SubMethods()
).thenReturn(true);
Mockito.doNothing().when(myClass).doThisLastThing();
assertEquals(myPrivateVariable.doThisThing(), true);
//moretests below
}
Notice the slight different for when you are mocking a void-return method and one that returns something.
Even these humble beginnings of my testing framework were enough to showcase the dependencies within the code and massive assumptions made about variable state as you jump further into the code. I was then able to use the tests to speed up my own development by making changes and checking that nothing had broken. It also helps when arguing for more time to re-structure code to ward off too much technical debt in the pursuit of business value.
No comments:
Post a Comment