best practices unit testing
DESCRIPTION
A little insight on some best practices for writing good, simple and easy to maintain unit tests.TRANSCRIPT
Unit testing best practicesLazo Apostolovski
Tricode BVDe Schutterij 12 -18
3905 PL VeenendaalThe Netherlands
tel: 0318 - 559210 fax: 0318 - 650909
Experience is a hard teacher because she gives the test first, the lesson afterward.
— Chinese proverb
Don’t make unnecessary assertions@Testpublic void scenarioOne() { User result = service.doSomething(user); assertThat(result.someState(), is(“Yes”));}
@Testpublic void scenarioTwo() { User result = service.doSomething(user); assertThat(result.someState(), is(“Yes”)); assertThat(result.isEnabled(), is(true));}
@Testpublic void useless(){ assertTrue(false);}
Use annotation to test thrown exceptions
GOOD:
@Test(expected = Exception.class)public void testSomethig() { service.methodThrowsException();}
BAD:
@Testpublic void testSomething() { boolean haveException = false; try { service.methodThrowsException(); } catch (Exception e) { haveException = true; }
assertTrue(haveException);}
Don't test Java
public class MyException extends Exception { public MyException(String message) { super(message); }}
public void someMethod() throws MyException { throw new MyException("SomeMessage");}
@Testpublic void testSomething() { try { service.someMethod(); } catch (MyException e) { assertThat(e.getMessage(), is("SomeMessage")); }}
Don’t test POJO objectspublic class POJO { private String field; public String getField() { return field; } public void setField(String field) { this.field = field; }}
@Testpublic void testPojo() { POJO pojo = new POJO(); pojo.setField("value"); assertThat(pojo.getField(), is("value"));}
I remember growing up with television, from the time it was just a test pattern, with maybe a little bit of programming once in a while.
— Francis Ford Coppola
Mock all external services and states
@Testpublic void multiple() { Dao dao = mock(Dao.class); EmailService emailService = mock(EmailService.class); RemoteControl remoteControl = mock(RemoteControl.class); Service service = new Service(dao, emailService, remoteControl);
User user = new User(); doReturn(10).when(emailService).sendEmail(); doReturn(10).when(dao).store(user);
service.save(user); verify(remoteControl, times(1)).turnOn();}
Avoid unnecessary preconditions and verifications
public class Service { private Dao dao; public Service(Dao dao) { this.dao = dao; } public int save(User user) { return dao.store(user); }}
public interface Dao { public int store(Object o); public Object read() ;}
@Testpublic void multiple() { Dao dao = mock(Dao.class); User user = new User(); Service service = new Service(dao); doReturn(new User()).when(dao).read(); doReturn(10).when(dao).store(user); assertThat(service.save(user), is(10)); verify(dao, times(1)).store(user);}
Test one code unit on a time
@Testpublic void multiple() { Service service = new Service(); assertThat(service.doSomething(), is("something")); assertThat(service.calculate(), is(10));}
public class Service { public String doSomething() { return "something"; } public Integer calculate() { return 10; }}
Write multiple test scenarios
public class Service { public String doSomething() { return "something"; } public Integer calculate() { return 10; }}
@Testpublic void testDoSomething() { Service service = new Service(); assertThat(service.doSomething(), is("something"));}
@Testpublic void testCalculate() { Service service = new Service(); assertThat(service.calculate(), is(10));}
Don’t rely on indirect testing
public class Service { public String doSomething() { return new AnotherService().doSomething(); }}public class AnotherService { public String doSomething() { return "someThing"; }}
@Testpublic void testAnotherService() { Service service = new Service(); assertThat(service.doSomething(), is("someThing"));}
Design is not just what it looks like and feels like. Design is how it works.
— Steve Jobs
If test get complicated, we need better code design
• Complex test objects need to be created to test
simple scenario (otherwise null pointers are fired)
• More than 4 dependencies need to be mocked.
• Test have too much mock statements for one test
scenario
• Test is getting more than ~5-10 lines of code.
• If you get in any of this states,re-factor your code
How to indicate bad code design?
Give tests good names
● shouldCreateNewUserForGivenUsername
● shouldThrowExceptionWhenUsernameIsNull
● shouldStoreUserToDatabase
● shouldActivateUserByUsername
● shouldEncryptUserPassword
● shouldValidateUserPassword
Don’t skip unit tests
@Ignore@Testpublic void testPojo() { Service service = new Service(); assertThat(service.doSomething(), is("someThing"));}
● Having invalid test cases in our source code will not help anyone.
● If test scenario is invalid, then remove it.
Add new tests for every bug you find
• Will keep an eye on a bug for us• Will prevent us of making changes that will make
the bug reappear• It is the agile way• It always good to have more unit tests
How will this help us?
Parametrize where you can
@RunWith(value = Parameterized.class)public class Example { private final Service service = new Service(); private final String value; private final boolean result; @Parameterized.Parameters public static Collection<Object[]> data() { final Object[][] data = new Object[][]{ {StringUtils.EMPTY, Boolean.FALSE}, {"123_some", Boolean.FALSE}, {null, Boolean.TRUE}, }; return Arrays.asList(data); } public Example(final String value, final boolean result) { this.value = value; this.result = result; } @Test public void isValid() { assertThat(service.doSomething(value), is(equalTo(result))); }}
Final notes
Tests are documentation tool. New developer can have more insight about what code do.
Test everything that could possibly break
Don’t test configuration settings
Don’t test logging
Keep tests independent
Follow us:tricode.nlfacebook.com/tricodelinkedin.com/company/tricodeslideshare.net/tricodetwitter.com/tricode