Spock proporciona 3 herramientas poderosas (pero diferentes en esencia) que simplifican las pruebas de escritura: Mock, Stub y Spy.
Muy a menudo, el c贸digo que necesita ser probado necesita interactuar con m贸dulos externos llamados dependencias (el art铆culo original usa el t茅rmino colaboradores, que no es muy com煤n en el entorno en ruso).
Las pruebas unitarias se dise帽an con mayor frecuencia para probar una sola clase aislada utilizando varias variantes de mok: Mock, Stub y Spy. Por lo tanto, las pruebas ser谩n m谩s confiables y menos propensas a romperse a medida que evoluciona el c贸digo de dependencia.
Tales pruebas aisladas son menos propensas a problemas al cambiar los detalles internos de la implementaci贸n de dependencia.
De un traductor: cada vez que uso Spock Framework para escribir pruebas, siento que puedo cometer un error al elegir una forma de reemplazar las dependencias. Este art铆culo tiene la hoja de trucos m谩s corta posible para elegir un mecanismo para crear mokas.
TL; DR
Simulacros
Use Mock para:
- verificaciones de contratos entre c贸digo de prueba y dependencias
- comprobar que los m茅todos de dependencia se llaman la cantidad correcta de veces
- validaci贸n de par谩metros con los que se llama el c贸digo de dependencia
Trozos
Use Stub para:
- proporcionar resultados de llamadas predefinidos
- realizar acciones predefinidas esperadas de dependencias, como lanzar excepciones
Esp铆as
Miedo esp铆as. Como dice la documentaci贸n de Spock:
Pi茅nselo dos veces antes de usar este mecanismo. Quiz谩s deber铆a redise帽ar su soluci贸n y reorganizar su c贸digo.
Pero sucede que hay situaciones en las que tenemos que trabajar con c贸digo heredado. El c贸digo heredado puede ser dif铆cil o incluso imposible de probar con mobs y trozos. En este caso, solo hay una soluci贸n: usar Spy.
Es mejor tener el c贸digo heredado cubierto con pruebas usando Spy que no tener pruebas heredadas.
Usa Spy para:
- prueba de c贸digo heredado que no puede ser probado por otros m茅todos
- comprobar que los m茅todos de dependencia se llaman la cantidad correcta de veces
- validaci贸n de par谩metros pasados
- proporcionando una respuesta de dependencia predefinida
- realizar acciones predefinidas en respuesta a llamadas a m茅todos de dependencia
Simulacros
Todo el poder de moxas se manifiesta cuando la tarea de una prueba unitaria es verificar el contrato entre el c贸digo probado y las dependencias. Veamos el siguiente ejemplo, donde tenemos un controlador FooController
que usa FooService
como una dependencia, y pruebe esta funcionalidad con mobs.
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" } }
En este escenario, queremos escribir una prueba que pruebe:
- contrato entre
FooController
y FooService
FooService.doSomething(name)
se llama la cantidad correcta de vecesFooService.doSomething(name)
con el par谩metro correcto
Echa un vistazo a la prueba:
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() } }
La prueba anterior crea una simulaci贸n de servicio:
def fooService = Mock(FooService)
La prueba tambi茅n comprueba que FooService.doSomething(name)
se llama una vez, y el par谩metro que se le pasa coincide con la cadena "Sally"
.
1 * fooService.doSomething("Sally")
El c贸digo anterior resuelve 4 problemas importantes:
- crea simulacro para
FooService
- verifica que
FooService.doSomething(String name)
se llame exactamente una vez con el par谩metro String
y el valor "Sally"
- a铆sla el c贸digo de prueba, reemplazando la implementaci贸n de dependencia
Trozos
驴El c贸digo probado utiliza dependencias? 驴El prop贸sito de las pruebas es asegurar que el c贸digo bajo prueba funcione correctamente cuando interact煤a con las dependencias? 驴Son los resultados de las llamadas a los m茅todos de dependencia valores de entrada para el c贸digo bajo prueba?
Si el comportamiento del c贸digo probado cambia seg煤n el comportamiento de las dependencias, entonces debe usar stubs (Stub).
Veamos el siguiente ejemplo con FooController
y FooService
y pruebe la funcionalidad del controlador usando stubs.
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" } }
C贸digo de prueba:
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: " "
Puede crear un trozo como este:
def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" }
El c贸digo anterior resuelve 4 problemas importantes:
- crea un trozo de
FooService
- se asegura de que
FooService.doSomething(String name)
devolver谩 la cadena "Stub did something"
independientemente del par谩metro pasado (por lo que utilizamos el car谩cter _
) - a铆sla el c贸digo probado, reemplazando la implementaci贸n de dependencia con un trozo
Esp铆as
Por favor no lea esta secci贸n.
No mires
Salta al siguiente.
驴Todav铆a leyendo? Bueno entonces, est谩 bien, tratemos con Spy.
No uses Spy. Como dice la documentaci贸n de Spock:
Pi茅nselo dos veces antes de usar este mecanismo. Quiz谩s deber铆a redise帽ar su soluci贸n y reorganizar su c贸digo.
En este caso, hay situaciones en las que tenemos que trabajar con c贸digo heredado. El c贸digo heredado no se puede probar con mobs o stubs. En este caso, el esp铆a sigue siendo la 煤nica opci贸n viable.
Los esp铆as son diferentes de los mokas o trozos porque no funcionan como trozos.
Cuando una dependencia se reemplaza con un mok o stub, se crea un objeto de prueba y no se ejecuta el c贸digo fuente real de la dependencia.
El esp铆a, por otro lado, ejecutar谩 el c贸digo fuente principal de la dependencia para la cual se cre贸 el esp铆a, pero el esp铆a le permitir谩 cambiar lo que devuelve el esp铆a y verificar las llamadas a los m茅todos, al igual que los mokas y los trozos. (De ah铆 el nombre Spy).
Veamos el siguiente ejemplo de FooController
, que usa FooService
, y luego pruebe la funcionalidad con un esp铆a.
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" } }
C贸digo de prueba:
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" } }
Crear una instancia de esp铆a es bastante simple:
def fooService = Spy(FooService)
En el c贸digo anterior, el esp铆a nos permite verificar la llamada a FooService.doSomething(name)
, la cantidad de llamadas y los valores de los par谩metros. Adem谩s, el esp铆a cambia la implementaci贸n del m茅todo para devolver un valor diferente.
1 * fooService.doSomething("Sally") >> "A Spy can modify implementation"
El c贸digo anterior resuelve 4 problemas importantes:
- crea una instancia de esp铆a para
FooService
- comprueba la interacci贸n de dependencia
- comprueba c贸mo funciona la aplicaci贸n de acuerdo con ciertos resultados de llamadas al m茅todo de dependencia
- a铆sla el c贸digo probado, reemplazando la implementaci贸n de dependencia con un trozo
FAQ
驴Qu茅 opci贸n usar: Mock, Stub o Spy?
Esta es una pregunta que enfrentan muchos desarrolladores. Estas preguntas frecuentes pueden ayudar si no est谩 seguro de qu茅 enfoque usar.
P: 驴El prop贸sito de probar la verificaci贸n del contrato entre el c贸digo probado y las dependencias?
A: Si respondi贸 S铆, use Mock
P: 驴El prop贸sito de las pruebas es asegurarse de que el c贸digo que se prueba funciona correctamente cuando interact煤a con las dependencias?
R: Si respondi贸 S铆, use Stub
P: 驴Los resultados de llamar a los m茅todos de dependencia introducen valores para el c贸digo bajo prueba?
R: Si respondi贸 S铆, use Stub
P: 驴Trabaja con c贸digo heredado, que es muy dif铆cil de probar y no le quedan opciones?
A: Intenta usar Spy
Ejemplo de c贸digo
Puede encontrar el c贸digo para todos los ejemplos en este art铆culo en:
https://github.com/ddelponte/mock-stub-spy
Enlaces utiles