Testing orientado a BDD con Spock (2/2)

Después de la primera parte de Testing orientado a BDD con Spock, en el que vimos cómo escribir especificaciones BDD para pruebas con Spock, en este post vamos a ver algunas utilidades más avanzadas que tenemos a nuestra disposición: cómo hacer mocking y stubbing, cómo testear excepciones o cómo utilizar algunas extensiones que nos pueden ser de gran ayuda. También veremos cómo sacar unos estupendos informes de tests que nos serán útiles tanto para el equipo como para el cliente.

Comenzamos…

Mocking y Stubbing

Seguro que, después de leer el primer post, te has preguntado cómo simular el comportamiento de algunos de tus servicios o funcionalidades sin necesidad de implementarlos o ejecutarlos. Para ello, Spock nos da soporte para hacer mocking y stubbing. Veamos en qué consiste cada uno.

Cuando hacemos ‘mocking’ conseguimos simular el comportamiento de una clase y las interacciones con otros colaboradores.

Cuando hacemos ‘stubbing’ podemos especificar cómo queremos que se comporten los métodos de una clase.

Si vemos el ejemplo, al hacer mocking de ‘NotificacionService’ lo estamos simulando, sin necesidad de levantar el contexto de la aplicación. Por otro lado, al hacer stubbing de ‘CustomerRepositoy’ no solo lo estamos simulando, sino que estamos especificando qué resultado queremos que devuelva su método ‘save’ (en esta caso, que devuelva ‘true’) sin especificar qué parámetros le vamos a pasar.

Por tanto, en este escenario, el resultado de llamar al método ‘CustomerService.registerCustomer()’ con unos datos cualquiera es que se va a invocar al método ‘NotificationService.sendWelcomeMessage()’ devolviendo el mensaje ‘Hi, welcome!’.

interface NotificationService {

   def sendWelcomeMessage(Customer customer, String message);

   def sendErrorMessage(Customer customer, String message);
}

NotificationService.groovy

@RepositoryRestResource(collectionResourceRel = "customer", path = "customer")
public interface CustomerRepository extends CrudRepository<Customer, Long> {
   List<Customer> findByName(String name)
  
   // Method save --> implicit method because
   // CustomerRepository extends of CrudRepository
}

CustomerRepository.groovy

void 'Send welcome notificacion when customer is created'() {
   setup:  
       // Mocking
       def mockedNotificationService = Mock(NotificationService)

       // Stubbing
       def stubbedCustomerRepository = Stub(CustomerRepository){
           save(_) >> true // customerRepository.save() method was ok
       }

       def customerService = new CustomerService(mockedNotificationService,stubbedCustomerRepository)

   When: 'A costumer is created correctly'
       customerService.registerCustomer('CustomerName', 'CustomerSurname')

   then: 'A welcome notification is sent'
       1 * mockedNotificationService.sendWelcomeMessage(_, "Hi, welcome!") // One call to this method
   
    and: 'An error message is not sent'
       0 * mockedNotificationService.sendErrorMessage(_, _) // No calls to this method
}

MockingAndStubbingSpec.groovy

class CustomerService {

   private NotificationService notificationService
   private CustomerRepository customerRepository

   public CustomerService(NotificationService notificationService,CustomerRepository customerRepository) {
       this.notificationService = notificationService
       this.customerRepository = customerRepository
   }

   void registerCustomer(String name, String lastName) {
       Customer customer = new Customer(name:name, lastName:lastName)
       if(customerRepository.save(customer))
           notificationService.sendWelcomeMessage(customer, "Hi, welcome!")
       else {
           notificationService.sendErrorMessage(customer, "Oops, an error has ocurred!")
       }
   }
}

CustomerService.groovy

Exceptions

Con Spock también podemos comprobar en nuestros test si se lanzan excepciones:

def 'Throw NullPointerException'() {

   given: 'a null object'
       Customer customer = null
   when: 'try to access to the object'
       customer.name
   then:'throw a nullpointerexception'
       thrown NullPointerException
}

Extensions

Además de anotaciones como las ya vistas en el post anteior (@Shared, @Unroll), veamos ahora algunas otras disponibles que nos proporcionarán utilidades extra a la hora de implementar nuestros tests.

@Narrative & @Title

Podemos utilizar estas anotaciones para añadir un título y una explicación a nuestros tests, como por ejemplo, la descripción de la historia de usuario.

@Narrative("""
As a user
I want to search customers by name
to find them quickly
""")
@Title("""This is easy
to read
_______________________
""")
class Example9_SearchCustomerUnitSpec extends Specification { … }

Uso de @Narrative & @Title en Informes

@Issue

Podemos utilizar esta extensión para enlazar la ‘issue’, ‘tarea’, ‘user story’ o elemento al que haga referencia nuestro test.

""")
@Issue("https://github.com/spockframework/spock/issues/684")
class Example9_SearchCustomerUnitSpec extends Specification{...}

Uso de @Issue en Informes

@Ignore, @IgnoreRest & @IgnoreIf

Estas etiquetas nos sirven respectivamente para: ignorar un test, ignorar el resto (menos el anotado) o ignorar bajo una condición:

@IgnoreIf({ isFriendsCharacter("Rachel") })
void 'run if is a friend character'() {
   expect:
       true
}

// helper methods
static boolean isFriendsCharacter(String name) {
   name in ['Rachel', 'Monica', 'Phoebe', 'Joey', 'Chandler', 'Ross']
}

@Requires

Cuando necesitamos que se cumpla una condición:

@Requires({ OperatingSystem.current.macOs })
void 'only run on MacOS'() {
   expect:
       println 'MacOS'
       true
}

@Stepwise

Una clase de test anotada con @Stepwise asegura que el orden de ejecución de los test es tal y como se ha declarado. Además, si uno de los tests de la clase falla, el resto no se ejecuta. Si tenemos tests anotados con alguno de los @Ignore… se ignorarán tal y como se hayan declarado.

@Timeout

Sirve para que los tests de una clase o un test en concreto fallen si se excede de un tiempo determinado (por defecto en milisegundos aunque podemos especificar las unidades):

@Timeout(value = 100, unit = TimeUnit.MILLISECONDS)

Encontramos más información en ‘Extensions’.

Informes de tests

Como introducía al principio de este post, Spock, por defecto, nos proporciona unos informes en html o xml donde, por cada ejecución, se muestra el resultado de dichos tests. Pero, además, está disponible la librería ‘Spock Reports Extension’ que da un paso más en la generación de dichos informes, proporcionando más información y permitiendo customización.

Si, por ejemplo, usas Gradle, solo tienes que añadir esta librería a tus dependencias:

testCompile( 'com.athaydes:spock-reports:1.2.13' )

Como resultado, tendremos unos informes con este aspecto:

Resumen de tests

Resultado con éxito

Datatable – Uso de @Unroll con sustitución de variables – resultado fallido

Datatable – Uso de @Unroll con sustitución de variables – resultado fallido

Por último, puedes encontrar toda la documentación y recursos del framework en la web oficial de Spock. Espero que después de haber leído el primer post y ahora éste, te animes a implementar las pruebas de tus proyectos con este framework y que aproveches la ocasión para iniciarte con Groovy como lenguaje de programación.

Ingeniero de Software desde 2007 (universidades, e-commerce, prensa digital, banca…). Scrum Master en Paradigma Digital desde 2013
PSM I Certified, Scrum.org. Ponente en diversos eventos sobre TI (Conferencia Agile Spain, Agile Open Space, Greach, SpringIO, SpringOne, T3chFest, Madrid Groovy User Group, Madrid Spring User Group). Organizadora de ‘Madrid Spring User Group’.

Ver toda la actividad de Fátima Casaú

Escribe un comentario