unit testing & mocks - komputeralgebra...

49
Bad and Best Practices in Unit Testing & Mocking Istvan Neuwirth 2014

Upload: tranhanh

Post on 13-Jun-2018

218 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Bad and

Best Practices

in Unit Testing & Mocking

Istvan Neuwirth 2014

Page 2: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Unit Test

• Tests for testing the smallest testable parts of the software (methods/classes)

Page 3: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Legend

Neutral/Others

Best Practice

Bad Practice

Page 4: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Unit Test Principles

• Fast

• Isolated

• Repeatable

• Self-verifying

• Timely

Page 5: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Test Doubles

• Dummy

• Stub

• Spy

• Mock

• Fake

Page 6: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Dummy

private static final Locale DUMMY = Locale.CHINA; @Test public void testGetYear() { Calendar calendar = new GregorianCalendar(DUMMY); calendar.set(Calendar.YEAR, 2013); assertEquals(2013, calendar.get(Calendar.YEAR)); }

Page 7: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Stub

public class PriceSummarizer { private final List<Product> products; private final CurrencyConverter converter; public PriceSummarizer(List<Product> products, CurrencyConverter converter) { this.products = products; this.converter = converter; } public BigDecimal calcPrices(Currency currency) { BigDecimal sum = BigDecimal.ZERO; for (Product product : products) { BigDecimal price = converter.convert(product.getCurrency() currency, product.getPrice()); sum.add(price); } return sum; } }

Page 8: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Stub

@Test public void testCalcPrices() { CurrencyConverter converter = Mockito.mock(CurrencyConverter.class); Mockito.when(converter.convert( Mockito.any(Currency.class), Mockito.any(Currency.class), Mockito.any(BigDecimal.class))) .thenReturn(BigDecimal.ONE); List<Product> products = createProducts(1); PriceSummarizer sut = new PriceSummarizer(products, converter); assertEquals(BigDecimal.ONE, sut.calcPrices(DUMMY)); }

Page 9: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Spy

public class GarbageCollector { private Finalizer finalizer; private Objects objects; public void gc() { stopTheWorld(); for (Object obj : objects) { if (thereIsNoReferenceFor(obj)) { finalizer.add(obj); } } compressHeap(); } ... }

Page 10: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Spy

@Test public void testGc_NonReferencedObject() { Finalizer finalizer = Mockito.mock(Finalizer.class); ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class); GarbageCollector gc = createGC(); gc.setFinalizer(finalizer); gc.gc(); Mockito.verify(finalizer).add(captor.capture()); assertEquals(obj, captor.getValue()); }

Page 11: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Mockito Spy (Partial Mock)

List list = new LinkedList(); List spy = Mockito.spy(list); //optionally, you can stub out some methods: when(spy.size()).thenReturn(100); //using the spy calls *real* methods spy.add("one"); spy.add("two"); //prints "one" - the first element of a list System.out.println(spy.get(0)); //size() method was stubbed - 100 is printed System.out.println(spy.size()); //optionally, you can verify verify(spy).add("one"); verify(spy).add("two");

Source: http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#13

Page 12: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Mock

@Test public void testPut() { Map<Class<?>, Object> map = EasyMock.createMock(Map.class); ClassToInstanceMap sut = new ClassToInstanceMap(map); EasyMock.expect(map.put(Integer.class, 5)).andReturn(null); EasyMock.replay(map); sut.put(5); EasyMock.verify(map); }

Page 13: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Mock

@Test public void testPut() { Map<Class<?>, Object> map = Mockito.mock(Map.class); ClassToInstanceMap sut = new ClassToInstanceMap(map); sut.put(5); Mockito.verify(map).put(Integer.class, 5); }

Page 14: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Fake

• HSQLDB

• DeterministicScheduler (see it later)

Page 15: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

static Formatter formatter; @BeforeClass public static void setUp() { formatter = new Formatter(); } @Test public void testOnWindows() { Printer printer = new Printer(formatter); Assert.assertEquals("aa\r\nbb", printer.printToString("aa", "bb")); formatter.setEndline("\n"); // <- WTF? } @Test public void testOnUnix() { Printer printer = new Printer(formatter); Assert.assertEquals("aa\nbb", printer.printToString("aa", "bb")); }

Avoid: order-dependent tests

Page 16: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Formatter formatter; @Before public void setUp() { formatter = new Formatter(); } @Test public void testOnWindows() { formatter.setEndline("\r\n"); Printer printer = new Printer(formatter); Assert.assertEquals("aa\r\nbb", printer.printToString("aa", "bb")); } @Test public void testOnUnix() { formatter.setEndline("\n"); Printer printer = new Printer(formatter); Assert.assertEquals("aa\nbb", printer.printToString("aa", "bb")); }

…better:

Page 17: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test public void testOnWindows() { Printer printer = newPrinterWithEndline("\r\n"); Assert.assertEquals("aa\r\nbb", printer.printToString("aa", "bb")); } @Test public void testOnUnix() { Printer printer = newPrinterWithEndline("\n"); Assert.assertEquals("aa\nbb", printer.printToString("aa", "bb")); } private Printer newPrinterWithEndline(String endline) { Formatter formatter = new Formatter(); formatter.setEndline(endline); return new Printer(formatter); }

…or:

Page 18: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test public void testPay_ShouldReduceMoney() { Customer customer = new Customer(10); // 10 fabatka customer.pay(4); Assert.assertEquals(6, customer.getMoneyAmount()); Assert.assertEquals(Mood.HAPPY, customer.getMood()); } @Test public void testPay_MakeBadMood() { Customer customer = new Customer(10); // 10 fabatka customer.pay(8); Assert.assertEquals(2, customer.getMoneyAmount()); Assert.assertEquals(Mood.SAD, customer.getMood()); }

Avoid: multiple logical assertions

Page 19: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test public void testPay_ShouldReduceMoney() { Customer customer = new Customer(10); // 10 fabatka customer.pay(4); Assert.assertEquals(6, customer.getMoneyAmount()); } @Test public void testPay_ALittle_ShouldCauseHappiness() { Customer customer = new Customer(10); // 10 fabatka customer.pay(2); Assert.assertEquals(Mood.HAPPY, customer.getMood()); } @Test public void testPay_ALot_ShouldCauseSadness() { Customer customer = new Customer(10); // 10 fabatka customer.pay(8); Assert.assertEquals(Mood.SAD, customer.getMood()); }

…instead:

Page 20: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

private final Splitter splitter = Splitter.on(','); @Test public void testSplit1() { assertEquals(list("a", "b", "c"), splitter.omitEmptyStrings().split("a,,b,c")); } @Test public void testSplit2() { assertEquals(list(), splitter.omitEmptyStrings().split(",,")); } @Test public void testSplit3() { assertEquals(list("a", "x", ""), splitter.trimResults().split(" a, x ,")); }

Do NOT use numbered names

Page 21: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

private final Splitter splitter = Splitter.on(','); @Test public void testSplit_OmitEmptyStrings() { assertEquals(list("a", "b", "c"), splitter.omitEmptyStrings().split("a,,b,c")); assertEquals(list(), splitter.omitEmptyStrings().split(",,")); } @Test public void testSplit_TrimResults() { assertEquals(list("a", "x", ""), splitter.trimResults().split(" a, x ,")); }

…rename them:

Page 22: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

public class CustomerTest { @Test public void testCustomer() { Customer customer = new Customer(10); // 10 fabatka customer.pay(4); Assert.assertEquals(6, customer.getMoneyAmount()); } }

Do NOT give misleading names I.

Page 23: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

public class CustomerTest { @Test public void testCustomerWonTheLottery() { Customer customer = new Customer(10); // 10 fabatka customer.pay(4); Assert.assertEquals(6, customer.getMoneyAmount()); } }

Do NOT give misleading names II.

Page 24: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

public class CustomerTest { @Test public void testPay_ShouldReduceMoney() { Customer customer = new Customer(10); // 10 fabatka customer.pay(4); Assert.assertEquals(6, customer.getMoneyAmount()); } }

…rename them:

Page 25: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

public class CustomerTest { @Test public void testCustomer_WithOneParameter() { Customer customer = new Customer(10); // 10 fabatka Assert.assertEquals(10, customer.getMoneyAmount()); } }

Do NOT give misleading names III.

Page 26: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

public class CustomerTest { @Test public void testConstructor_WithMoney() { Customer customer = new Customer(10); // 10 fabatka Assert.assertEquals(10, customer.getMoneyAmount()); } }

…rename them:

Page 27: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Avoid: too long or too short names

0

50

100

150

200

250

300

350

5 15 25 35 45 55 65 75 85 95 105 115 125

Length of Java test method names (without 'test' prefix)

Frequency

Results collected from some of our projects

Page 28: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test(expected = FileNotFoundException.class) public void testReadFileShouldThrowFileNotFoundExceptionWhenFileIsNotFound() { }

Avoid: too long names

Page 29: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test(expected = FileNotFoundException.class) public void testReadFile_ShouldThrowFileNotFoundException_WhenFileIsNotFound() { }

…consider: separator in names

Page 30: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test(expected = FileNotFoundException.class) public void testReadFile_FileDoesNotExist() { }

…and omit redundant phases:

Page 31: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Assert.assertEquals(null, sut.getValue()); Assert.assertEquals(true, sut.isFinished()); Assert.assertEquals(false, sut.isInProgress());

Avoid: using assertEquals only

Page 32: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Assert.assertNull(sut.getValue()); Assert.assertTrue(sut.isFinished()); Assert.assertFalse(sut.isInProgress());

…instead:

Page 33: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Assert.assertTrue(sut.getValue() == null); Assert.assertTrue(sut.isFinished() == true); Assert.assertTrue(sut.isInProgress() == false);

Avoid: using assertTrue only

Page 34: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Assert.assertNull(sut.getValue()); Assert.assertTrue(sut.isFinished()); Assert.assertFalse(sut.isInProgress());

…instead:

Page 35: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

public class CustomerTest { }

Don’t create empty test classes

Page 36: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

public class CustomerTest { @Ignore("test the passed values for different types") @Test public void testConstructor_WithMoney() { // ... } }

…add some searchable helper:

Page 37: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

// FIXME write tests public class SplitterTest { // TODO write tests for splitter with ommitting empty strings // TODO write tests for splitter with trimming splitted values // TODO write tests for boundary cases (empty, non-null string) // TODO write negative tests for null reference }

…add some searchable helper:

Page 38: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Avoid: to test private, final or static stuffs; to mock constructor

Page 39: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

…or you can use PowerMock

Page 40: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Time-dependent Tests

• System.currentTimeMillis()

• PowerMockito?

– Nope!

Page 41: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Time-dependent Code

public class Stopwatch { private long start; public Stopwatch() { start(); } public long start() { return start = System.currentTimeMillis(); } public long elapsedMillis() { return System.currentTimeMillis() - start; } }

Page 42: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test public void testElapsedMillis() throws InterruptedException { Stopwatch sut = new Stopwatch(); sut.start(); Thread.sleep(1000); assertEquals(1000, sut.elapsedMillis(), 100); }

Don’t do this! Never ever.

Page 43: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

import org.joda.time.DateTimeUtils; public class Stopwatch { private long start; public Stopwatch() { start(); } public long start() { return start = DateTimeUtils.currentTimeMillis(); } public long elapsedMillis() { return DateTimeUtils.currentTimeMillis() - start; } }

…instead:

Page 44: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test public void testElapsedMillis() throws InterruptedException { MillisProvider millisProvider = Mockito.mock(MillisProvider.class); Mockito.when(millisProvider.getMillis()).thenReturn(0L, 50L, 1050L); DateTimeUtils.setCurrentMillisProvider(millisProvider); Stopwatch sut = new Stopwatch(); sut.start(); assertEquals(1000, sut.elapsedMillis()); }

…and:

Page 45: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Testing Concurrent Code

• Fast?

• Repeatable?

Page 46: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Concurrent Code

public class SimpleScheduler { private ScheduledExecutorService executor = new ScheduledThreadPoolExecutor( Runtime.getRuntime().availableProcessors()); private final Runnable runnable; private final int tickIntervall; public SimpleScheduler(int tickIntervall, Runnable runnable) { this.runnable = runnable; this.tickIntervall = tickIntervall; } public void start() { executor.scheduleAtFixedRate(runnable, 0, tickIntervall, TimeUnit.MILLISECONDS); } }

Page 47: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

private ConcurrentLinkedQueue<Long> timeStamps = new ConcurrentLinkedQueue<Long>(); @Test public void testSchedulingTasks_() throws Exception { Runnable runnable = createRunnable(); SimpleScheduler scheduler = new SimpleScheduler(1000, runnable); scheduler.start(); Thread.sleep(2500); assertEquals(3, timeStamps.size()); } private Runnable createRunnable() { return new Runnable() { @Override public void run() { timeStamps.add(System.currentTimeMillis()); } }; }

Don’t do this

Page 48: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

@Test public void testSchedulingTasks() { Runnable runnable = createRunnable(); SimpleScheduler scheduler = new SimpleScheduler(1000, runnable); final DeterministicScheduler executor = new DeterministicScheduler(); scheduler.setExecutor(executor); scheduler.start(); executor.tick(2500, TimeUnit.MILLISECONDS); assertEquals(3, timeStamps.size()); }

…instead:

Page 49: Unit Testing & Mocks - Komputeralgebra Tanszékcompalg.inf.elte.hu/~attila/materials/unit-testing-best-and-bad... · CurrencyConverter converter = Mockito.mock(CurrencyConverter.class);

Concurrent Code Tests

• jMock library

– DeterministicScheduler

– DeterministicExecutor

http://jmock.org/