ART OF UNIT TESTING
how to do it right
About speaker
Few words about me:
◦ 10 years in software development
◦ Technical Lead at V.I.Tech
◦ Like culture
Hello!
I AM DMYTRO PATSERKOVSKYI
What we are talk about
◦ about developers thoughts;
◦ about code quality;
◦ about unit tests tips;
◦ about developers.
Developer, Level 1.
Developer, Level 1: thoughts
◦ code is good;
◦ code always works properly;
◦ if you think that you found a bug,
read above.
Developer, Level 2.
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;
Developer, Level 5.
? 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
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.
Developer, Level 11.
? DO UNIT TESTS HAVE PRINCIPLES?
unit
simple
independent
complete
isolated
deterministic
? 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.
? 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
? PRINCIPLES: UNIT & SIMPLE
? 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();
}
? 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()));
}
? 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));
}
? PRINCIPLES: ISOLATED
? 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());
}
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.
Developer, Level 30.
? HOW MANY UNITS I SHOULD WRITE TO SLEEP SAFETY?
one per class?
one per method?
90%?
? CODE COVERAGE
Line Coverage
Branch CoverageOverall Coverage
85% - min overall coverage
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.
Developer, Level 70.
? WHAT ELSE UNITS COULD GIVE ME?
units as documentation;
units as design;
? 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.
? 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
Developer, Level 70: thoughts
◦ unit tests documents our code;
◦ TDD/BDD helps to look at design of
code from different point of view.
What next?
QUICK TIPS
Know what you're
testing
A test written without a clear objective
in mind is easy to spot
Naming Convention
One of the first thing that you notice about
a failed test is its name
Do Repeat Yourself
Readability is very important in unit testing,
so it is acceptable to have duplicate code
Test Results, not
Implementation
Successful unit testing requires writing tests
that would only fail in case of an actual
error or requirement change
Thanks!
ANY QUESTIONS?

Art of unit testing: how to do it right

  • 1.
    ART OF UNITTESTING how to do it right
  • 2.
    About speaker Few wordsabout me: ◦ 10 years in software development ◦ Technical Lead at V.I.Tech ◦ Like culture Hello! I AM DMYTRO PATSERKOVSKYI
  • 3.
    What we aretalk about ◦ about developers thoughts; ◦ about code quality; ◦ about unit tests tips; ◦ about developers.
  • 4.
  • 5.
    Developer, Level 1:thoughts ◦ code is good; ◦ code always works properly; ◦ if you think that you found a bug, read above.
  • 6.
  • 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;
  • 8.
  • 9.
    ? WHAT ISUNIT 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.
  • 11.
  • 12.
    ? DO UNITTESTS HAVE PRINCIPLES? unit simple independent complete isolated deterministic
  • 13.
    ? PROJECT ARCHITECTURE Developerfaced 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
  • 15.
  • 16.
    ? PRINCIPLES: INDEPENDENT testshould 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 testshould 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 encapsulatedlogic 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)); }
  • 19.
  • 20.
    ? PRINCIPLES: DETERMINISTIC Unittest 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 1125: 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.
  • 22.
  • 23.
    ? HOW MANYUNITS I SHOULD WRITE TO SLEEP SAFETY? one per class? one per method? 90%?
  • 24.
    ? CODE COVERAGE LineCoverage 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.
  • 26.
  • 27.
    ? WHAT ELSEUNITS COULD GIVE ME? units as documentation; units as design;
  • 28.
    ? UNITS ASDOCUMENTATION 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 ASDESIGN 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.
  • 31.
  • 32.
  • 33.
    Know what you're testing Atest written without a clear objective in mind is easy to spot
  • 34.
    Naming Convention One ofthe first thing that you notice about a failed test is its name
  • 35.
    Do Repeat Yourself Readabilityis very important in unit testing, so it is acceptable to have duplicate code
  • 36.
    Test Results, not Implementation Successfulunit testing requires writing tests that would only fail in case of an actual error or requirement change
  • 37.