Faking Hell

32
© Asprotunity Ltd Giovanni Asproni email: [email protected] twitter: @gasproni linkedin: http://www.linkedin.com/in/gasproni Faking Hell Use, abuse and misuse of fakes, mocks and other test doubles 1

description

Slides of the "In The Brains" talk given at SkillsMatter on the 28th of October 2014. The use of test doubles in testing at various levels has become commonplace, however, correct usage is far less common. In this talk Giovanni Asproni shows the most common and serious mistakes he's seen in practice and he'll give some hints on how to avoid them (or fix them in existing code).

Transcript of Faking Hell

Page 1: Faking Hell

© Asprotunity Ltd

Giovanni Asproni email: [email protected] twitter: @gasproni linkedin: http://www.linkedin.com/in/gasproni

Faking HellUse, abuse and misuse of fakes, mocks and other test doubles

1

Page 2: Faking Hell

© Asprotunity Ltd

Test Doubles

• Sometimes we need to test classes that interact with other objects which are difficult to control

• The real object has nondeterministic behaviour (e.g., random number generator)

• The real object is difficult to set up (e.g., requiring a certain file system, database, or network environment)

• The real object has behaviour that is hard to trigger (e.g., a network error)

• The real object is slow

• The real object has (or is) a user interface

• The test needs to ask the real object about how it was used (e.g., confirm that a callback function was actually called)

• The real object does not yet exist (e.g., interfacing with other teams or new hardware systems)

2

Page 3: Faking Hell

© Asprotunity Ltd

Test Doubles

• Dummies

• Stubs

• Spies

• Fakes

• Mocks

3

Page 4: Faking Hell

© Asprotunity Ltd

Dummies

• Dummy objects are passed around but never actually used

• E.g., a mandatory argument in a constructor never used during a specific test

4

Page 5: Faking Hell

© Asprotunity Ltd

Stubs

• Stubs provide canned answers to calls made during the test

5

Page 6: Faking Hell

© Asprotunity Ltd

Spies

• Spies are stubs that also record some information based on how they were called. (e.g., the number of times a method has been called)

6

Page 7: Faking Hell

© Asprotunity Ltd 7

public class OrderStateTest { private static String TALISKER = "Talisker"; private WarehouseStub warehouse = new WarehouseStub();

@Test public void orderIsFilledIfEnoughInWarehouse() {

Order order = new Order(TALISKER, 50); order.fill(warehouse); assertTrue(order.isFilled())

assertTrue(warehouse.removeCalled); }

@Test public void orderDoesNotRemoveIfNotEnough() {

Order order = new Order(TALISKER, 51); order.fill(warehouse); assertFalse(order.isFilled()); assertFalse(warehouse.removeCalled);

}}

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

public class WarehouseStub implements Warehouse {

public boolean removeCalled = false;

public void hasInventory(String brand, int amount) { return “Talisker”.equals(brand) && amount <= 50

}

public void remove(String brand, int amount) {removeCalled = true;

}

…..}

Page 8: Faking Hell

© Asprotunity Ltd

Fakes

• Fake objects actually have working implementations, but take some shortcuts which make them not suitable for production (e.g., an in memory database)

8

Page 9: Faking Hell

© Asprotunity Ltd 9

public class OrderStateTest { private static String TALISKER = "Talisker"; private Warehouse warehouse = new WarehouseFake();

@Test public void orderIsFilledIfEnoughInWarehouse() {

Order order = new Order(TALISKER, 50); order.fill(warehouse); assertTrue(order.isFilled())

}

@Test public void orderDoesNotRemoveIfNotEnough() {

Order order = new Order(TALISKER, 51); order.fill(warehouse); assertFalse(order.isFilled());

}}

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

public class WarehouseFake implements Warehouse {

private HashMap<String, Integer> inventoryByBrand;

public WarehouseFake() { inventoryByBrand =

new HashMap<>(){{put(“Talisker”, 50);}} }

public void hasInventory(String brand, int required) { available = inventoryByBrand.get(brand)

return available != null && required <= available; }

public void remove(String brand, int amount) {available = inventoryByBrand.get(brand)if (available == null || amount > available) {

// Manage the error…}else {

inventoryByBrand.put(brand, available - amount);}

}

…..}

Page 10: Faking Hell

© Asprotunity Ltd

Mocks

• Mocks are objects pre-programmed with expectations which form a specification of the calls they are expected to receive

10

Page 11: Faking Hell

© Asprotunity Ltd

History of MockObjects

• Invented at Connextra and XtC in 1999

• The initial purpose was to get rid of getters in testing

• Component composition

• Tell don’t ask

• Testing behaviours

• More at http://www.mockobjects.com/2009/09/brief-history-of-mock-objects.html

11

Page 12: Faking Hell

© Asprotunity Ltd 12

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

@Test public void fillingDoesNotRemoveIfNotEnoughInStock() {

Order order = new Order(TALISKER, 51); Mockery context = new JUnit4Mockery();

Warehouse mockWarehouse = context.mock(Warehouse.class); //setup - expectations context.checking(new Expectations() {{ oneOf(mockWarehouse).hasInventory(with(any(String.class)), with(any(int.class))); will(returnValue(false));

never(mockWarehouse).remove(with(any(String.class)), with(any(int.class))); }}); order.fill(mockWarehouse)); context.assertIsSatisfied(); assertFalse(order.isFilled());

}

Page 13: Faking Hell

© Asprotunity Ltd

Mocks Aren’t Stubs

• Mocks are meant for testing behaviour

• Stubs and all other doubles are generally used for testing state

• Classic TDD vs Mockist TDD (Classic school vs London School of TDD)

13

http://martinfowler.com/articles/mocksArentStubs.html

Page 14: Faking Hell

© Asprotunity Ltd 14

public class OrderStateTest { private static String TALISKER = "Talisker"; private Warehouse warehouse = new WarehouseStub();

@Test public void orderIsFilledIfEnoughInWarehouse() {

Order order = new Order(TALISKER, 50); order.fill(warehouse); assertTrue(order.isFilled())

}

@Test public void orderDoesNotRemoveIfNotEnough() {

Order order = new Order(TALISKER, 51); order.fill(warehouse); assertFalse(order.isFilled());

}}

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

public class WarehouseStub implements Warehouse { public void hasInventory(String brand, int amount) {

return “Talisker”.equals(brand) && amount <= 50 }

public void remove(String brand, int amount) {// Intentionally blank

}

…..}

Page 15: Faking Hell

© Asprotunity Ltd 15

public class OrderInteractionTest { private static String TALISKER = "Talisker";

@Test public void fillingRemovesInventoryIfInStock() {

//setup - data Order order = new Order(TALISKER, 50); Mockery context = new JUnit4Mockery();

Warehouse mockWarehouse = context.mock(Warehouse.class); //setup - expectations context.checking(new Expectations() {{ oneOf(mockWarehouse).hasInventory(TALISKER, 50); will(returnValue(true));

oneOf(mockWarehouse).remove(TALISKER, 50); }}); order.fill(mockWarehouse); context.assertIsSatisfied(); assertTrue(order.isFilled());

}

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

Page 16: Faking Hell

© Asprotunity Ltd 16

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

@Test public void fillingDoesNotRemoveIfNotEnoughInStock() {

Order order = new Order(TALISKER, 51); Mockery context = new JUnit4Mockery();

Warehouse mockWarehouse = context.mock(Warehouse.class); //setup - expectations context.checking(new Expectations() {{ oneOf(mockWarehouse).hasInventory(with(any(String.class)), with(any(int.class))); will(returnValue(false));

never(mockWarehouse).remove(with(any(String.class)), with(any(int.class))); }}); order.fill(mockWarehouse)); context.assertIsSatisfied(); assertFalse(order.isFilled());

}

Page 17: Faking Hell

© Asprotunity Ltd 17

Outside-In And Middle-Out

Database

Client

GUI Domain layer ServerWalking skeleton

Page 18: Faking Hell

© Asprotunity Ltd

Sometimes we need to test state, sometimes we need to test behaviour, and sometimes we need both

18

State Or Behaviour?

Page 19: Faking Hell

© Asprotunity Ltd

Problems Frequently Due To

• The design of the application is unfit for purpose

• The test has no clear purpose

• Plain old confusion

• …I just want to increase code coverage…

19

Page 20: Faking Hell

© Asprotunity Ltd

The Design Of The Application Is Unfit For Purpose

• Too many dependencies to mock

• Too many interactions with a single mock object

• Mocks nested into each other

20

Page 21: Faking Hell

© Asprotunity Ltd

The Test Has No Clear Purpose

• Testing state or behaviour?

• Fakes or stubs used to test behaviour

• Use of mock objects used to test state

21

Page 22: Faking Hell

© Asprotunity Ltd 22

public class OrderStateTest { private static String TALISKER = "Talisker"; private WarehouseStub warehouse = new WarehouseStub();

@Test public void orderIsFilledIfEnoughInWarehouse() {

Order order = new Order(TALISKER, 50); order.fill(warehouse); assertTrue(order.isFilled())

assertTrue(warehouse.removeCalled); }

@Test public void orderDoesNotRemoveIfNotEnough() {

Order order = new Order(TALISKER, 51); order.fill(warehouse); assertFalse(order.isFilled()); assertFalse(warehouse.removeCalled);

}}

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

public class WarehouseStub implements Warehouse {

public boolean removeCalled = false;

public void hasInventory(String brand, int amount) { return “Talisker”.equals(brand) && amount <= 50

}

public void remove(String brand, int amount) {removeCalled = true;

}

…..}

Page 23: Faking Hell

© Asprotunity Ltd 23

public class OrderInteractionTest {

private static String TALISKER = "Talisker";

@Test public void fillingRemovesInventoryIfInStock() {

//setup - data Order order = new Order(TALISKER, 50); Mockery context = new JUnit4Mockery();

Warehouse mockWarehouse = context.mock(Warehouse.class); //setup - expectations context.checking(new Expectations() {{ oneOf(mockWarehouse).hasInventory(TALISKER, 50); will(returnValue(true));

oneOf(mockWarehouse).remove(TALISKER, 50); }}); order.fill(mockWarehouse); // We don’t check that context.assertIsSatisfied(); assertTrue(order.isFilled());

}

Adapted from: http://martinfowler.com/articles/mocksArentStubs.html

Page 24: Faking Hell

© Asprotunity Ltd

Plain Old Confusion

• Testing the mocks

• Too many dependencies or interactions

• Partial mocking

24

Page 25: Faking Hell

© Asprotunity Ltd 25

…I just want to increase code coverage…

Page 26: Faking Hell

© Asprotunity Ltd

The Solution To Those Problems?

• Learn to listen to what the tests are telling you

• Avoid dubious practices as much as possible

• Partial mocks

• Mocking concrete classes

• Monkey patching

26

Page 27: Faking Hell

© Asprotunity Ltd 27

To Mock Or Not To Mock?

Page 28: Faking Hell

© Asprotunity Ltd

Test Doubles: Some Advantages

• Clarify which interactions are actually important for the test

• Help in design protocols

• Make testing of some behaviours possible

• Allows for creation of faster tests

• Can alleviate dependencies on other teams

28

Page 29: Faking Hell

© Asprotunity Ltd

Test Doubles: Some (Alleged) Disadvantages

• Duplication of effort

• Changes of behaviour of the real object need to be reflected in the test doubles

• Tests coupled to implementation (in case of mocks)

29

Page 30: Faking Hell

© Asprotunity Ltd

I’m Biased

• I find test doubles useful

• The important thing is to know which ones to use when

• …and when it’s best to use the real object

• The real object is “simple” to build and to interact with

• Full system tests

• Etc.

30

Page 31: Faking Hell

© Asprotunity Ltd 31

Questions?