You are on page 1of 63

Java Unit Testing

Advanced practices for agile code


Presented by Nizar Garrache

Security Solutions for a Changing World

2010 Oberthur Technologies

Agenda
Testing overview Double objects Test Driven Development Best practices Some great tools

Security Solutions for a Changing World

Testing overview

Security Solutions for a Changing World

Why we test?

Security Solutions for a Changing World

Why we write tests?


Understand requirements Ensure non regression Improve productivity Allow refactoring Explain code Avoid technical debt

Security Solutions for a Changing World

Test types
Unit tests Integration tests Functional tests Performance tests Acceptance tests

Security Solutions for a Changing World

Unit tests

1 Unit Test 1 methods Behavior/State Dependencies shouldnt be used!

Security Solutions for a Changing World

Double objects

Security Solutions for a Changing World

Double objects?!
Dummy object
Passed as argument but never used. Just to fill parameter lists.

Fake object
Working implementations, but take some shortcut which makes them not suitable for production.

Stub
Pre-programmed with responses to calls made during tests. Sometimes with a memory.
http://martinfowler.com/

Mock
Pre-programmed with expectations which form a specification of the calls they are expected to receive.
Security Solutions for a Changing World 9

Mockito

Security Solutions for a Changing World

10

Manual Dummy VS Mockito

Drink drink = new Drink("martini", null); Drink drink = new Drink("martini", new VolumeImpl(0)); Drink drink = new Drink("martini", new DummyVolume());

Drink drink = new Drink("martini", mock(Volume.class));

Security Solutions for a Changing World

11

Manual Dummy VS Mockito


No NPE No constructors changes impacts Work with interfaces and classes Less code/test dependencies

Security Solutions for a Changing World

12

Manual Fake VS Mockito

AtomicClock fakeAtomicClock = new AtomicClock() { @Override public long getTime() { return System.nanoTime(); } }

AtomicClock fakeClock = mock(AtomicClock.class); when(fakeClock.getTime()).thenReturn(1, 2, 3, 4, 5, 6, 7);

Security Solutions for a Changing World

13

Manual Fake VS Mockito


A Fake implementation could be complex (e.g. memory DB)
This is not a Mock framework role!

Only for stateless implementations


More flexible Easier testing No need to change code

Security Solutions for a Changing World

14

Manual Stub VS Mockito


@Test public void shouldSendEmail() { SmtpServerStub smtpServer = new SmtpServerStub(); NotificationServer(smtpServer); NotificationServer notificationServer = new boolean wasNotified = notificationServer.notify(new assertTrue(wasNotified); } User("john@doe.com"));

class SmtpServerStub implements SmtpServer { @Override public boolean send(Email email) { count++; return true; } int count = 0; int count() { return count; } }
Security Solutions for a Changing World 15

Manual Stub VS Mockito


@Test public void shouldSendEmail() { SmtpServer smtpServer = mock(SmtpServer.class); when(smtpServer.send(any(Email.class))).thenReturn(true); NotificationServer notificationServer = new NotificationServer(smtpServer); boolean wasNotified = notificationServer.notify(new assertTrue(wasNotified); } User("john@doe.com"));

Security Solutions for a Changing World

16

Manual Mock VS Mockito


@Test public void shouldSendEmail() { SmtpServerStub smtpServer = new SmtpServerStub(); NotificationServer(smtpServer); NotificationServer notificationServer = new notificationServer.notify(new User("john@doe.com")); assertThat(smtpServer.count()).isEqualTo(1); }

class SmtpServerStub implements SmtpServer { @Override public boolean send(Email email) { count++; return true; } int count = 0; int count() { return count; } }
Security Solutions for a Changing World 17

Manual Mock VS Mockito


@Test public void shouldSendEmail() { SmtpServer smtpServer = mock(SmtpServer.class); NotificationServer notificationServer = new NotificationServer(smtpServer); notificationServer.notify(new User("john@doe.com")); verify(smtpServer).send(any(Email.class)); }

Security Solutions for a Changing World

18

Mocks with Mockito


Check behavior and interactions Assertions and/or verifications Test exceptions handling Check multiples calls Parameters capture Multiples responses Use annotations

Security Solutions for a Changing World

19

Demo Mockito

Security Solutions for a Changing World

20

Test Driven Development

Security Solutions for a Changing World

21

TDD history
Officially developed by Kent Beck Related to the test-first programming concepts of XP (1999) Now, created more general interest in its own right First book in 2002

Security Solutions for a Changing World

22

Whats TDD?

Add a Test

Pass Run the Test Fail Make a little change

Fail Run the Test Pass Refactor

Security Solutions for a Changing World

23

Whats TDD?

RED GREEN REFACTOR

Security Solutions for a Changing World

24

TDD philosophy
Programming by intention Mastering requirements
Implement only identified requirements Decomposing requirements into a test list Write only what we know how to test

Emergent Design
Start with the simplest design/architecture Evolve it if needed

Dont forget to refactor!


Both production & test code

Others
KISS (Keep It Simple, Stupid) YAGNI (You Aint Gonna Need It) DRY (Dont Repeat Yourself)

Fixing Also for Bug Code and Legacy

Security Solutions for a Changing World

25

Demo TDD

Security Solutions for a Changing World

26

Best practices

Security Solutions for a Changing World

27

Readability Tips

Security Solutions for a Changing World

28

Tip 1: Good naming (1/2)


testIsSimCardMatchOperator1() testIsSimCardMatchOperator2() testRetrieveServiceAndAssertActiveOk() testRetrieveServiceAndAssertActiveKo1() testRetrieveServiceAndAssertActiveKo2()

isSimCardMatchOperator_Match_ReturnTrue() isSimCardMatchOperator_Mismatch_ReturnFalse() retrieveServiceAndAssertActive_NotFound_ThrowException() retrieveServiceAndAssertActive_NotActive_ThrowException() retrieveServiceAndAssertActive_Active_UpdateService()

Security Solutions for a Changing World

29

Tip 1: Good naming (2/2)


methodName_BehaviorUnderTest_ExpectedBehavior()

divide_2ndParamIsZero_ThrowException()

In divide() when the 2nd parameter is zero then throw an exception

Security Solutions for a Changing World

30

Tip 2: Dont hide useful information

public class CustomerDaoTest { @Test public void testGetNameById() { CustomerDao customerDao = new CustomerDao(); JpaUtils.injectEntityManager(customerDao); String name = customerDao.getGetNameById(1); assertEquals(name, "Kevin"); } }

Security Solutions for a Changing World

31

Tip 3: Hide useless information


Setup & Teardown Fixture Creation Method Creation Builder Static Import

Security Solutions for a Changing World

32

Tip 3: Setup
@Test public void assertCardProfileNfc_IsNfc_DoNothing() throws CardProfileNotNfcException { EligibilityService eligibilityService = new EligibilityServiceBean(); CardProfile cardProfile = new CardProfile(); cardProfile.setSwpHciVersion(SWP_HCI_VERSION); eligibilityService.setCardProfile(cardProfile); eligibilityService.assertCardProfileNfc(); }

Security Solutions for a Changing World

33

Tip 3: Setup
private EligibilityService eligibilityService; @Before public void setUp() { eligibilityService = new EligibilityServiceBean(); } @Test public void assertCardProfileNfc_IsNfc_DoNothing() throws CardProfileNotNfcException { CardProfile cardProfile = new CardProfile(); cardProfile.setSwpHciVersion(SWP_HCI_VERSION); eligibilityService.setCardProfile(cardProfile); eligibilityService.assertCardProfileNfc(); }

Security Solutions for a Changing World

34

Tip 3: Creation Method


@Test public void testIsHostConfigurationValid() { Properties properties = null; try { InputStream inputStream = ClassLoader .getSystemResourceAsStream("host.properties"); properties.load(inputStream); } catch (Exception e) { logger.error("Config file not found", e); return; } // Test code }

Security Solutions for a Changing World

35

Tip 3: Creation Method


private Properties loadConfigFile(String file) { Properties properties = null; try { InputStream inputStream = ClassLoader .getSystemResourceAsStream(file); properties.load(inputStream); } catch (Exception e) { logger.error("Config file not found", e); return null; } return properties; }

@Test public void testIsHostConfigurationValid() { Properties properties = loadConfigFile("host.properties"); // Test code }


Security Solutions for a Changing World 36

Tip 3: Creation Builder

Map<Date, BigDecimal> charges = new HashMap<Date, BigDecimal>(); charges.put(DATE_DEBUT_13_03_2011, new BigDecimal(QUANTITE_1000)); charges.put(addDaysMonthsToDate(DATE_DEBUT_13_03_2011, 2), new BigDecimal(QUANTITE_3000));

Security Solutions for a Changing World

37

Tip 3: Creation Builder

Map<Date, BigDecimal> charges = new MapCharges<Date>() .with(DATE_DEBUT_13_03_2011, QUANTITE_1000) .and(addDaysMonthsToDate(DATE_DEBUT_13_03_2011, 2), QUANTITE_3000) .build();

Security Solutions for a Changing World

38

Tip 3: Creation Builder


private class MapCharges<T> { private Map<T, BigDecimal> cdcMap = new HashMap<T, BigDecimal>(); public MapCharges<T> with(T dateDebut, int quantity) { cdc.put(dateDebut, new BigDecimal(quantity)); return this; } public MapCharges<T> and(T dateDebut, int quantity) { return with(dateDebut, quantity); } Public Map<T, BigDecimal> build() { return cdcMap; } }

Security Solutions for a Changing World

39

Tip 4: try/catch fail()


@Test public void testGetNameById() { CustomerDao customerDao = new CustomerDao(); JpaUtils.injectEntityManager(customerDao); try { String name = customerDao.getGetNameById(1); } catch (Exception e) { e.printStackTrace(); Assert.fail(); } assertEquals(name, NAME_FOR_TEST); }

Security Solutions for a Changing World

40

Tip 4: try/catch fail()


@Test public void testGetNameById() throws Exception { CustomerDao customerDao = new CustomerDao(); JpaUtils.injectEntityManager(customerDao, NAME_FOR_TEST); String name = customerDao.getGetNameById(1); assertEquals(name, NAME_FOR_TEST); }

Security Solutions for a Changing World

41

Tip 5: Guard Assert

if (customers != null) { assertEquals(customers.size(), 3); } else { fail(); }

assertNotNull(customers); assertEquals(customers.size(), 3);

Security Solutions for a Changing World

42

Tip 6: Use toString()

assertEquals( "Service not equal: " + result.getId() + "/" + result.getServId() + "/" + result.getVersion(), result, expectedService);

assertEquals("Service not equal: " + result, result, expectedService);

Security Solutions for a Changing World

43

Tip 7: Magic number, magic null


Site siteNord = createSite(NORD, 10); Site siteSud = createSite(SUD, 12); int affectations = dao.selectAffectationsNord(siteNord, siteSud, null); assertThat(affectations).isEqualTo(10);

Site siteNord = createSite(NORD, AFFECTATION_NORD); Site siteSud = createSite(SUD, AFFECTATION_SUD); int affectations = dao.selectAffectationsNord(siteNord, siteSud, SITE_NULL); assertThat(affectations).isEqualTo(AFFECTATION_NORD);
Security Solutions for a Changing World 44

Tip 8: Same test structure


@Test public void testGetNameById() { // Given CustomerDao customerDao = new CustomerDao(); JpaUtils.injectEntityManager(customerDao, NAME_FOR_TEST); // When String name = customerDao.getGetNameById(1); // Then assertEquals(name, NAME_FOR_TEST); }

Security Solutions for a Changing World

45

Tip 9: Nice assertions


Dont do that:
assertFalse(toto == null); assertTrue(list.size() == 0); assertEquals(result, null);

Use Fest Assert!

Security Solutions for a Changing World

46

Robustness Tips

Security Solutions for a Changing World

47

Tip 1: No duplication
Hide useless information
Creation Method Creation Builder

Test Helper Custom assertions

Security Solutions for a Changing World

48

Tip 2: One behavior per test method


public final class AlgorithmApplet { public static byte[] convertHexaToByte(String s); }

public final class MessageFormat { public static String getPScript(String slot, String serviceLabel, String passPhrase, byte[] key, long counterValue, String salt) throws SASException; }

Security Solutions for a Changing World

49

Tip 2: One behavior per test method


@Test public void testGetPScript() throws SASException { String passPhrase1 = "12345678"; String slot1 = "01"; String serviceLabel1 = "Service1"; byte[] key1 = AlgorithmApplet .convertHexaToByte("00112233445566778899aabbccddeeff00112233"); String salt = "00112233445566778899aabb"; String pScript1 = MessageFormat.getPScript(slot1, serviceLabel1, passPhrase1, key1, 0, salt); assertEquals("script must be", pScript1, "AA421d01085365727669636531003400000000000000000011223344 5566778899aabbC3518DA926D9C3D8F5FE97D384457B91EDD29549AA2 5A48F9ED93F8351482C3502"); }

Security Solutions for a Changing World

50

Tip 3: Avoid Reflection


@Test public void testIsEligible() { // Given CustomerDao customerDao = new CustomerDao(); JpaUtils.injectEntityManager(customerDao, NAME_FOR_TEST, ELIGIBLE); // When Method isEligible = ReflectHelper.getPrivateMethod(customerDao, isEligible.setAccessible(true); boolean eligible = isEligible.invoke(customerDao); // Then assertTrue(eligible); }

"isEligible");

Security Solutions for a Changing World

51

Tip 3: Avoid Reflection


@Test public void testIsEligible() { // Given CustomerDao customerDao = new CustomerDao(); JpaUtils.injectEntityManager(customerDao, NAME_FOR_TEST, ELIGIBLE); // When boolean eligible = customerDao.isEligible(NAME_FOR_TEST); // Then assertTrue(eligible); }

Security Solutions for a Changing World

52

Tip 4: What to test?


public String xor(char[] a, char[] b) { int length = Math.min(a.length, b.length); int[] result = new int[length]; StringBuffer buf = new StringBuffer(); for (int i = 0; i < length; i++) { result[i] = (a[i] ^ b[i]); buf.append(result[i]); } String str = buf.toString(); return str; }
cation State verifi l Low Leve

Security Solutions for a Changing World

53

Tip 4: What to test?


public String getPersonalizationScript(String slot, String serviceLabel, String passPhrase,OTPType otpType, String masterKeyRef, byte[] tokenKey,long counterValue, String issuerId) throws byte[] masterKey = getMasterKey(masterKeyRef,issuerId); byte[] key = getDiversifiedKey(otpType, masterKey, tokenKey); MasterKey mk = findByMasterKeyRef(masterKeyRef, issuerId); String salt= mk.getSaltValue(); String personalizationScript = MessageFormat.getPScript(slot, serviceLabel, passPhrase, key, counterValue,salt); return personalizationScript; }
erification Behavior v High Level

SASException {

Security Solutions for a Changing World

54

Some tools

Security Solutions for a Changing World

55

MoreUnit
Open source Eclipse plugin http://moreunit.sourceforge.net/ See whats tested Jump to test Run current test Refactoring friendly Generate tests and Mocks

Security Solutions for a Changing World

56

infinitest
Open source Eclipse plugin http://infinitest.github.com/ Continuous testing Run all impacted tests after each code change

Security Solutions for a Changing World

57

Demo MoreUnit & infinitest

Security Solutions for a Changing World

58

Fest Assert
Open source framework http://fest.easytesting.org/ More readable and fluent assertions Method chaining pattern

Security Solutions for a Changing World

59

Fest Assert example (1/2)

for (Company compagny : companies) { Assert.assertFalse(compagny.getStatus().equalsIgnoreCase( ConstantsUtil.CANCELLED)); }

assertThat(companies).onProperty("status").containsOnly(VALID);

Security Solutions for a Changing World

60

Fest Assert example (2/2)


String host = ConfigurationStore.getInstance().getTcpServerHost(); assertThat(host).isEqualTo("127.0.0.1"); String protocol = properties .getProperty(CONFIGURATION_JSSE_SSL_PROTOCOL); assertThat(protocol).isNotNull().isIn("SSL", "TLS"); int removed = employees.removeFired(); assertThat(removed).isZero(); List<Employee> newEmployees = employees.hired(TODAY); assertThat(newEmployees).hasSize(6).contains(tom, sam); assertThat(yoda).isInstanceOf(Jedi.class).isEqualTo(foundJedi) .isNotEqualTo(foundSith);

Security Solutions for a Changing World

61

Books

Security Solutions for a Changing World

62

Thank you! Any question?


Nizar Garrache Java Agile Architect n.garrache@oberthur.com

Security Solutions for a Changing World

2010 Oberthur Technologies

You might also like