Por qué necesitamos Vulcan a bordo: Revisión del marco de Spock

La automatización de pruebas ayuda a monitorear constantemente la calidad de un producto de TI, así como a reducir costos a largo plazo. Hay varios enfoques en la automatización, por ejemplo, el desarrollo impulsado por el comportamiento (BDD), el desarrollo a través del comportamiento.



Asociados con este enfoque están pepino, marco de robot, comportamiento y otros, que separan los scripts de ejecución y la implementación de cada construcción. Esta separación ayuda a escribir scripts legibles, pero lleva mucho tiempo y, por lo tanto, puede resultar poco práctica al escribir una implementación.



Echemos un vistazo a cómo puede simplificar su trabajo con BDD utilizando las herramientas adecuadas, por ejemplo, el marco Spock, que combina la belleza, la conveniencia de los principios de BDD y las características de jUnit.







Marco de Spock



Spock es un marco de prueba y especificación para aplicaciones Java y Groovy. Usando la plataforma JUnit como base, este marco es compatible con todos los IDE populares (en particular, IntelliJ IDEA), varias herramientas de compilación (Ant, Gradle, Maven) y servidores de integración continua (CI).



Como escriben los desarrolladores del marco, Spock "está inspirado en JUnit , RSpec , jMock , Mockito , Groovy , Scala , Vulcans y otras formas de vida fascinantes".



En este artículo, echaremos un vistazo a la última versión disponible, Spock Framework 2.0. Sus características: la posibilidad de usar JUnit5, Java 8+, groovy 2.5 (también hay un ensamblaje con la versión 3.0). Spock tiene licencia de Apache 2.0 y tiene una comunidad de usuarios receptiva. Los desarrolladores del marco continúan perfeccionando y desarrollando Spock, que ya incluye muchas extensiones que le permiten ajustar su ejecución de prueba. Por ejemplo, una de las áreas de mejora anunciadas más interesantes es la incorporación de la ejecución de pruebas en paralelo.



Groovy



Groovy es un lenguaje de programación orientado a objetos desarrollado para la plataforma Java como complemento con capacidades de Python, Ruby y Smalltalk. Groovy utiliza una sintaxis similar a Java con compilación dinámica para el código de bytes JVM y trabaja directamente con otros códigos y bibliotecas de Java. El lenguaje se puede utilizar en cualquier proyecto Java o como lenguaje de programación.



Las características de groovy incluyen: escritura tanto estática como dinámica; sintaxis incorporada para listas, matrices y expresiones regulares; sobrecarga de operaciones. Sin embargo, los cierres en Groovy aparecieron mucho antes que Java.



Groovy es muy adecuado para el desarrollo rápido de pruebas, donde puede usar azúcar sintáctico similar a Python sin preocuparse por escribir objetos.



Características de Spock Framework



Una de las características clave del marco es que el desarrollador tiene la capacidad de escribir especificaciones con las características esperadas del sistema utilizando los principios del enfoque BDD. Este enfoque permite componer pruebas funcionales orientadas al negocio para productos de software con una gran complejidad temática y organizativa.



La especificación es una clase maravillosa que se extiende a spock.lang.



class MyFirstSpecification extends Specification {
  // fields
  // fixture methods
  // feature methods
  // helper methods
}


La lista de materiales puede contener varios campos auxiliares que se activan para cada clase de lista de materiales.



Con la anotación @Shared, puede dar acceso al campo a las clases heredadas de la especificación.



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }

}


Métodos de personalización de clases de BOM:



def setupSpec() {} //     feature    
def setup() {}     //    feature 
def cleanup() {}   //    feature 
def cleanupSpec() {} //     feature   


La siguiente tabla muestra qué palabras clave y métodos en el marco de Spock tienen contrapartes JUnit.







Bloques de masa



En Spock Framework, cada fase de prueba se divide en un bloque de código separado (consulte la documentación para ver un ejemplo ).







Un bloque de código comienza con una etiqueta y termina con el comienzo del siguiente bloque de código o el final de una prueba.



El bloque dado es responsable de establecer las condiciones de prueba iniciales.



Bloquea el cuándo , el entonces siempre se usa junto. El bloque when contiene el estimulante, el estímulo del sistema, y ​​el bloque then contiene la respuesta del sistema.



En los casos en los que es posible acortar la cláusula when-then a una sola expresión, puede utilizar un solo bloque de espera... Los siguientes ejemplos se utilizarán de la documentación del marco oficial de Spock:



when:
def x = Math.max(1, 2)
 
then:
x == 2


o una expresión



expect:
Math.max(1, 2) == 2


El bloque de limpieza se utiliza para liberar recursos antes de la siguiente iteración de la prueba.



given:
def file = new File("/some/path")
file.createNewFile()
 
// ...
 
cleanup:
file.delete()


La cláusula where se utiliza para transferir datos para realizar pruebas (pruebas basadas en datos).



def "computing the maximum of two numbers"() {
  expect:
  Math.max(a, b) == c
 
  where:
  a << [5, 3]
  b << [1, 9]
  c << [5, 9]
}


Los tipos de transferencia de datos de entrada se discutirán a continuación.



Implementación de prueba de muestra en Spock Framework



A continuación, consideraremos enfoques para la implementación de probar la página web para la autorización del usuario en el sistema que usa selenio.



import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver
    
    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }
}


Aquí vemos la clase base de la especificación de la página. Al comienzo de la clase, vemos la importación de las clases requeridas. La siguiente es la anotación compartida , que permite que las clases heredadas accedan al controlador web. En el bloque setup () , vemos el código para inicializar el controlador web y abrir la página web. El bloque cleanup () contiene el código para finalizar el controlador web.



A continuación, pasemos a una descripción general de la especificación de la página de inicio de sesión del usuario.



import pages.LoginPage
import spock.lang.Issue

class LoginPageTest extends PagesBaseSpec {

    @Issue("QAA-1")
    def "QAA-1: Authorization with correct login and password"() {

        given: "Login page"
        def loginPage = new LoginPage(driver)

        and: "Correct login and password"
        def adminLogin = "adminLogin"
        def adminPassword = "adminPassword"

        when: "Log in with correct login and password"
        loginPage.login(adminLogin, adminPassword)

        then: "Authorized and moved to main page"
        driver.currentUrl == "www.anywebservice.ru/main"
    }
}


La especificación de la página de autorización hereda de la especificación de la página base. La anotación Problema especifica el ID de la prueba en un sistema de seguimiento externo (como Jira). En la siguiente línea, vemos el nombre de la prueba, que por convención se establece mediante cadenas literales, lo que le permite utilizar cualquier carácter en el nombre de la prueba (incluidos los rusos). En el bloque dado , se inicializa el objeto de página de la clase de página de autorización y se obtienen el nombre de usuario y la contraseña correctos para la autorización en el sistema. En el bloque when , se realiza la acción de autorización. El bloque then se usa para verificar la acción esperada, es decir, la autorización exitosa y la redirección a la página principal del sistema.



En el ejemplo de esta especificación, vemos la ventaja más significativa de usar el paradigma BDD en spock: la especificación del sistema es al mismo tiempo su documentación. Cada prueba describe un comportamiento determinado, cada paso de la prueba tiene su propia descripción, que es comprensible no solo para los desarrolladores, sino también para los clientes. Las descripciones de los bloques se pueden presentar no solo en el código fuente de la prueba, sino también en mensajes de diagnóstico o informes sobre la operación de prueba.



El marco proporciona la capacidad de transferir varios inicios de sesión y contraseñas para realizar pruebas (parametrizar la prueba).



Pruebas basadas en datos en Spock Framework



Prueba basada en datos = prueba basada en tabla = prueba parametrizada


Para probar un escenario con varios parámetros, puede utilizar varias opciones para pasarlos.



Tablas de datos



Veamos algunos ejemplos de la documentación oficial del marco.



class MathSpec extends Specification {
  def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c
 
    where:
    a | b | c
    1 | 3 | 3
    7 | 4 | 7
    0 | 0 | 0
  }
}


Cada fila de la tabla es una iteración de prueba separada. Además, la tabla se puede representar mediante una columna.



where:
a | _
1 | _
7 | _
0 | _


_ Es un objeto stub de la clase BOM.



Para una mejor percepción visual de los parámetros, puede reescribir el ejemplo anterior de la siguiente forma:



def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c
 
    where:
    a | b || c
    1 | 3 || 3
    7 | 4 || 7
    0 | 0 || 0
}


Ahora podemos ver que a , b son entradas yc es el valor esperado.



Tubos de datos



En algunos casos, usar la tabla de diseño será muy engorroso. En tales casos, puede utilizar el siguiente tipo de paso de parámetros:



...
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]


Aquí, el desplazamiento a la izquierda << es un operador maravilloso sobrecargado que ahora actúa como una adición a la lista.



Para cada iteración de prueba, se solicitarán los siguientes datos de la lista para cada variable:



1 iteración: a = 1, b = 3, c = 3;

2da iteración: a = 7, b = 4, c = 7;

3 iteraciones: a = 0, b = 0, c = 0.



Además, los datos de entrada no solo se pueden transmitir explícitamente, sino que también se pueden solicitar, si es necesario, de diversas fuentes. Por ejemplo, de la base de datos:



@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
 
def "maximum of two numbers"() {
  expect:
  Math.max(a, b) == c
 
  where:
  [a, b, c] << sql.rows("select a, b, c from maxdata")
}


Asignación de variables de datos



...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b


Aquí vemos la variable c calculada dinámicamente en los datos de prueba.



Combinación de diferentes tipos de transferencia de parámetros



...
where:
a | _
3 | _
7 | _
0 | _
 
b << [5, 0, 0]
 
c = a > b ? a : b


Nadie le prohíbe utilizar varios tipos de transferencia a la vez, si es necesario.



Un ejemplo de implementación de una prueba parametrizada en Spock Framework



@Issue("QAA-1-parametrized")
def "QAA-1-parametrized: Authorization with correct login and password"() {

   given: "Login page"
   def loginPage = new LoginPage(driver)

   when: "Log in with correct login and password"
   loginPage.login(login, password)

   then: "Authorized and moved to main page"
   driver.currentUrl =="www.anywebservice.ru/main"

   where: "Check for different logins and passwords"
   login            | password
   "adminLogin"     | "adminPassword"
   "moderatorLogin" | "moderatorPassword"
   "userLogin"      | "userPassword"
}


Aquí vemos el ya familiar bloque where, en el que se establecen las claves de los parámetros (logins y contraseñas), que se almacenan en el archivo de configuración.



Debido a las peculiaridades de la implementación utilizada de la especificación, para cada parámetro se realizará el ciclo de configuración del controlador web y su cierre (y por ende el cierre del navegador), lo que afectará negativamente el tiempo de ejecución de la prueba. Sugerimos finalizar la especificación y mejorar el tiempo de ejecución de la prueba.



Un ejemplo de implementación de una prueba parametrizada con una especificación modificada



Antes de la revisión



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }

}


Después de la revisión



import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesNoRestartBaseSpec extends Specification {

    @Shared
    protected WebDriver driver

    def setupSpec() {
        this.driver = DriverFactory.createDriver()
    }

    def setup() {
        this.driver.get("www.anywebservice.ru")
    }

    def cleanup() {
        this.driver.get("www.anywebservice.ru/logout")
        this.driver.manage().deleteAllCookies();
    }

    void cleanupSpec() {
        this.driver.quit()
    }
}


En la especificación actualizada, vemos que el procedimiento para crear un controlador web se realizará solo cuando se configure la clase de especificación y se cierre el navegador solo después de que las pruebas de la especificación hayan terminado de ejecutarse. En el método setup (), vemos el mismo código para obtener la dirección web del servicio y abrirlo en el navegador, y en el método cleanup (), vamos a www.anywebservice.ru/logout para terminar de trabajar con el servicio para el usuario actual y eliminar las cookies. (para probar el servicio web actual, este procedimiento es suficiente para simular un lanzamiento "único"). El código de prueba en sí no ha cambiado.



Como resultado, con la ayuda de mejoras simples, obtuvimos al menos una disminución del doble en el tiempo de operación de la prueba automática, en comparación con la implementación inicial.



Comparación de pruebas para testNG, pytest, pytest-bdd



Para empezar, veremos la implementación de una prueba en el marco de prueba testNG en el lenguaje de programación Java, que, como Spock Framework, está inspirado en el marco jUnit y admite pruebas basadas en datos.



package javaTests;

import org.testng.Assert;
import org.testng.annotations.*;
import pages.LoginPage;


public class LoginPageTest extends BaseTest {


    @BeforeClass
    public final void setup() {
        createDriver();
        driver.get("www.anywebservice.ru");
    }

    @DataProvider(name = "userParameters")
    public final Object[][] getUserData(){
        return new Object[][] {
                {"adminLogin", "adminPassword"},
                {"moderatorLogin", "moderatorPassword"},
                {"userLogin", "userPassword"}
        };
    }

    @Test(description = "QAA-1-1: Authorization with correct login and password",
            dataProvider = "userParameters")
    public final void authorizationWithCorrectLoginAndPassword(String login, String password){
        //Login page
        LoginPage loginPage = new LoginPage(driver);

        //Log in with correct login and password
        loginPage.login(login, password);

        //Authorized and moved to main page
        Assert.assertEquals("www.anywebservice.ru/main", driver.getCurrentUrl());
    }

    @AfterMethod
    public final void cleanup() {
        driver.get("www.anywebservice.ru/logout");
        driver.manage().deleteAllCookies();
    }

    @AfterClass
    public final void tearDown() {
        driver.quit();
    }
}


Aquí podemos ver la clase de prueba con todos los métodos setup (), cleanup () necesarios, así como la parametrización de la prueba en forma de un método getUserData () adicional con la anotación @DataProvider, que parece un poco engorrosa después de lo que examinamos en la prueba usando Spock. Marco de referencia. Además, para entender lo que está sucediendo en la prueba, se dejaron comentarios similares a la descripción de los pasos.



Vale la pena señalar que testNG, a diferencia de Spock Framework, admite la ejecución de pruebas en paralelo.







A continuación, pasemos a una prueba utilizando el marco de prueba pytest en el lenguaje de programación Python.



import pytest
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


class TestLogin(object):

    @pytest.mark.parametrize("login,password", [
        pytest.param(("adminLogin", "adminPassword"), id='admin'),
        pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
        pytest.param(("userLogin", "userPassword"), id='user')
    ])
    def test_authorization_with_correct_login_and_password(self, login, password, driver, test_cleanup):
        # Login page
        login_page = LoginPage(driver)
        # Log in with correct login and password
        login_page.login(login, password)

        # Authorized and moved to main page
        assert expected_conditions.url_to_be("www.anywebservice.ru/main")
 
    @pytest.fixture()
    def test_cleanup(self, driver):
        yield "test"
        driver.get("www.anywebservice.ru/logout")
        driver.delete_all_cookies()


Aquí también vemos soporte para pruebas basadas en datos como una construcción separada, similar a @DataProvider en testNG. El método para configurar el controlador web está "oculto" en el dispositivo del controlador. Gracias a la escritura dinámica y los accesorios de pytest, esta prueba parece más limpia que Java.







A continuación, pasemos a una descripción general del código de prueba utilizando el complemento pytest-bdd, que le permite escribir pruebas como archivos de características de Gherkin (enfoque BDD puro).



login.feature



Feature: Login page
  A authorization

  Scenario: Authorizations with different users
    Given Login page
    When Log in with correct login and password
    Then Authorized and moved to main page


test_login.py



import pytest
from pytest_bdd import scenario, given, when, then
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


@pytest.mark.parametrize("login,password", [
    pytest.param(("adminLogin", "adminPassword"), id='admin'),
    pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
    pytest.param(("userLogin", "userPassword"), id='user')
])
@scenario('login.feature', 'Authorizations with different users')
def test_login(login, password):
    pass


@given('Login page')
def login_page(driver):
    return LoginPage(driver)


@when('Log in with correct login and password')
def login_with_correct_login_and_password(login_page, login, password):
    login_page_object = login_page
    login_page_object.login(login, password)

@then('Authorized and moved to main page')
def authorized_and_moved_to_main_page(driver, login):
    assert expected_conditions.url_to_be("www.anywebservice.ru/main")


Una de las ventajas es que sigue siendo un marco de pytest, que tiene muchos complementos para diversas situaciones, incluso para ejecutar pruebas en paralelo. La desventaja es el enfoque BDD puro en sí mismo, que limitará constantemente al desarrollador con sus propias características. Spock Framework hace posible escribir código más conciso y fácil de diseñar en comparación con el paquete PyTest + pytest-bdd.







Conclusión



En este artículo, analizamos cómo simplificar el trabajo con BDD utilizando el marco Spock. Resumiendo, resaltemos brevemente los principales, en nuestra opinión, los pros y los contras de Spock en comparación con algunos otros marcos de prueba comunes.



Pros:



  • El uso de principios de BDD en lugar de un enfoque de BDD puro le brinda más flexibilidad al escribir pruebas.
  • La especificación de prueba escrita también es la documentación del sistema.
  • .
  • groovy ( , , closures ).


:



  • groovy. , , IDE , . Intellij IDEA, , , , .
  • groovy JVM -. , groovy, , . java, groovy .
  • El conjunto de extensiones no es tan extenso como el de testNG, por ejemplo. Como resultado, no hay una prueba de funcionamiento en paralelo. Hay planes para agregar esta funcionalidad, pero se desconoce el momento de su implementación.


En última instancia, Spock Framework indudablemente merece atención, ya que es adecuado para resolver el complejo problema común de automatizar escenarios comerciales para productos de software con una gran complejidad organizativa y temática.



¿Qué más puedes leer?






All Articles