© Asprotunity Ltd
Giovanni Asproni
email: gasproni@asprotunity.com
twitter: @gasproni
linkedin: http://www.linkedin.com/in/gasproni
Faking Hell
Use, 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 Server
Walking 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?
© Asprotunity Ltd 32

Faking Hell

  • 1.
    © Asprotunity Ltd GiovanniAsproni email: gasproni@asprotunity.com twitter: @gasproni linkedin: http://www.linkedin.com/in/gasproni Faking Hell Use, abuse and misuse of fakes, mocks and other test doubles 1
  • 2.
    © Asprotunity Ltd TestDoubles • 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
  • 3.
    © Asprotunity Ltd TestDoubles • Dummies • Stubs • Spies • Fakes • Mocks 3
  • 4.
    © 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
  • 5.
    © Asprotunity Ltd Stubs •Stubs provide canned answers to calls made during the test 5
  • 6.
    © 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
  • 7.
    © Asprotunity Ltd7 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; } ….. }
  • 8.
    © 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
  • 9.
    © Asprotunity Ltd9 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); } } ….. }
  • 10.
    © Asprotunity Ltd Mocks •Mocks are objects pre-programmed with expectations which form a specification of the calls they are expected to receive 10
  • 11.
    © Asprotunity Ltd Historyof 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
  • 12.
    © Asprotunity Ltd12 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()); }
  • 13.
    © Asprotunity Ltd MocksAren’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
  • 14.
    © Asprotunity Ltd14 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 } ….. }
  • 15.
    © Asprotunity Ltd15 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
  • 16.
    © Asprotunity Ltd16 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()); }
  • 17.
    © Asprotunity Ltd17 Outside-In And Middle-Out Database Client GUI Domain layer Server Walking skeleton
  • 18.
    © Asprotunity Ltd Sometimeswe need to test state, sometimes we need to test behaviour, and sometimes we need both 18 State Or Behaviour?
  • 19.
    © Asprotunity Ltd ProblemsFrequently 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
  • 20.
    © Asprotunity Ltd TheDesign 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
  • 21.
    © Asprotunity Ltd TheTest Has No Clear Purpose • Testing state or behaviour? • Fakes or stubs used to test behaviour • Use of mock objects used to test state 21
  • 22.
    © Asprotunity Ltd22 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; } ….. }
  • 23.
    © Asprotunity Ltd23 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
  • 24.
    © Asprotunity Ltd PlainOld Confusion • Testing the mocks • Too many dependencies or interactions • Partial mocking 24
  • 25.
    © Asprotunity Ltd25 …I just want to increase code coverage…
  • 26.
    © Asprotunity Ltd TheSolution 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
  • 27.
    © Asprotunity Ltd27 To Mock Or Not To Mock?
  • 28.
    © Asprotunity Ltd TestDoubles: 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
  • 29.
    © Asprotunity Ltd TestDoubles: 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
  • 30.
    © Asprotunity Ltd I’mBiased • 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
  • 31.
    © Asprotunity Ltd31 Questions?
  • 32.