Faking Hell

Preview:

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).

Citation preview

© Asprotunity Ltd

Giovanni Asproni email: gasproni@asprotunity.com twitter: @gasproni linkedin: http://www.linkedin.com/in/gasproni

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

1

© 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

© Asprotunity Ltd

Test Doubles

• Dummies

• Stubs

• Spies

• Fakes

• Mocks

3

© 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

© Asprotunity Ltd

Stubs

• Stubs provide canned answers to calls made during the test

5

© 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

© 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;

}

…..}

© 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

© 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);}

}

…..}

© Asprotunity Ltd

Mocks

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

10

© 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

© 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());

}

© 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

© 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

}

…..}

© 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

© 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());

}

© Asprotunity Ltd 17

Outside-In And Middle-Out

Database

Client

GUI Domain layer ServerWalking skeleton

© Asprotunity Ltd

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

18

State Or Behaviour?

© 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

© 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

© 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

© 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;

}

…..}

© 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

© Asprotunity Ltd

Plain Old Confusion

• Testing the mocks

• Too many dependencies or interactions

• Partial mocking

24

© Asprotunity Ltd 25

…I just want to increase code coverage…

© 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

© Asprotunity Ltd 27

To Mock Or Not To Mock?

© 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

© 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

© 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

© Asprotunity Ltd 31

Questions?