Friday, June 28, 2013

Testing on the Toilet: Fake Your Way to Better Tests

By Jonathan Rockway and Andrew Trenk

This article was adapted from a Google Testing on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.

After many years of blogging, you decide to try out your blog platform's API. You start playing around with it, but then you realize: how can you tell that your code works without having your test talk to a remote blog server?

public void deletePostsWithTag(Tag tag) {
  for (Post post : blogService.getAllPosts()) {
    if (post.getTags().contains(tag)) { blogService.deletePost(post.getId()); }
  }
}

Fakes to the rescue! A fake is a lightweight implementation of an API that behaves like the real implementation, but isn't suitable for production. In the case of the blog service, all you care about is the ability to retrieve and delete posts. While a real blog service would need a database and support multiple frontend servers, you don’t need any of that to test your code, all you need is any implementation of the blog service API. You can achieve this with a simple in-memory implementation:

public class FakeBlogService implements BlogService {  
  private final Set<Post> posts = new HashSet<Post>(); // Store posts in memory
  public void addPost(Post post) { posts.add(post); }
  public void deletePost(int id) {
    for (Post post : posts) {
      if (post.getId() == id) { posts.remove(post); return; }
    }
    throw new PostNotFoundException("No post with ID " + id);
  }
  public Set<Post> getAllPosts() { return posts; }
}

Now your tests can swap out the real blog service with the fake and the code under test won't even know the difference.

Fakes are useful for when you can't use the real implementation in a test, such as if the real implementation is too slow (e.g. it takes several minutes to start up) or if it's non-deterministic (e.g. it talks to an external machine that may not be available when your test runs).

You shouldn't need to write your own fakes often since each fake should be created and maintained by the person or team that owns the real implementation. If you’re using an API that doesn't provide a fake, it’s often easy to create one yourself: write a wrapper around the part of the code that you can't use in your tests, and create a fake for that wrapper. Remember to create the fake at the lowest level possible (e.g. if you can't use a database in your tests, fake out the database instead of faking out all of your classes that talk to the database), that way you'll have fewer fakes to maintain, and your tests will be executing more real code for important parts of your system.

Fakes should have their own tests to ensure that they behave like the real implementation (e.g. if the real implementation throws an exception when given certain input, the fake implementation should also throw an exception when given the same input). One way to do this is to write tests against the API's public interface, and run those tests against both the real and fake implementations.

If you still don't fully trust that your code will work in production if all your tests use a fake, you can write a small number of integration tests to ensure that your code will work with the real implementation.

2 comments:

  1. See also http://martinfowler.com/articles/mocksArentStubs.html, which explains the difference between Dummies, Fakes, Stubs and Mocks.

    ReplyDelete
    Replies
    1. That article is a very good read, I think it should be required reading for anyone that writes unit tests.

      There will also be another Testing on the Toilet post coming up soon that discusses the different types of test doubles.

      Delete

The comments you read and contribute here belong only to the person who posted them. We reserve the right to remove off-topic comments.