自动化测试中,我们常会使用一些经过简化的,行为与表现类似于生产环境下的对象的复制品。引入这样的复制品能够降低构建测试用例的复杂度,允许我们独立而解耦地测试某个模块,不再担心受到系统中其他部分的影响;这类型对象也就是所谓的 Test Double。实际上对于 Test Double 的定义与阐述也是见仁见智,Gerard Meszaros 在这篇文章中就介绍了五个不同的 Double 类型;而人们更倾向于使用 Mock 来统一描述不同的 Test Doubles。不过对于 Test Doubles 实现的误解还是可能会影响到测试的设计,使测试用例变得混乱和脆弱,最终带来不必要的重构。本文则是从作者个人的角度描述了常见的 Test Doubles 类型及其具体的实现:Fake、Stub 与 Mock,并且给出了不同的 Double 的使用场景。
成都创新互联公司是少有的网站建设、网站设计、营销型企业网站、小程序开发、手机APP,开发、制作、设计、买友情链接、推广优化一站式服务网络公司,从2013年成立,坚持透明化,价格低,无套路经营理念。让网页惊喜每一位访客多年来深受用户好评
Fake
如下图所示,Fake 可以是某个 Data Access Object 或者 Repository 的基于内存的实现;该实现并不会真的去进行数据库操作,而是使用简单的 HashMap 来存放数据。这就允许了我们能够在并没有真的启动数据库或者执行耗时的外部请求的情况下进行服务的测试。
- @Profile("transient")
- public class FakeAccountRepository implements AccountRepository {
- Map
accounts = new HashMap<>(); - public FakeAccountRepository() {
- this.accounts.put(new User("john@bmail.com"), new UserAccount());
- this.accounts.put(new User("boby@bmail.com"), new AdminAccount());
- }
- String getPasswordHash(User user) {
- return accounts.get(user).getPasswordHash();
- }
- }
除了应用到测试,Fake 还能够用于进行原型设计或者峰值模拟中;我们能够迅速地实现系统原型,并且基于内存存储来运行整个系统,推迟有关数据库设计所用到的一些决定。另一个常见的使用场景就是利用 Fake 来保证在测试环境下支付永远返回成功结果。
Stub
Stub 的典型应用场景即是当某个对象需要从数据库抓取数据时,我们并不需要真实地与数据库进行交互或者像 Fake 那样从内存中抓取数据,而是直接返回预定义好的数据。
- public class GradesService {
- private final Gradebook gradebook;
- public GradesService(Gradebook gradebook) {
- this.gradebook = gradebook;
- }
- Double averageGrades(Student student) {
- return average(gradebook.gradesFor(student));
- }
- }
我们在编写测试用例时并没有从 Gradebook 存储中抓取数据,而是在 Stub 中直接定义好需要返回的成绩列表;我们只需要足够的数据来保证对平均值计算函数进行测试就好了。
- public class GradesServiceTest {
- private Student student;
- private Gradebook gradebook;
- @Before
- public void setUp() throws Exception {
- gradebook = mock(Gradebook.class);
- student = new Student();
- }
- @Test
- public void calculates_grades_average_for_student() {
- when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); //stubbing gradebook
- double averageGrades = new GradesService(gradebook).averageGrades(student);
- assertThat(averageGrades).isEqualTo(8.0);
- }
- }
Command Query Separation
仅返回部分结果而并没有真实改变系统状态的的方法被称作查询(Query)。譬如 avarangeGrades,用于返回学生成绩平均值的函数就是非常典型的例子:Double getAverageGrades(Student student);。该函数仅返回了某个值,而没有其他的任何副作用。正如我们上文中介绍的,我们可以使用 Stubs 来替换提供实际成绩值的函数,从而简化了整个测试用例的编写。不过除了 Query 之外还有另一个类别的方法,被称作 Command。即当某个函数在执行某些操作的时候还改变了系统状态,不过该类型函数往往没有什么返回值:void sendReminderEmail(Student student);。这种对于方法的划分方式也就是 Bertrand Meyer 在Object Oriented Software Construction 一书中介绍的 Command Query 分割法。
对于 Query 类型的方法我们会优先考虑使用 Stub 来代替方法的返回值,而对于 Command 类型的方法的测试则需要依赖于 Mock。
Mock
当我们并不希望真的调用生产环境下的代码或者在测试中难于验证真实代码执行效果的时候,我们会用 Mock 来替代那些真实的对象。典型的例子即是对邮件发送服务的测试,我们并不希望每次进行测试的时候都发送一封邮件,毕竟我们很难去验证邮件是否真的被发出了或者被接收了。我们更多地关注于邮件服务是否按照我们的预期在合适的业务流中被调用,其概念如下图所示:
- public class SecurityCentral {
- private final Window window;
- private final Door door;
- public SecurityCentral(Window window, Door door) {
- this.window = window;
- this.door = door;
- }
- void securityOn() {
- window.close();
- door.close();
- }
- }
在上述代码中,我们并不想真的去关门来测试 securityOn 方法,因此我们可以设置合适的 Mock 对象:
- public class SecurityCentralTest {
- Window windowMock = mock(Window.class);
- Door doorMock = mock(Door.class);
- @Test
- public void enabling_security_locks_windows_and_doors() {
- SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock);
- securityCentral.securityOn();
- verify(doorMock).close();
- verify(windowMock).close();
- }
- }
在 securityOn 方法执行之后,window 与 door 的 Mock 对象已经记录了所有的交互信息,这就允许我们能够去验证 Window 与 Door 是否被真实的调用。或许有人会疑问是否在真实环境下门与窗是否被真的关闭了?其实我们并不能保证,不过这也不是我们关注的点,也不是 SecurityCentral 这个类关注的目标。门与窗是否能被正常的关闭应该是由 Door 与 Window 这两个类所关注的。
【本文是专栏作者“张梓雄 ”的原创文章,如需转载请通过与作者联系】
戳这里,看该作者更多好文
当前名称:测试中Fakes、Mocks以及Stubs概念明晰
标题网址:http://www.csdahua.cn/qtweb/news22/24622.html
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网