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