This document discusses unit testing at different developer experience levels. A level 1 developer thinks code always works properly without needing tests. A level 2 developer sees value in occasional manual testing but not unit tests due to cost. A level 5 developer sees value in basic unit tests to prevent bugs. A level 11 developer understands principles of unit tests like being simple, independent, complete, and deterministic. Higher levels emphasize high test coverage, using tests for documentation and design, and ensuring tests are reproducible. The document provides examples of good and bad unit test code and discusses tools for measuring coverage.
7. Developer, Level 2: thoughts
◦ as a usually, code works properly;
◦ code could have bugs;
◦ it is to expensive to write unit tests;
◦ we could test our code manually,
time to time;
9. ? WHAT IS UNIT TEST?
@BeforeClass
public void testBeforeSuite() {
prepareMailService();
}
@Test
public void testEmailService() {
MailService service= generateMailService();
assertNotNull(service);
}
@Test
public void testSending() {
MailService service= generateMailService();
assertTrue(service.sendMail(subject, message));
}
Setup
Test Method
Assertion
10. Developer, Level 5: thoughts
◦ bugs happens;
◦ we should prevent bugs;
◦ it is cheaper to write unit tests;
◦ one unit test per class / method
should be enough.
12. ? DO UNIT TESTS HAVE PRINCIPLES?
unit
simple
independent
complete
isolated
deterministic
13. ? PROJECT ARCHITECTURE
Developer faced with component for sending emails.
Services:
◦ MailService - service for sending emails. Based on
Javax Mail.
◦ LogsStorage - service for collecting logs about
mails sending and flushing it to persistent storage.
◦ LogsMailService - wrapper for work with emails
and logs.
14. ? PRINCIPLES: UNIT & SIMPLE
// Bad
@Test
public void testEmailSending() {
MailService service= generateMailService();
assertNotNull(service);
MailBuilder builder= new MailBuilder();
Mail mail = new MailBuilder().
.newMail()
.addSubject("my mail")
.addRecipient(firstRecipient)
.addRecipient(secondRecipient)
.build();
assertNotNull(mail);
assertEquals(EXPECT, mail.getSubject());
...
assertTrue(service.sendMail(mail));
}
// Good
@BeforeTest
public void initialize() {
service= generateMailService();
}
@Test
public void testEmailSending() {
assertTrue(
service.sendMail(prepareMail()));
}
each test should cover one piece of code
16. ? PRINCIPLES: INDEPENDENT
test should runs in random order and in parallel
// Bad
@Test
public void testEmailLogsSuccessful() {
mlStorage.logGood();
assertEquals(1, mlStorage.getCountGood());
assertEquals(1, mlStorage.getCountAll());
}
@Test
public void testEmailLogsFailed() {
mlStorage.logBad();
assertEquals(1, mlStorage.getCountBad());
assertEquals(2, mlStorage.getCountAll());
}
// Good
@Test
public void testEmailLogsSuccessful() {
mlStorage.logGood();
assertEquals(1, mlStorage.getCountGood());
assertEquals(1, mlStorage.getCountAll());
}
@Test
public void testEmailLogsFailed() {
mlStorage.logBad();
assertEquals(1, mlStorage.getCountBad());
assertEquals(1, mlStorage.getCountAll());
}
@AfterMethod
public void cleanAfter() {
mlStorage.cleanState();
}
17. ? PRINCIPLES: COMPLETE
test should cover both: success and fail cases
// Bad
@BeforeTest
public void initialize() {
service= generateMailService();
}
@Test
public void testEmailSending() {
assertTrue(service
.sendMail(prepareGoodMail)));
}
// Good
@BeforeTest
public void initialize() {
service= generateMailService();
}
@Test
public void testEmailSendingSuccess() {
assertTrue(service
.sendMail(prepareGoodMail()));
}
@Test
public void testEmailSendingFailed() {
assertFalse(service
.sendMail(prepareBadMail()));
}
18. ? PRINCIPLES: ISOLATED
encapsulated logic should be covered with separated
tests
// Bad
@Before
public void before() {
service = new LogMailService();
service.setMailService(
new MailService());
}
@Test
public void testLogEmailService() {
assertTrue(
service.sendMail(mail));
}
// Good
@Mock
MailService mlMock;
@Before
public void before() {
service = new LogMailService();
}
@Test
public void testLogEmailService() {
when(mock.sendMail(mail))
.thenReturn(true);
service.setMailService(mlMock)
assertTrue(service.sendMail(mail));
times(1, mlMock.sendMail(mail));
}
20. ? PRINCIPLES: DETERMINISTIC
Unit test behaviour should be reproducible
// Bad
@Before
public void before() {
service = new MailService();
}
@Test
public void testEmailService() {
service.sendMail(mail);
delay(1000);
assertEquals(1,
service.getSentCount());
}
// Good
@Mock
Transport transportMock;
@Before
public void before() {
service = new MailService();
service.setTransport(transportMock);
}
@Test
public void testEmailService() {
service.sendMail(mail);
assertEquals(1,
service.getSentCount());
}
21. Developer, Level 11 25: thoughts
◦ we have to use isolation frameworks;
◦ our code should be testable;
◦ unit tests improves code quality;
◦ unit tests catch bugs early;
◦ unit tests speed up debugging and
troubleshooting.
23. ? HOW MANY UNITS I SHOULD WRITE TO SLEEP SAFETY?
one per class?
one per method?
90%?
24. ? CODE COVERAGE
Line Coverage
Branch CoverageOverall Coverage
85% - min overall coverage
25. Developer, Level 30: thoughts
◦ we should know test coverage of
our code;
◦ we should use tools for calculating
coverage;
◦ high code coverage let us make
refactoring safety.
27. ? WHAT ELSE UNITS COULD GIVE ME?
units as documentation;
units as design;
28. ? UNITS AS DOCUMENTATION
Units are NOT a reference.
Units could (should) be used as helpful
documentation about:
◦ definition of class/interface contract;
◦ corner cases of functionality;
◦ list of behaviour.
29. ? UNITS AS DESIGN
Writing the test first forces you to think
through your design and what it must
accomplish before you write the code.
TDD / BDD
30. Developer, Level 70: thoughts
◦ unit tests documents our code;
◦ TDD/BDD helps to look at design of
code from different point of view.