Effective Unit Testing with Test Doubles (GDG Boulder, June 2016)

Effective Unit Testing with Test Doubles (GDG Boulder, June 2016)

A test double is any object that takes the place of a real object in a test, just like a stunt double takes the place of an actor in a movie. There are several types of test doubles, including stubs, fakes, dummies, mocks, and spies. In this talk, we'll explore the common uses of these types of test doubles, their advantages and pitfalls, and best practices for using test doubles to write effective and reliable unit tests.

http://www.meetup.com/Google-Developer-Group-Boulder/events/231374141/

https://www.youtube.com/watch?v=_pCwcdNtxog

8d9b8aa31d299a7bc2211f4a4a517215?s=128

Matt Logan

June 30, 2016
Tweet

Transcript

  1. 2.

    • Terminology • Test philosophy • Types of test doubles

    • Best practices • Full example • Extra considerations Outline
  2. 3.

    • System under test • Depended-on component • Indirect output

    • Indirect input • Test double Terminology
  3. 4.

    –Martin Fowler “Test Double is a generic term for any

    case where you replace a production object for testing purposes.”
  4. 5.

    Why? • Enable testing • Ease testing • Improve testing

    • NOT just to “isolate” system under test
  5. 6.

    Test philosophy • Tests should: • Verify correct behavior (or

    state) • Allow for safe refactoring • Drive good design • Serve as documentation • Be easy to write!
  6. 8.

    Dummies • Never used • Satisfy method parameters • Easy

    to implement • Primitives or objects
  7. 9.

    Dummies class DummyClient implements LocationClient {
 
 @Override public void

    startUpdates(Callback c) {
 throw new RuntimeException("Dummy!");
 }
 }
  8. 11.

    Fakes • Replace a “depended-on component” with a lighter weight

    implementation • Takes shortcuts, but still functional • Can be used for input or output • Usually hand-coded • Example: in-memory database • Another example: fake web service
  9. 12.

    Fakes class FakeDb implements LocationDatabase {
 Map<String, Location> map =

    new HashMap<>();
 
 @Override
 public void save(String key, Location loc) {
 map.put(key, loc);
 }
 
 @Override
 public Location get(String key) {
 return map.get(key);
 }
 }
  10. 13.

    Stubs • Control indirect inputs to system under test •

    Provide “canned responses” • Example: stubbed HTTP responses
  11. 15.

    Stubs // Create the “mock” DeviceInfo deviceInfo = mock(DeviceInfo.class); //

    “Stub” its method(s)
 when(deviceInfo.isLocationEnabled()) .thenReturn(false);
  12. 16.

    Mocks • Mocks “expect” • Good for verifying exact behavior

    • Good for strict TDD • Can lead to over-specification • Also a generic (confusing) term for any test double • Also a verb for creating a test double
  13. 17.

    Mocks // Create system under test with mock object expect(callbacks

    .onLocationUnavailable()); // Exercise system under test // Will throw if method not called
  14. 18.

    Spies • Very similar to mocks • Record interactions, “verify”

    later • Better at showing intent — can hide irrelevant calls • Debugging can be harder
  15. 19.

    Spies // Create system under test with spy // Exercise

    system under test verify(callbacks) .onLocationUnavailable(); // Will throw if method not called
  16. 20.

    Best practices • Don’t use doubles for values! • Examples:

    Location, Date, Employee • Just create them • Don’t need to verify that accessors are called • If object creation is complicated, try builders
  17. 21.

    Best practices • Don’t use doubles for classes — just

    interfaces! • Interfaces show relationships between objects • Extract “what you need” into an interface • Need interfaces to create hand-coded doubles
  18. 22.

    Best practices • Don’t test implementation details! • Ask question:

    “Is this is a required behavior?” • Leads to brittle, unreliable tests • Refactoring implementation without changing API should NEVER break tests.
  19. 24.

    public void trackLocation() {
 if (!device.isLocationEnabled()) {
 callbacks.onLocationUnavailable();
 return;
 }


    
 client.startUpdates(new Callback() {
 @Override
 public void onUpdate(Location curLoc) {
 if (!curLoc.equals(lastLoc)) {
 callbacks.onChange(curLoc);
 }
 lastLoc = curLoc;
 }
 });
 }
  20. 27.

    @Test
 public void locationUnavailableCalled() {
 DeviceInfo device = // Stub

    new NoLocationDevice();
 LocationClient client = // Dummy new DummyClient();
 Callbacks callbacks = // Spy mock(Callbacks.class);
 LocationChangeTracker tracker =
 new LocationChangeTracker(device,
 client,
 callbacks);
 tracker.trackLocation();
 verify(callbacks) .onLocationUnavailable();
 }
  21. 28.

    public void trackLocation() {
 if (!device.isLocationEnabled()) {
 callbacks.onLocationUnavailable();
 return;
 }


    
 client.startUpdates(new Callback() {
 @Override
 public void onUpdate(Location curLoc) {
 if (!curLoc.equals(lastLoc)) {
 callbacks.onChange(curLoc);
 }
 lastLoc = curLoc;
 }
 });
 }
  22. 29.

    class FakeClient implements LocationClient {
 List<Location> locs = new ArrayList<>();


    
 FakeClient(Location... locs) {
 Collections.addAll(this.locs, locs);
 }
 
 @Override
 public void startUpdates(Callback c) {
 for (Location loc : locs) {
 c.onUpdate(loc);
 }
 }
 
 @Override
 public void stopUpdates() {}
 }
  23. 31.

    @Test
 public void onChangeCalled() {
 DeviceInfo device = // Stub


    new LocationEnabledDevice();
 LocationClient client = // Fake
 new FakeClient(new Location(1, 0),
 new Location(2, 2),
 new Location(2, 2));
 Callbacks callbacks = // Spy
 mock(Callbacks.class);
 LocationChangeTracker tracker =
 new LocationChangeTracker(device,
 client,
 callbacks);
 tracker.trackLocation();
 verify(callbacks, times(2))
 .onChange(isA(Location.class));
 }
  24. 32.

    Extra considerations • Testing behavior vs. testing state • Test

    doubles aren’t just for “unit” tests • Dependency injection helps • Fakes can become production objects • It’s okay to write tests for your test doubles
  25. 33.

    Contact + resources • Contact & follow me (if you

    want to) • matt.b.logan@gmail.com • @_mattlogan • mattlogan.me • More resources • github.com/mattlogan/locationtracker • Mocks Aren’t Stubs - Martin Fowler • Mocks Aren’t Stubs, Dummies, Fakes or Spies - Dave Marshall • xUnit Test Patterns - Gerard Meszaros