Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Matt Logan

June 30, 2016
Tweet

More Decks by Matt Logan

Other Decks in Programming

Transcript

  1. Matt Logan
    Effective Unit Testing
    with Test Doubles

    View full-size slide

  2. • Terminology
    • Test philosophy
    • Types of test doubles
    • Best practices
    • Full example
    • Extra considerations
    Outline

    View full-size slide

  3. • System under test
    • Depended-on component
    • Indirect output
    • Indirect input
    • Test double
    Terminology

    View full-size slide

  4. –Martin Fowler
    “Test Double is a generic term for any case
    where you replace a production object for
    testing purposes.”

    View full-size slide

  5. Why?
    • Enable testing
    • Ease testing
    • Improve testing
    • NOT just to “isolate” system under test

    View full-size slide

  6. Test philosophy
    • Tests should:
    • Verify correct behavior (or state)
    • Allow for safe refactoring
    • Drive good design
    • Serve as documentation
    • Be easy to write!

    View full-size slide

  7. • Dummy
    • Fake
    • Stub
    • Mock
    • Spy
    Types of test doubles

    View full-size slide

  8. Dummies
    • Never used
    • Satisfy method parameters
    • Easy to implement
    • Primitives or objects

    View full-size slide

  9. Dummies
    class DummyClient
    implements LocationClient {


    @Override
    public void startUpdates(Callback c) {

    throw new RuntimeException("Dummy!");

    }

    }

    View full-size slide

  10. Dummies
    • More examples
    • -1
    • “String that will not be used”
    • null

    View full-size slide

  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

    View full-size slide

  12. Fakes
    class FakeDb implements LocationDatabase {

    Map 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);

    }

    }

    View full-size slide

  13. Stubs
    • Control indirect inputs to system under test
    • Provide “canned responses”
    • Example: stubbed HTTP responses

    View full-size slide

  14. Stubs
    class LocationEnabledDevice

    implements DeviceInfo {


    @Override

    public boolean isLocationEnabled() {

    return true;

    }

    }

    View full-size slide

  15. Stubs
    // Create the “mock”
    DeviceInfo deviceInfo =
    mock(DeviceInfo.class);
    // “Stub” its method(s)

    when(deviceInfo.isLocationEnabled())
    .thenReturn(false);

    View full-size slide

  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

    View full-size slide

  17. Mocks
    // Create system under test with mock
    object
    expect(callbacks
    .onLocationUnavailable());
    // Exercise system under test
    // Will throw if method not called

    View full-size slide

  18. Spies
    • Very similar to mocks
    • Record interactions, “verify” later
    • Better at showing intent — can hide irrelevant calls
    • Debugging can be harder

    View full-size slide

  19. Spies
    // Create system under test with spy
    // Exercise system under test
    verify(callbacks)
    .onLocationUnavailable();
    // Will throw if method not called

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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.

    View full-size slide

  23. Location Tracker
    github.com/mattlogan/locationtracker

    View full-size slide

  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;

    }

    });

    }

    View full-size slide

  25. class DummyClient
    implements LocationClient {


    @Override
    public void startUpdates(Callback c) {

    throw new RuntimeException("Dummy!");

    }

    }

    View full-size slide

  26. class NoLocationDevice

    implements DeviceInfo {


    @Override

    public boolean isLocationEnabled() {

    return false;

    }

    }

    View full-size slide

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

    }

    View full-size slide

  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;

    }

    });

    }

    View full-size slide

  29. class FakeClient implements LocationClient {

    List 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() {}

    }

    View full-size slide

  30. class LocationEnabledDevice

    implements DeviceInfo {


    @Override

    public boolean isLocationEnabled() {

    return true;

    }

    }

    View full-size slide

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

    }

    View full-size slide

  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

    View full-size slide

  33. Contact + resources
    • Contact & follow me (if you want to)
    [email protected]
    • @_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

    View full-size slide