Automatización de pruebas con Karate II

Automatización de pruebas con Karate II

Patricia Mateo Vega

QE Tester

22 de marzo de 2022

¿Qué vamos a ver?

Karate, un framework opensource de testing que, gracias a su sencillez y expresividad, facilita el desarrollo de pruebas automáticas de APIs de una manera rápida, clara y mantenible.

Como vimos en el anterior artículo Karate presenta innumerables funcionalidades que pueden ser muy útiles a la hora de su implementación.

En este artículo se desarrollarán tres de éstas.

Utilización de clases Java

Karate proporciona la posibilidad de utilizar clases Java para utilidades complejas o funciones de ayuda que facilita su depuración y mantenibilidad. Gracias a esta interoperabilidad con Java se puede incluir llamadas a bases de datos que es de gran ayuda a la hora de comprobar que el estado del sistema es el esperado.

Caso de uso

Para poner esta funcionalidad en práctica se usará el caso de uso de obtención de la fecha actual un año anterior.

Para ello se definirá una clase de utilidades de fecha en código Java en la que se insertará un método que reste a una fecha un año.

public class DateUtil {

   private static final Logger logger = LoggerFactory.getLogger(DateUtil.class);

   public static String getDate(){
       SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

       String dateString = format.format(new Date());
       logger.debug("date: {}", dateString);
       return dateString;
   }

   public static String getSubtractedYear(){
       DateTimeFormatter format = DateTimeFormatter.ofPattern("uuuu-MM-dd");
       LocalDate localDate = LocalDate.parse(getDate(), format);
       localDate = localDate.minusMonths(12);
       String dateString =  localDate.format(format);
       logger.debug("One year ago is: {}", dateString);
       return dateString;
   }
}

Una vez que se tiene la clase Java, se crea la feature desde la que se hará la llamada a ésta.

Feature: timestamp Java example

  Background:
  * def dateUtil = Java.type('reqres_in_karate.utils.DateUtil')

  Scenario: Capture a year old date
     * def oneYearAgo = dateUtil.getSubtractedYear()
     * match oneYearAgo != null

Se puede observar que bastaría con definir una variable con la expresión de Karate “Java.type” y el path de la clase y usarlo dentro del escenario que sea necesario.

Como resultado de esta prueba, gracias a la inserción de logs dentro de la clase, se comprueba que la fecha es correcta:

10:22:08.580 [main] DEBUG reqres_in_karate.utils.DateUtil - Actual date: 2020-09-11
10:22:08.585 [main] DEBUG reqres_in_karate.utils.DateUtil - One year ago is: 2019-09-11

Configuración de entornos

Proporciona soporte para cambiar fácilmente la configuración en diferentes entornos.

Ejemplo de uso

Bastaría con crear un archivo karate-config-envName.js por cada entorno en el que insertar todos las variables que vayamos a necesitar y hacer referencia a la ubicación de este archivo en la clase que se vaya a ejecutar, en el runner:

package examples;

import com.intuit.karate.KarateOptions;
import com.intuit.karate.junit4.Karate;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;

@RunWith(Karate.class)
@KarateOptions(features = "classpath:examples/timestampJavaENV.feature")
public class ExamplesRunnerEnv {
    @BeforeClass
    public static void beforeClass() {
        System.setProperty("karate.config.dir", "examples/environmentConfig");
    }
}

En el archivo karate-config-envName.js podemos definir no sólo variables de entorno, sino también clases java u otros features que se vayan a utilizar.

function fn() {
 var env = karate.environment; // get system property 'karate.karate.environment'
 karate.log('env:', env);
 var config = { apiUrl: 'https://dev.reqres.in/api/users' };
 config.myJavaUtils = Java.type('demo_karate.utils.DateUtil');
 karate.log('config:', config);
 return config;
}

Habría dos posibilidades a la hora de ejecutar estas pruebas por entorno y sería crear “runners” distintos para ello o utilizar dentro de los features la anotación con la variable del entorno y en ese caso sólo se ejecutarán los escenarios que estén marcados con el entorno deseado.

Feature: timestamp with config file

  Scenario: timestamp
     * def getDate = myJavaUtils.getDate()
     * print getDate
     * match getDate != null


  Scenario: Capture a year old date
  * def oneYearAgo = myJavaUtils.getSubtractedYear()
  * print oneYearAgo
  * match oneYearAgo != null

  @preprod
  Scenario: check environment preprod
    * match apiUrl == 'https://preprod.reqres.in/api/users'

  @dev
  Scenario: check environment dev
    * match apiUrl == 'https://dev.reqres.in/api/users'

Para cambiar de entorno habría que establecer una propiedad del sistema Java. Ésto se logra desde línea de comando con la siguiente instrucción:

mvn test -Dkarate.options="--tags @dev" -DargLine="-Dkarate.env=dev" -Dtest=ExamplesRunnerEnv

En el ejemplo mostrado, ejecutando el anterior comando, se lanzan sólo 3 escenarios dentro de la feature. Los que no tienen anotación y el que está anotado con el @dev; el @preprod sería ignorado.

Este tipo de soluciones hace que las features sean cada vez más cortas y legibles.

Ejecución en paralelo

Karate ofrece la posibilidad de ejecutar las pruebas en paralelo reduciendo drásticamente el tiempo de ejecución. Esta característica es propia de Karate, no depende de JUnit, Maven o Gradle.

Este tipo de ejecución te permite seleccionar fácilmente features o anotaciones para componer conjuntos de pruebas de una manera muy flexible.

Al ejecutarse se genera un objeto “Results” con el que verificar si algún escenario falló y que resume los errores en caso de haberlos.

Esta funcionalidad va con el añadido de la generación de los informes tanto JUnit como Cucumber.

Se puede introducir tanto con JUnit 4 como JUnit 5. En este caso se desarrollará el ejemplo con el primero.

package examples.parallelRunner;

import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import org.junit.Test;

import static org.junit.Assert.assertTrue;


public class ExamplesRunnerParallel {

   @Test
   public void testParallel() {
       System.setProperty("karate.env", "demo");
       Results results = Runner.path("classpath:examples/features").tags("~@ignore").parallel(5);                    assertTrue(results.getErrorMessages(), results.getFailCount() == 0);
   }

Los modificadores del Runner que se utilizan son:

  • path: especifica la localización de la feature o conjunto de éstas que se desean ejecutar. En caso de ser más de uno se colocan separados por comas.
  • tags: para seleccionar las anotaciones. Para omitir alguna, bastará con colocar el operador “~” delante de la anotación que no se desea ejecutar.
  • parallel: en este caso se le pasa como parámetro el número de hilos mediante los cuales se quiere ejecutar en paralelo.

Para lanzar las pruebas se usa el siguiente comando:

mvn test -Dtest=ExamplesRunnerParallel

Y como resultado se obtiene el siguiente log:

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.846 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

En ejemplo dado se reduce el tiempo de ejecución casi a la mitad con respecto a la ejecución secuencial.

Repositorio de código

Podéis encontrar y descargaros el código utilizado en este repositorio.

Conclusiones

En esta segunda parte del artículo se han descrito nuevas funcionalidades de Karate mediante ejemplos con código y se ha podido comprobar que aun siendo su simplicidad su principal característica tiene la versatilidad como pilar fundamental logrando construir con el mínimo esfuerzo un proyecto de tests funcionales que ponga a prueba cualquier API.

Patricia Mateo Vega

QE Tester