advance unittest

29
Advance Unit Testing Or How I Learned to Stop Worrying and Love the unit- test

Upload: reza-arbabi

Post on 14-Apr-2017

92 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Advance unittest

Advance Unit Testing

Or How I Learned to Stop Worrying and Love the unit-test

Page 2: Advance unittest

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

Page 3: Advance unittest

Test Doubles

▪Dummies

▪Fakes

▪Mock

▪Stubs

Different types of mocks

▪Pure mock vs nice mock

▪Partial mocks and spies

3

Page 4: Advance unittest

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

Page 5: Advance unittest

Examplepublic 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

Page 6: Advance unittest

State Verification Sample@Testpublic void testSendInvitation() throws InviteesRedListed { InvitationService sut = new InvitationService(emailService, emailFilterService); Invitation invitation = new Invitation(); invitation.emailAddress = "[email protected]"; 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 = "[email protected]"; sut.sendInvitation(invitation);} 6

Page 7: Advance unittest

Behavior Verification Sample

@Testpublic 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 = "[email protected]"; 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("[email protected]"); inOrder.verify(emailServiceMock, times(1)).sendEmailInvite(invitation);}

7

Page 8: Advance unittest

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

Page 9: Advance unittest

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

Page 10: Advance unittest

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

Page 11: Advance unittest

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

Page 12: Advance unittest

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

Page 13: Advance unittest

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

Page 14: Advance unittest

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

Page 15: Advance unittest

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

Page 16: Advance unittest

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

Page 17: Advance unittest

Writing Clean Test

17

Page 18: Advance unittest

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

Page 19: Advance unittest

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

Page 20: Advance unittest

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

Page 21: Advance unittest

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

Page 22: Advance unittest

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

Page 23: Advance unittest

Be mindful of scopes

@Mock Map<String, Integer> counters;

@BeforeTestpublic 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

Page 24: Advance unittest

Unit Test Design Patterns

24

Page 25: Advance unittest

Can we write a test that is both DAMP and DRY?

The answer is Yes!

25

Page 26: Advance unittest

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

Page 27: Advance unittest

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

Page 28: Advance unittest

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

Page 29: Advance unittest

Q&AHope you enjoyed!

29