0%

用Fake写出更好的测试用例

写了多年博客之后,你决定自己写一套博客平台的API。你开始写了,但你意识到:如果没有测试对于远程博客服务器的调用,怎么才能说明你的代码没问题呢?

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

Fake来拯救你!Fake是API的轻量级实现,但其表现与真实实现一致,但不适合运行在生产环境中。在博客服务的例子中,你所关注的是获取和删除posts的能力。然而,一个真实的博客服务需要一个数据库以及支持多个服务节点,如果只是为了测试你不需要这些(数据库和多服务节点),你所需要的是任何一个实现了博客服务API的实现。你可以通过一个简单的内存实现,达到这个目的:
1
2
3
4
5
6
7
8
9
10
11
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; }
}

现在你的测试用例可以用上述Fake把真实的博客服务替换掉,对于待测代码来说看不出来区别。
当你无法在测试中使用真实实现时,Fake是非常有用的,例如真实实现太慢(例如,启动TA需要几分钟)或者是TA不具备确定性(例如,TA需要与远程服务器交付,而该远程服务器可能在测试执行的时候不可用)。
你不应该需要自己写Fake,因为:
每一个Fake应该被负责真实实现的团队创建并维护
如果你使用的API没有提供Fake,自己写一个Fake并不困难:写一个wrapper,把你在测试中没法用的代码包起来,然后对这个wrapper创建一个fake。记住,要在尽可能低的层级创建Fake(例如,如果你无法在测试中使用数据库,fake一个数据库,而不是fake所有需要访问数据库的类),使用这样的方式需要维护的Fake会少很多,且你的测试用例可以执行到系统中更多的真实代码。
Fake应该有自己的测试用例,用以保证其余真实实现一致(例如,真实实现在给定某个输入时抛出了异常,那么该fake也应该在给定该输入时抛出异常)。一个方式是对着API的公有接口写测试用例,且将这份测试用例同时用于验证真实实现与Fake。
如果测试用例中使用了fake使得你无法确信自己的代码在生产环境中会正常工作,你可以写一组少量的集成测试来保证你的代码可以与真实实现正确结合。

翻译自

https://testing.googleblog.com/2013/06/testing-on-toilet-fake-your-way-to.html