Download pdf - Faking Hell

Transcript
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?