Advance Unit
Testing
Or How I Learned to Stop Worrying
and Love the unit-test
Why?
▪ Unit-test is the first client of your
code
▪ Verification and regression through
automation
▪ Powerful design tool
▪ Higher quality code
▪ Any many more…
2
Test Doubles
▪ Dummies
▪ Fakes
▪ Mock
▪ Stubs
Different types of mocks
▪ Pure mock vs nice mock
▪ Partial mocks and spies
3
Different type of tests
State Verification
▪ Execute one or many methods
of objects then assert expected
results.
▪ They keep continuing to pass
even if the internal of the method
changes.
Behavior Verification
▪ How the system behaves as it
operates rather than specifying
the expected end state.
▪ It verifies interaction between
the objects.
4
Example
public class InvitationService {
private final EmailService emailService;
private final EmailFilterService redList;
public InvitationService(EmailService
emailService, EmailFilterService redList) {
this.emailService = emailService;
this.redList = redList;
}
public void sendInvitation(Invitation invitation)
throws InviteeIsRedListed {
if (redList.redlisted(invitation.emailAddress)) {
throw new InviteeIsRedListed();
}
emailService.sendEmailInvite(invitation);
invitation.setIsSent(true);
}
}
5
State Verification Sample
@Test
public void testSendInvitation() throws InviteesRedListed {
InvitationService sut = new InvitationService(emailService,
emailFilterService);
Invitation invitation = new Invitation();
invitation.emailAddress = "1@mail.com";
sut.sendInvitation(invitation);
Assert.assertTrue(invitation.isSent());
}
@Test(expected = InviteesRedListed.class)
public void testSendInvitationGettingFiltered()
throws InviteesRedListed {
InvitationService sut = new InvitationService(emailService,
emailFilterService);
Invitation invitation = new Invitation();
invitation.emailAddress = "1@fake.com";
sut.sendInvitation(invitation);
} 6
Behavior Verification Sample
@Test
public void testSendInvitation() throws InviteesRedListed {
EmailService emailServiceMock = mock(EmailService.class);
EmailFilterService filterServiceMock = mock(EmailFilterService.class);
InvitationService sut = new InvitationService(emailServiceMock,
filterServiceMock);
Invitation invitation = new Invitation();
invitation.emailAddress = "1@test.com";
sut.sendInvitation(invitation);
// this is not important, if it is set by mock method then we can skip it.
Assert.assertTrue(invitation.isSent());
InOrder inOrder = inOrder(emailServiceMock, filterServiceMock);
inOrder.verify(filterServiceMock, times(1)).redlisted("1@test.com");
inOrder.verify(emailServiceMock, times(1)).sendEmailInvite(invitation);
}
7
Common fallacies of writing testable code
▪ Object construction in application logic
▪ Looking for things instead of asking for
them
▪ Doing too much work in the constructor
▪ Keeping global state
▪ Singleton pattern
▪ Static methods and utility classes
8
Example
public class ProfileService {
ProfileDataLayer dataLayer = new ProfileDataLayer();
ProfileValidator validator;
public ProfileService () {
validator = ProfileValidator.getValidator();
}
public CreateStatus create(String name, String imageUrl) {
try {
Profile profile = ProfileTransformer.transform(name, imageUrl);
validator.validate(profile);
dataLayer.insert(profile);
return CreateStatus.SUCCEED;
} catch (Exception e) {
return CreateStatus.FAILED;
}
}
Object construction
Looking for object
Static method call
9
Fixed example
public class ProfileService {
final ProfileDataLayer dataLayer;
final ProfileValidator validator;
final ProfileTransformer transformer
@Inject
public ProfileService (
ProfileDataLayer dataLayer,
ProfileValidator validator,
ProfileTransformer transformer) {
this.validator = validator;
this.dataLayer = dataLayer;
this.transformer = transformer;
}
Objects passed in
the constructor
through dependency
injection framework
10
Fixed example
public CreateStatus create(String name, String imageUrl) {
try {
Profile profile = transformer.transform(name, imageUrl);
validator.validate(profile);
dataLayer.insert(profile);
return CreateStatus.SUCCEED;
} catch (Exception e) {
return CreateStatus.FAILED;
}
}
No knowledge and
dependency on
implementation and
where logic comes
from.
11
Common fallacies of writing testable code
▪ Overusing inheritance as a method of code
reuse
▪ Lots of conditionals (IF and SWITCH
statements)
▪ Mixing value objects and service objects
▪ Breaking SRP (Single Responsibility
Principle)
12
Solutions overview
▪ Use dependency injection frameworks
▪ Favor composition over inheritance
▪ Favor polymorphism over conditions
▪ Command pattern, strategy patter, chain of
responsibility, etc
▪ Respect SRP
▪ RedFlag#1: Classes with bad postfix (Util,
Helper, Manager)
▪ RedFlag#2: Classes with more than 250 lines of
codes.
▪ RedFlag#3: Classes that have “And” or “OR” in
their names.
▪ RedFlag#4: Low Cohesion
▪ Validators, Transformation, Formatting, etc
should be in its own class.
13
Ultimate solution
▪ Test Driven Development - TDD
▪ Always prefer TDD for the system that you
own and you understanding the domain of
the problem very well.
▪ Test-after is OK when you are maintaining a
system or working on a horizontal feature
that you will need to change lots of other
people code.
14
Testing Legacy Code
▪ In this context Legacy code is a
code that is written and designed
without having testing in mind,
and it is still live code.
▪ They are usually hard to test.
15
Testing Legacy Code
▪ Refactoring is a powerful tool
▪ Introduce wrappers and
adapters
▪ Change access modifiers on
methods to make them visible
for testing
▪ public @interface VisibleForTesting {}
16
Writing Clean Test
17
Coding Style
Production
▪ DRY (Don’t Repeat Yourself)
▪ High cohesion.
▪ Methods are interacting with
each other
▪ Lifecycle at class level
Unit Test
▪ DAMP (Descriptive And
Meaningful Phrases)
▪ Low cohesion
▪ Each test (method) are self-
contained
▪ Lifecycle at test methods.
▪ Meaningful and long method
names.
18
DAMP vs DRY
DRY example:
public void test1() {
setupTestFixture(ps1, ps2, ps3);
setupMocks(pm1, pm2);
verify(pv1, pv2, pv3);
}
▪ Not readable in its entirety.
19
Clean tests
▪ Avoid testing several things in one test
▪ When you need to persist data for your test,
persist in-memory
▪ Setup and cleanup your test
▪ Test must run fast. Really fast!
▪ No sleeping thread
20
Clean tests (part 2)
▪ Make your test have a Cyclomatic
Complexity of one. Tests should not have
indention or indirections.
▪ Beware of Liar anti-pattern, always observe
test failures
▪ Do not use try-catch blocks in your tests
▪ Do not be a mock-happy tester
▪ Do not target high code coverage
21
Test life cycle, threading and
frameworks
▪ Tests’ life cycle is separate from
applications
▪ For example TestNG runs Suite,
Group, Class, Test, Method
configurations before and after tests
▪ Tests have different threading models
▪ Parallel forks vs single threaded
▪ Frameworks do a lot of legwork
▪ Exception and failure handling
▪ Reports
▪ Tooling: Assertions, data providers,
factories, dependencies
22
Be mindful of scopes
@Mock Map<String, Integer> counters;
@BeforeTest
public void setUp() { MockitoAnnotations.initMocks(this); }
@Test public void testCount1() {
Mockito.when(counters.get(TEST_KEY)).thenReturn(1);
Assert.assertEquals((int) counters.get(TEST_KEY), 1);
Mockito.verify(counters).get(TEST_KEY);
}
@Test public void testCount2() {
Mockito.when(counters.get(TEST_KEY)).thenReturn(2);
Assert.assertEquals((int) counters.get(TEST_KEY), 2);
Mockito.verify(counters).get(TEST_KEY);
}
Verification fails since
mock is set up for the
whole test.
(use e.g. BeforeMethod)
23
Unit Test Design Patterns
24
Can we write a test that is
both DAMP and DRY?
The answer is Yes!
25
Test Data Builder
public class UserProfileInputBuilder {
private String firstName;
private String lastName;
public UserProfileInputBuilder() {
this.firstName = "ftest";
this.lastName = "ltest";
...
}
public UserProfileInputBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public UserProfileInputBuilder withLastName(String lastName) {
this.lastName = lastName;
return this;
}
...
public UserProfile build() {
return new UserProfile(this.firstName,
this.lastName);
}
}
26
How to use it
EmailValidator emailValidatorMock = Mockito.mock(EmailValidator.class);
LocalizeName localizerMock = Mockito.mock(LocalizeName.class);
ProfileService sut = new ProfileService(emailValidatorMock, localizerMock);
UserProfile invalidInput =
new UserProfileInputBuilder()
.withEmail("invalid email format")
.build();
Mockito.when(emailValidator.validate(invalidInput.getEmail()).thenReturn(false));
//Exercise system and expect InvalidInputException
//Note that create user is suppose to throw an exception when the email is invalid
//We do not need to provide noise and create locale, firstName, and lastName
//for this test case.
sut.createUser(invalidInput);
27
Test Data Providers
@DataProvider(name = "drinks")
public Object[][] getDrinks() {
return new Object[][] {
{ "Whiskey", true },
{ "Daiquiri", false }
};
}
@Test(dataProvider = "drinks")
public void testDrinkingService(
String drink,
boolean shouldPass) {
Assert.assertEquals(service.isGood(drink), shouldPass);
}
28
Q&A
Hope you enjoyed!
29

Advance unittest

  • 1.
    Advance Unit Testing Or HowI Learned to Stop Worrying and Love the unit-test
  • 2.
    Why? ▪ Unit-test isthe first client of your code ▪ Verification and regression through automation ▪ Powerful design tool ▪ Higher quality code ▪ Any many more… 2
  • 3.
    Test Doubles ▪ Dummies ▪Fakes ▪ Mock ▪ Stubs Different types of mocks ▪ Pure mock vs nice mock ▪ Partial mocks and spies 3
  • 4.
    Different type oftests State Verification ▪ Execute one or many methods of objects then assert expected results. ▪ They keep continuing to pass even if the internal of the method changes. Behavior Verification ▪ How the system behaves as it operates rather than specifying the expected end state. ▪ It verifies interaction between the objects. 4
  • 5.
    Example public class InvitationService{ private final EmailService emailService; private final EmailFilterService redList; public InvitationService(EmailService emailService, EmailFilterService redList) { this.emailService = emailService; this.redList = redList; } public void sendInvitation(Invitation invitation) throws InviteeIsRedListed { if (redList.redlisted(invitation.emailAddress)) { throw new InviteeIsRedListed(); } emailService.sendEmailInvite(invitation); invitation.setIsSent(true); } } 5
  • 6.
    State Verification Sample @Test publicvoid testSendInvitation() throws InviteesRedListed { InvitationService sut = new InvitationService(emailService, emailFilterService); Invitation invitation = new Invitation(); invitation.emailAddress = "1@mail.com"; sut.sendInvitation(invitation); Assert.assertTrue(invitation.isSent()); } @Test(expected = InviteesRedListed.class) public void testSendInvitationGettingFiltered() throws InviteesRedListed { InvitationService sut = new InvitationService(emailService, emailFilterService); Invitation invitation = new Invitation(); invitation.emailAddress = "1@fake.com"; sut.sendInvitation(invitation); } 6
  • 7.
    Behavior Verification Sample @Test publicvoid testSendInvitation() throws InviteesRedListed { EmailService emailServiceMock = mock(EmailService.class); EmailFilterService filterServiceMock = mock(EmailFilterService.class); InvitationService sut = new InvitationService(emailServiceMock, filterServiceMock); Invitation invitation = new Invitation(); invitation.emailAddress = "1@test.com"; sut.sendInvitation(invitation); // this is not important, if it is set by mock method then we can skip it. Assert.assertTrue(invitation.isSent()); InOrder inOrder = inOrder(emailServiceMock, filterServiceMock); inOrder.verify(filterServiceMock, times(1)).redlisted("1@test.com"); inOrder.verify(emailServiceMock, times(1)).sendEmailInvite(invitation); } 7
  • 8.
    Common fallacies ofwriting testable code ▪ Object construction in application logic ▪ Looking for things instead of asking for them ▪ Doing too much work in the constructor ▪ Keeping global state ▪ Singleton pattern ▪ Static methods and utility classes 8
  • 9.
    Example public class ProfileService{ ProfileDataLayer dataLayer = new ProfileDataLayer(); ProfileValidator validator; public ProfileService () { validator = ProfileValidator.getValidator(); } public CreateStatus create(String name, String imageUrl) { try { Profile profile = ProfileTransformer.transform(name, imageUrl); validator.validate(profile); dataLayer.insert(profile); return CreateStatus.SUCCEED; } catch (Exception e) { return CreateStatus.FAILED; } } Object construction Looking for object Static method call 9
  • 10.
    Fixed example public classProfileService { final ProfileDataLayer dataLayer; final ProfileValidator validator; final ProfileTransformer transformer @Inject public ProfileService ( ProfileDataLayer dataLayer, ProfileValidator validator, ProfileTransformer transformer) { this.validator = validator; this.dataLayer = dataLayer; this.transformer = transformer; } Objects passed in the constructor through dependency injection framework 10
  • 11.
    Fixed example public CreateStatuscreate(String name, String imageUrl) { try { Profile profile = transformer.transform(name, imageUrl); validator.validate(profile); dataLayer.insert(profile); return CreateStatus.SUCCEED; } catch (Exception e) { return CreateStatus.FAILED; } } No knowledge and dependency on implementation and where logic comes from. 11
  • 12.
    Common fallacies ofwriting testable code ▪ Overusing inheritance as a method of code reuse ▪ Lots of conditionals (IF and SWITCH statements) ▪ Mixing value objects and service objects ▪ Breaking SRP (Single Responsibility Principle) 12
  • 13.
    Solutions overview ▪ Usedependency injection frameworks ▪ Favor composition over inheritance ▪ Favor polymorphism over conditions ▪ Command pattern, strategy patter, chain of responsibility, etc ▪ Respect SRP ▪ RedFlag#1: Classes with bad postfix (Util, Helper, Manager) ▪ RedFlag#2: Classes with more than 250 lines of codes. ▪ RedFlag#3: Classes that have “And” or “OR” in their names. ▪ RedFlag#4: Low Cohesion ▪ Validators, Transformation, Formatting, etc should be in its own class. 13
  • 14.
    Ultimate solution ▪ TestDriven Development - TDD ▪ Always prefer TDD for the system that you own and you understanding the domain of the problem very well. ▪ Test-after is OK when you are maintaining a system or working on a horizontal feature that you will need to change lots of other people code. 14
  • 15.
    Testing Legacy Code ▪In this context Legacy code is a code that is written and designed without having testing in mind, and it is still live code. ▪ They are usually hard to test. 15
  • 16.
    Testing Legacy Code ▪Refactoring is a powerful tool ▪ Introduce wrappers and adapters ▪ Change access modifiers on methods to make them visible for testing ▪ public @interface VisibleForTesting {} 16
  • 17.
  • 18.
    Coding Style Production ▪ DRY(Don’t Repeat Yourself) ▪ High cohesion. ▪ Methods are interacting with each other ▪ Lifecycle at class level Unit Test ▪ DAMP (Descriptive And Meaningful Phrases) ▪ Low cohesion ▪ Each test (method) are self- contained ▪ Lifecycle at test methods. ▪ Meaningful and long method names. 18
  • 19.
    DAMP vs DRY DRYexample: public void test1() { setupTestFixture(ps1, ps2, ps3); setupMocks(pm1, pm2); verify(pv1, pv2, pv3); } ▪ Not readable in its entirety. 19
  • 20.
    Clean tests ▪ Avoidtesting several things in one test ▪ When you need to persist data for your test, persist in-memory ▪ Setup and cleanup your test ▪ Test must run fast. Really fast! ▪ No sleeping thread 20
  • 21.
    Clean tests (part2) ▪ Make your test have a Cyclomatic Complexity of one. Tests should not have indention or indirections. ▪ Beware of Liar anti-pattern, always observe test failures ▪ Do not use try-catch blocks in your tests ▪ Do not be a mock-happy tester ▪ Do not target high code coverage 21
  • 22.
    Test life cycle,threading and frameworks ▪ Tests’ life cycle is separate from applications ▪ For example TestNG runs Suite, Group, Class, Test, Method configurations before and after tests ▪ Tests have different threading models ▪ Parallel forks vs single threaded ▪ Frameworks do a lot of legwork ▪ Exception and failure handling ▪ Reports ▪ Tooling: Assertions, data providers, factories, dependencies 22
  • 23.
    Be mindful ofscopes @Mock Map<String, Integer> counters; @BeforeTest public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testCount1() { Mockito.when(counters.get(TEST_KEY)).thenReturn(1); Assert.assertEquals((int) counters.get(TEST_KEY), 1); Mockito.verify(counters).get(TEST_KEY); } @Test public void testCount2() { Mockito.when(counters.get(TEST_KEY)).thenReturn(2); Assert.assertEquals((int) counters.get(TEST_KEY), 2); Mockito.verify(counters).get(TEST_KEY); } Verification fails since mock is set up for the whole test. (use e.g. BeforeMethod) 23
  • 24.
    Unit Test DesignPatterns 24
  • 25.
    Can we writea test that is both DAMP and DRY? The answer is Yes! 25
  • 26.
    Test Data Builder publicclass UserProfileInputBuilder { private String firstName; private String lastName; public UserProfileInputBuilder() { this.firstName = "ftest"; this.lastName = "ltest"; ... } public UserProfileInputBuilder withFirstName(String firstName) { this.firstName = firstName; return this; } public UserProfileInputBuilder withLastName(String lastName) { this.lastName = lastName; return this; } ... public UserProfile build() { return new UserProfile(this.firstName, this.lastName); } } 26
  • 27.
    How to useit EmailValidator emailValidatorMock = Mockito.mock(EmailValidator.class); LocalizeName localizerMock = Mockito.mock(LocalizeName.class); ProfileService sut = new ProfileService(emailValidatorMock, localizerMock); UserProfile invalidInput = new UserProfileInputBuilder() .withEmail("invalid email format") .build(); Mockito.when(emailValidator.validate(invalidInput.getEmail()).thenReturn(false)); //Exercise system and expect InvalidInputException //Note that create user is suppose to throw an exception when the email is invalid //We do not need to provide noise and create locale, firstName, and lastName //for this test case. sut.createUser(invalidInput); 27
  • 28.
    Test Data Providers @DataProvider(name= "drinks") public Object[][] getDrinks() { return new Object[][] { { "Whiskey", true }, { "Daiquiri", false } }; } @Test(dataProvider = "drinks") public void testDrinkingService( String drink, boolean shouldPass) { Assert.assertEquals(service.isGood(drink), shouldPass); } 28
  • 29.