Spock框架中的小样,存根和间谍

Spock提供了3个功能强大(但本质上有所不同)的工具,可简化编写测试的过程:Mock,Stub和Spy。



通常,需要测试的代码需要与称为依赖项的外部模块进行交互(原始文章使用协作者一词,在俄语环境中并不常见)。


单元测试通常设计为使用mok的各种变体(模拟,存根和间谍)测试单个隔离的类。 因此,随着依赖代码的发展,测试将更加可靠,并且不会破裂。


当更改依赖关系实现的内部细节时,此类隔离的测试不太容易出现问题。


译者的话:每次我使用Spock框架编写测试时,我都觉得选择替代依赖的方式时会犯错。 本文提供了最短的备忘单,用于选择创建Mokas的机制。


TL; DR


嘲弄


使用模拟来:


  • 测试代码和依赖项之间的合同检查
  • 检查依赖方法的正确次数
  • 验证依赖代码被调用的参数

存根


使用存根可以:


  • 提供预定义的通话结果
  • 执行依赖项期望的预定义操作,例如引发异常

间谍


恐惧间谍。 正如Spock文档所述:


使用此机制前请三思。 也许您应该重新设计解决方案并重新组织代码。

但是碰巧的是,在某些情况下,我们不得不使用遗留代码。 旧版代码可能很难甚至不可能用暴民和存根进行测试。 在这种情况下,只有一种解决方案:使用Spy。


最好让旧代码包含在使用Spy的测试中,而不要完全没有旧测试。


使用间谍可以:


  • 测试无法通过其他方法测试的旧版代码
  • 检查依赖方法的正确次数
  • 验证传递的参数
  • 提供预定义的依赖项响应
  • 执行预定义的操作以响应对依赖方法的调用

嘲弄



当单元测试的任务是验证被测代码与依赖项之间的约定时,moxas的所有功能都得到体现。 让我们看下面的示例,其中有一个使用FooService作为依赖项的FooController控制器,并通过FooService测试了此功能。


FooController.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } } 

FooService.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } } 

在这种情况下,我们要编写一个测试来测试:


  • FooControllerFooService之间的合同
  • FooService.doSomething(name)被调用的正确次数
  • 使用正确的参数调用FooService.doSomething(name)

看一下测试:


MockSpec.groovy


 package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class MockSpec extends Specification implements ControllerUnitTest<FooController> { void "Mock FooService"() { given: "  " def fooService = Mock(FooService) and: "    " controller.fooService = fooService when: "  " controller.doSomething() then: "         " 1 * fooService.doSomething("Sally") and: "  ''    - 'null'" response.text == null.toString() } } 

上面的测试创建了一个服务模拟:


 def fooService = Mock(FooService) 

该测试还检查FooService.doSomething(name)是否被调用一次,并且传递给它的参数与字符串"Sally"匹配。


 1 * fooService.doSomething("Sally") 

上面的代码解决了4个重要问题:


  • FooService创建模拟
  • 验证是否使用String参数和值"Sally"恰好一次调用了FooService.doSomething(String name)
  • 隔离测试代码,替换依赖关系实现

存根


被测试的代码是否使用依赖关系? 测试的目的是为了确保与依赖项进行交互时,被测代码能够正常工作? 依赖方法调用的结果是否输入了被测代码的值?


如果测试代码的行为根据依赖项的行为而变化,则需要使用存根(Stub)。


让我们看下面的FooControllerFooService示例,并使用存根测试控制器功能。


FooController.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } } 

FooService.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } } 

测试代码:


StubSpec.groovy


 package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class StubSpec extends Specification implements ControllerUnitTest<FooController> { void "Stub FooService"() { given: "  " def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" } and: "    " controller.fooService = fooService when: "  " controller.doSomething() then: "   " // 1 * fooService.doSomething() //        response.text == "Stub did something" } } 

您可以这样创建一个存根:


 def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" } 

上面的代码解决了4个重要问题:


  • 创建一个存根FooService
  • 确保无论传递的参数如何, FooService.doSomething(String name)都将返回字符串"Stub did something" (因此我们使用了_字符)
  • 隔离测试的代码,用存根替换依赖实现

间谍


请不要阅读本节。


不要看


跳到下一个。


还在读书吗? 好吧,好的,让我们处理Spy。



不要使用间谍。 正如Spock文档所述:


使用此机制前请三思。 也许您应该重新设计解决方案并重新组织代码。

在这种情况下,有些情况下我们不得不使用遗留代码。 旧版代码无法使用小怪或存根进行测试。 在这种情况下,间谍仍然是唯一可行的选择。


间谍与mokas或stub有所不同,因为它们不像stub那样工作。


用mok或stub替换依赖项时,将创建测试对象,并且不执行依赖项的实际源代码。


另一方面,间谍将执行为其创建间谍的依赖项的主要源代码,但间谍将允许您更改间谍返回的内容并检查方法调用,就像mokas和stub一样。 (因此命名为Spy)。


让我们看下面的FooController示例,该示例使用FooService ,然后使用间谍测试功能。


FooController.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } } 

FooService.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } } 

测试代码:


SpySpec.groovy


 package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class SpySpec extends Specification implements ControllerUnitTest<FooController> { void "Spy FooService"() { given: " -" def fooService = Spy(FooService) and: "   " controller.fooService = fooService when: "  " controller.doSomething() then: "     " 1 * fooService.doSomething("Sally") >> "A Spy can modify implementation" and: '     ' response.text == "A Spy can modify implementation" } } 

创建一个间谍实例非常简单:


 def fooService = Spy(FooService) 

在上面的代码中,间谍程序使我们可以检查对FooService.doSomething(name)的调用,调用数量和参数值。 而且,间谍更改了方法的实现以返回不同的值。


 1 * fooService.doSomething("Sally") >> "A Spy can modify implementation" 

上面的代码解决了4个重要问题:


  • FooService创建一个间谍实例
  • 检查依赖项交互
  • 根据依赖方法调用的某些结果检查应用程序的工作方式
  • 隔离测试的代码,用存根替换依赖实现

常见问题


使用哪个选项:模拟,存根或间谍?


这是许多开发人员面临的问题。 如果不确定使用哪种方法,此常见问题解答可以提供帮助。


问:测试的目的是为了验证被测代码与依赖项之间的契约?


答:如果回答是,请使用模拟


问:测试的目的是确保与依赖项进行交互时被测代码能够正常工作吗?


答:如果回答是,请使用存根


问:调用依赖方法的结果是否输入了被测代码的值?


答:如果回答是,请使用存根


问:您是否使用旧代码,这很难测试,并且没有其他选择?


答:尝试使用间谍


代码示例


您可以在以下位置找到本文所有示例的代码:


https://github.com/ddelponte/mock-stub-spy


有用的链接


Source: https://habr.com/ru/post/zh-CN435488/


All Articles