通用技术 [译文] Testing on the Toilet: Know Your Test Doubles

陈恒捷 · 2015年07月01日 · 最后由 陈恒捷 回复于 2015年07月03日 · 2574 次阅读
本帖已被设为精华帖!

虽然见到过很多次 fake,stub,mock 这些单词,但一直对它们之间的区分比较混乱,前几天看到 @htmlbiji 在一篇文章中注明三者的中文译名后,觉得不能再混乱下去了,于是搜了一下,结果在 TotT 中找到这篇简短而又清晰的文章,在此分享给大家。

原文地址:http://googletesting.blogspot.com/2013/07/testing-on-toilet-know-your-test-doubles.html
原文作者:Andrew Trenk
译文作者:chenhengjie123

译者注:Testing on the Toilet (TotT) 是 google 的一个测试文章系列。文章内容都比较简短,在一页 A4 纸以内,可以很方便地被打印出来并贴到厕所的海报栏中。

test double (网上找到的中文译名为测试替代,但考虑到英文名称更为通用,所以后文均以英文原名表示) 是一个用于在测试中代替一个真实对象的对象,类似于电影中特技表演时使用的替身。这些概念在大部分情况下被认为是 "mocks(模拟)",但由于不同的 test double 用于不同的地方,因此学会区分它们很重要。 最常见的 test double 是 stubs(桩),mocks(模拟)和 fakes(伪)

stub 没有逻辑,仅返回你让它返回的内容。 当你需要一个对象返回一个特定的值以让你的代码在一个确定的环境下运行时,你需要使用 stub。虽然手写一个 stub 非常简单,但使用一个 moking framework(指用于创建模拟对象的框架)来创建 stub 是一个更方便的方式。

// 传入一个由 moking framework 创建的 stub 
AccessManager accessManager = new AccessManager(stubAuthenticationService);

// 在 authentication 服务返回 false 时,用户不应该拥有访问权限
when(stubAuthenticationService.isAuthenticated(USER_ID)).thenReturn(false);
assertFalse(accessManager.userHasAccess(USER_ID));

// 在 authentication 服务返回 true 时,用户应该拥有访问权限
when(stubAuthenticationService.isAuthenticated(USER_ID)).thenReturn(true);
assertTrue(accessManager.userHasAccess(USER_ID));

mock 拥有指定的调用方式,并且如果调用 mock 的方式有错误,测试应该 fail 。 Mock 用于测试对象之间的交互,并且在没有其他可以验证的可见的状态变化或返回值时相当有用。例如:如果你的代码从磁盘读取数据,并且你想确认它仅仅读取了一次磁盘,你可以使用 mock 来验证这个用来读取文件的方法是否只被调用了一次。

// 传入一个由 moking framework 创建的 mock
AccessManager accessManager = new AccessManager(mockAuthenticationService);
accessManager.userHasAccess(USER_ID);

// 如果 accessManager.userHasAccess(USER_ID) 没有调用 mockAuthenticationService.isAuthenticated(USER_ID) 或者调用它不止一次,这个测试应该会 fail
verify(mockAuthenticationService).isAuthenticated(USER_ID);

fake 并不使用 moking framework : 它是一个轻量级的、实现一个和真实模块具有一样行为的 API,但并不适用于生产环境,例如:一个存在内存里面的数据库。 Fake 可以在你不能在你的测试中使用真实模块的情况下使用,例如:真实模块太慢或者它需要通过网络通讯。你不需要经常编写你自己的 fake ,因为 fake 应该被编写真实模块的人/团队进行创建和维护。

// 创建 fake 相当快速和简单
AuthenticationService fakeAuthenticationService = new FakeAuthenticationService();
AccessManager accessManager = new AccessManager(fakeAuthenticationService);

// 由于用户未被添加到 authentication 服务中,用户不应该拥有访问权限
assertFalse(accessManager.userHasAccess(USER_ID));

// 当用户被添加到 authentication 服务中后,用户应该拥有访问权限
fakeAuthenticationService.addAuthenticatedUser(USER_ID);
assertTrue(accessManager.userHasAccess(USER_ID));

"test double" 的概念是被 Gerard Meszaros 在《xUnit Test Patterns》(xUnit 测试模式)书中被提出的。你可以在该书、或者该书的网站中找到更多关于 test double 的信息。你同时可以在这篇由 Martin Fowler 编写的文章中找到不同类型的 test double 的讨论。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 11 条回复 时间 点赞
1楼 已删除

头像还没换?

#2 楼 @woniu 换了,你刷新看看。

#3 楼 @chenhengjie123 我擦……这头像碉堡了

#3 楼 @chenhengjie123 还是原来那个头像

#5 楼 @wuyan 你清空一下缓存。

#6 楼 @chenhengjie123 居然不是换真人头像~~

这三个的实现基本是:

  • component 级别的 unittest case
  • component 级别的 unittest framework
  • front-only 的 integration test

对于第三种,如果前后还有 control 层分离的不够彻底的话,搞起来的话应该很痛苦吧。。

对于客户端应用自动回归测试来说,如果与之配套的服务器不稳定的话,经常因服务异常发生回归测试失败。
所以解决方法有二:
1、对服务器也做自动回归测试,用 cucumber-api,restclient 之类的工具,提高服务器的稳定度。
2、实现服务器的 stub,因为测试者知道客户端每个操作步骤的服务器期望响应,所以可以在所有客户端测试步骤前,动态实现对应 web api 的响应内容。

还有一种情况,一定要实现 stub,就是此功能运行后会改变状态的并且不可恢复的:
试想一下火箭发射时的红色按钮怎么测试的,总不见得用真实火箭来测试,肯定要实现一套验证发射系统的模拟火箭。

#10 楼 @htmlbiji 对于你上面提到的,按照文中的说明应该是实现一个服务器的 fake ,而不是 stub 。stub 更多的是在同一模块内的东西,例如你直接把解析返回值的函数替换成返回确定值的函数,这才是 stub 。

#9 楼 @andward 如果模块耦合度高,那么搞什么测试都会很痛苦。。。
另外,不是很理解你前面提到的

这三个的实现基本是:

  • component 级别的 unittest case
  • component 级别的 unittest framework
  • front-only 的 integration test

你说的是主要用的地方是吧? unittest framework 具体是指什么?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册