TEST1 - Añadiendo Pruebas

Iniciando

El paso del tutorial utiliza el add_http solución del proyecto como su punto de partida.

Si en algún momento desea ver la solución a este paso, puede encontrarla en el add_tests solución.

Hasta ahora, hemos estado enfocándonos en implementar una serie de diferentes capacidades del IPF Framework y analizando cómo se pueden combinar para iniciar rápidamente un IPF application. Durante este tiempo, sin embargo, aún no hemos explorado formas de probar nuestra aplicación.

En esta etapa, presentaremos el Test Framework de Icon, y muestre cómo se puede utilizar para probar la aplicación que ha construido. Supondremos en este tutorial que usted tiene un conocimiento básico de lo que el Icon Test Framework es, y una comprensión de ambos BDD y Gherkin sintaxis.

El Icon Test Framework

Conceptos

Comenzaremos nuestra introducción al Test Framework resumiendo algunos conceptos clave:

  • Mensaje: Un modelo de abstracción para cualquier 'mensaje' que sea gestionado por la implementación del marco (solicitud, respuesta, carga útil, etc.). Un mensaje se escribe contra un conocido Java tipo que representa la forma deserializada del contenido, también conocida como Document Type.

  • TipoDeMensaje: Una representación del tipo de mensajes que se puede consultar a través de la BDD, debe haber una relación uno a uno.mapping entre la instancia MessageType y el asociado de un Message Document type.

  • MessageDefinition: Una estructura contextual que proporciona funcionalidad para manejar mensajes del tipo configurado, sirviendo como un punto de Inversion of Control con el marco de pruebas. Debería haber una relación uno a uno.mapping entre el MessageDefinition instancia y tipo de mensaje configurado, y es común ver tanto el mensaje como MessageDefinition como argumentos a core métodos.

  • MessageBucket: Una colección glorificada que contiene cualquier mensaje recibido por el Test Framework(ya sea directamente de los consumidores, o secundario como HTTP se añaden a las respuestas). La colección interna está encapsulada y se proporcionan métodos de acceso basados en predicados para "pescar" mensajes correlacionados del contenedor. Un mensaje "pescado" con éxito se elimina típicamente del bucket y se añade al objeto Context del test.

  • Transporter: Una abstracción de un protocolo sobre el cual se puede enviar un mensaje al sistema objetivo, por ejemplo,HTTP, JMS etc

  • Contexto: Un contexto de escenario que contiene información de prueba y es accesible desde cualquier paso, la estructura de datos interna es local a cada hilo para facilitar la paralelización y se limpia entre escenarios mediante los ganchos del ciclo de vida de JBehave.

No se preocupe si estos conceptos parecen algo abstractos en este momento. Se volverán más claros a medida que avancemos a través de la rest de este tutorial.

Extensiones

En este tutorial, vamos a utilizar un extension del Test Framework que ha sido diseñado para simplificar el proceso de escritura de pruebas para aplicaciones basadas en IPF:

<dependency>
    <groupId>com.iconsolutions.ipf.core.test</groupId>
    <artifactId>ipf-test-fw-whitebox</artifactId>
    <scope>test</scope>
</dependency>

El *ipf-test-fw-caja blanca*extension proporciona una serie de cosas útiles:

  1. Un conjunto de pasos predefinidos que utilizan:

    1. el system events estructura de un IPF application para proporcionar pasos de procesamiento ricos que pueden ser utilizados para la validación

    2. la capacidad de operaciones del modelo para interrogar el agregado de cualquier flujo dado.

  2. Un conjunto de common steps(inicio / fin del escenario)

  3. Un conjunto de transporter utilidades para permitir una fácil configuración de stubbed HTTP, Kafka y JMS servicios.

Utilizará estas funciones a medida que avancemos a través del tutorial.

Configuración del Proyecto

Antes de que podamos comenzar a escribir pruebas, primero necesitamos establecer una ubicación en nuestro proyecto donde las almacenaremos. Esto será un nuevo Maven módulo que usted llamará ipf-tutorial-application-tests. Si está utilizando IntelliJ, puede hacerlo haciendo clic derecho en la raíz {solution-name} módulo de proyecto del que está trabajando (por ejemplo,add_http) en la vista del proyecto y seleccionando Nuevo  Módulo.

A continuación, se le pedirá que añada un nuevo Maven módulo:

add test new module 1

Después de hacer clic en "Siguiente", proporcione al módulo el nombre.ipf-tutorial-application-tests

add test new module 2

Luego presione "Finalizar" para completar la configuración del proyecto. Una vez completado, si expande el módulo en el navegador, puede eliminar el ipf-tutorial-application-tests/src/main directorio ya que solo estaremos añadiendo cosas al test carpeta aquí.

Agregue un nuevo directorio "resources" bajo el test directorio, y marque esto como una raíz de recursos de prueba (haga clic derecho en la carpeta > Marcar directorio como > Raíz de recursos de prueba). Bajo este nuevo directorio de recursos, añadiremos un directorio más llamado "historias".

Cuando hayamos completado todos estos pasos, la estructura de nuestro proyecto debería verse como la siguiente:

add test resource stories

Una primera prueba BDD

Ahora tenemos un módulo para almacenar nuestras pruebas, procedamos a escribir nuestro primer caso de prueba BDD. Para hacer esto, necesitamos crear un archivo de "historia". Creemos un nuevo archivo llamado HappyPath.story y agréguelo al directorio de nuevas historias.

 Hay algunos excelentes complementos disponibles en IntelliJ para ayudar a soportar el desarrollo de casos de prueba BDD.
Le recomendamos utilizar este.https://plugins.jetbrains.com/plugin/7268-jbehave-support[Soporte de JBehave].
Cuando esté instalado, resaltará qué pasos ya han sido implementados y proporcionará la capacidad de hacer clic para ver el código.

Ahora comencemos a completar su archivo de historia:

Meta:

Narrative:
This test covers the basic tutorial happy path flow.

Scenario: Basic Happy Path

Given the IPF service is healthy

Esta es la base de todas las historias que escribiremos para IPF. La primera línea del escenario "Dado que el IPF service "es saludable" es uno de los pasos que utilizaremos para verificar que nuestro IPF application ha comenzado con éxito y está listo para procesar mensajes. Esto verificará que todos los conectores en la aplicación estén activos y funcionando antes de que comencemos una prueba.

 Al ejecutar una prueba, si este paso falla, consulte los registros, ya que le indicarán qué conectores han failed para iniciar con éxito.
Esto se debe normalmente a un error de configuración en su prueba.

Habiendo confirmado que nuestra aplicación está en funcionamiento, necesitamos comenzar a pensar en los diferentes pasos del proceso de pago que hemos construido como parte de las etapas DSL del tutorial. Dado que este es nuestro primer intento de escribir una prueba BDD, solo cubriremos un ejemplo simplificado de este viaje de pago aquí que demuestra el core capacidades del test framework. Para explorar la gama completa de funcionalidades proporcionadas por el test framework, por favor consulte el Icon Test Framework guía.

Iniciando un Pago

Para comenzar, consideremos la primera parte del proceso de pago, que implica la iniciación del pago en sí.

1. El pago se envía a la aplicación

En las etapas del tutorial de DSL, la iniciación de un pago se realizó enviando una solicitud al (HTTP) controlador de iniciación. El test framework aquí actúa como la parte iniciadora, o "canal", y por lo tanto utilizamos el When palabra clave para este paso de BDD:

When the channel sends a 'initiation request' with values:
| requestId | fraud-happy-request-id |
| value     | 25                     |

Definimos el tipo de solicitud que deseamos enviar aquí — una "solicitud de iniciación" — y proporcionamos dos valores para incluir en esta solicitud:

  • a requestId conteniendo la cadena fraud-happy-request-id

  • a value de 25.

El value es fácil de entender. Estamos enviando un valor < 30 para asegurar que seguimos el camino de éxito correcto a través del flujo.

El requestId se utilizará en nuestra prueba para señalar el escenario específico que estamos ejecutando. El test framework es capaz de ejecutar muchas pruebas en paralelo, por lo que necesitamos poder identificar de manera única cada prueba. El requestId servirá para ese propósito aquí. No se preocupe, se volverá más obvio cómo funciona esto más adelante.

2. Se crea un flujo de iniciación.

Después de que el controlador reciba la solicitud de inicio de pago, si todo va bien, se iniciará un nuevo Flujo de Inicio como parte de la lógica de manejo de solicitudes en el controlador. Dado que este comportamiento se encuentra dentro del IPF application estamos probando (y no el test framework), utilizaremos el Then palabra clave para este paso de BDD:

Then a new 'InitiationFlow' flow is started
3. El HTTP la respuesta se devuelve a la llamada iniciadora.

La última parte del proceso de iniciación de pago implica enviar una respuesta de vuelta al canal después de que el Flujo de Iniciación se haya iniciado con éxito. Nuevamente, dado que este comportamiento se encuentra dentro del IPF application, ampliaremos el anterior Then paso y utilice el And palabra clave aquí:

And the channel receives a 'initiation response'

Juntando esto, ahora tenemos un conjunto de pasos que cubren la parte de iniciación de pagos de nuestro recorrido de pagos:

Narrative:
This test covers the basic tutorial happy path flow.

Scenario: Basic Happy Path

Given the IPF service is healthy
When the channel sends a 'initiation request' with values:
| requestId | fraud-happy-request-id |
| value     | 25                     |
Then a new 'InitiationFlow' flow is started
And the channel receives a 'initiation response'

Continuaremos añadiendo a esto a medida que avancemos a través del tutorial, pero por ahora, este pequeño ejemplo representa un escenario de prueba ejecutable, así que procedamos a cómo podemos ejecutarlo.

Implementación de Pruebas (Parte 1)

Hay una serie de aspectos clave que debemos abordar para que nuestras pruebas se ejecuten:

  1. Un "runner" - esta es una clase que proporcionará la spring boot ejecutor de pruebas que ejecutará nuestro archivo de historia.

  2. "Clases de configuración" - estos proporcionan la solicitud/respuesta de prueba message definition s y transportes que especifican cómo el test framework interactúa con el flujo en el IPF application. Para nuestro caso de prueba actual, esto solo cubrirá el "canal", pero a medida que avancemos, también será necesario incluir todos los servicios externos con los que interactúa el flujo durante el proceso de pago.

  3. archivo "Config" - necesitaremos proporcionar la configuración para que el test framework sabe cómo conectarse a la aplicación tutorial.

Vamos a revisar cada uno de estos ahora.

El Corredor

El ejecutor realmente ejecuta nuestras pruebas. Su responsabilidad es determinar todos los archivos de historia disponibles y ejecutar los escenarios dentro de ellos.

Antes de crear este ejecutor, necesitamos agregar algunas dependencias en nuestro ipf-tutorial-application-tests pom.xml:

<dependencies>
    <dependency>
        <groupId>com.iconsolutions.ipf.core.test</groupId>
        <artifactId>ipf-test-fw-whitebox</artifactId>
         <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.iconsolutions.ipf.tutorial</groupId>
        <artifactId>ipf-tutorial-app</artifactId>
        <version>${project.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Como puede ver, el primero es el ipf-test-fw-whitebox la dependencia mencionada anteriormente. La segunda dependencia que incluimos es el módulo de la aplicación tutorial, que nos permite iniciar la aplicación tutorial desde nuestro ejecutor de pruebas.

Ahora configuremos nuestra clase runner como se muestra a continuación:

@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest. WebEnvironment. DEFINED_PORT)
@Import({AllTestConfig.class})
public class FeatureTestRunner extends IPFFeatureTestRunner {
}

y agréguelo a un nuevo com.iconsolutions.ipf.tutorial.test paquete dentro del módulo de prueba de la aplicación.

El corredor:

  • extiende el IPFFeatureTestRunner y importa el AllTestConfig clase de configuración proporcionada por el ipf-test-fw-whitebox dependencia.AllTestConfig habilita una serie de características que utilizaremos para configurar la ejecución de nuestra prueba, y discutiremos estas más adelante.

  • utiliza el Application.class desde el ipf-tutorial-app módulo como base de la spring boot prueba.

Necesitaremos una base de datos Mongo para escribir como parte de la prueba, y vamos a utilizar un contenedor de prueba para proporcionarnos el docker implementación para esto. También necesitamos proporcionar un Kafka contenedor aquí, ya que la aplicación tutorial se conecta a Kafka al iniciar, y utilizaremos esto cuando incluyamos la interacción con el sistema de Sanciones en nuestra prueba más adelante. Aprovechando la funcionalidad lista para usar proporcionada por el test framework, podemos proporcionar estos contenedores muy fácilmente modificando primero nuestra definición de clase para el ejecutor:

public class FeatureTestRunner extends IPFFeatureTestRunner implements KafkaIntegrationTestSupport, MongoIntegrationTestSupport {
}

y luego añadiendo el siguiente bloque estático para activar el Kafka y contenedores de Mongo al inicio de la prueba:

static {
    kafkaContainer.withEnv("KAFKA_CREATE_TOPICS", "SANCTIONS_RESPONSE:1:1, SANCTIONS_REQUEST:1:1, PAYMENT_INITIATION_REQUEST:1:1, PAYMENT_INITIATION_RESPONSE:1:1");
    SingletonContainerPattern.startAllInParallel(mongoContainer, kafkaContainer);
}

Tenga en cuenta que también hemos proporcionado un conjunto de Kafka temas aquí, ya que sin ellos, la verificación de salud fallará (piense en el Given the IPF service is healthy paso que definimos en la prueba). Los relacionados con sanciones se utilizarán cuando añadamos los pasos de BDD para cubrir la interacción entre la aplicación tutorial y el sistema de sanciones más adelante. La solicitud/respuesta de iniciación de pago no se utilizará en esta prueba (ya que estamos iniciando un pago a través del HTTP controlador en nuestra prueba), pero deben estar aquí debido a que incorporamos externos Kafka iniciación de pago enviar/receive connector s como parte de CON1 - Añadiendo la Iniciación de Pagos.

Eso es todo lo que necesitamos para nuestro corredor en este momento, así que pasemos a la clase de configuración.

La Clase de Configuración de Iniciación de Pagos

Como se discutió anteriormente, vamos a iniciar los pagos a través de la HTTP controlador en la aplicación tutorial. En nuestro escenario de prueba BDD, definimos dos mensajes: la "solicitud de iniciación" y la "respuesta de iniciación". Ahora crearemos tipos y definiciones de mensaje para estos mensajes dentro de una clase de configuración de iniciación.

Creemos un nuevo paquete com.iconsolutions.ipf.tutorial.test.config y añada un nuevo InitiationConfig clase. Lo primero que debemos hacer en nuestra configuración de inicio es definir una clase enum interna, que contendrá nuestra message type definiciones e implemente el test framework proporcionado MessageType interfaz:

public class InitiationConfig {

    enum InitiationTypes implements MessageType {

        INITIATION_REQUEST("initiate request"),
        INITIATION_RESPONSE("initiation response");

        private final String name;

        InitiationTypes(String name) {
            this.name = name;
        }

        @Override
        public String getName() {
            return name();
        }

        @Override
        public Set<String> getAliases() {
            return Set.of(name);
        }
    }

}

Lo clave a tener en cuenta en la definición anterior es que los nombres (alias) proporcionados en el constructor de los tipos de enumeración deben coincidir con los nombres proporcionados en su archivo de historia BDD.

Ahora necesitamos crear el message definition s para estos tipos. Comencemos con la "solicitud de iniciación":

@Bean
MessageDefinition<InitiationRequest> initiationRequestMessageDefinition(@Value("${application.base.url}") String baseUrl) {
    return new DefaultMessageDefinition. Builder<InitiationRequest>()
            .withType(InitiationTypes. INITIATION_REQUEST) (1)
            .withDocumentTypeClass(InitiationRequest.class) (2)
            .withGenerator(props -> new InitiationRequest()) (3)
            .withDestination(baseUrl + "/submit") (4)
            .withCorrelatingIdGet(message -> Optional.ofNullable(message.getDocument().getRequestId())) (5)
            .withPreSend(message -> ContextUtils.setClientRequestId(message.getDocument().getRequestId())) (6)
            .build();
}

Analicemos cada parte de esta definición anterior:

1 Aquí estamos vinculando el message definition al tipo al que se aplica esta definición, en este caso, la "solicitud de iniciación".
2 Este método nos permite definir el actual java tipo del message definition, que es el InitiationRequest aquí.
3 A medida que el test framework está actuando como el iniciador del pago, necesita enviar una solicitud de inicio válida a la aplicación tutorial como parte de la prueba. Este método generador indica el test framework que, cuando necesite enviar esta solicitud de iniciación, la construirá utilizando la implementación que se le haya pasado. En este caso simple, solo necesitamos proporcionar un nuevo objeto InitiationRequest al generador (crearemos una definición de generador más completa más adelante cuando stubbing el sistema de sanciones).
4 Para el destino, esto es el HTTP dirección a la que se enviará la solicitud de inicio. Tenga en cuenta que estamos inyectando la ruta de la url utilizando Spring @Value anotación aquí, por lo que necesitaremos incluir el valor para esto en nuestro application.conf archivo (que haremos más adelante en la sección del archivo de configuración a continuación).
5 Aquí definimos una función que indicará el test framework cómo correlacionar esta solicitud con la respuesta de inicio subsiguiente (que crearemos en breve). Como se mencionó anteriormente, el test framework es capaz de ejecutar muchas pruebas en paralelo, con todos los mensajes recibidos por el test framework añadido a un solo " Message Bucket ". Por lo tanto, necesitamos proporcionar al marco una forma de determinar qué pares de solicitud/respuesta están vinculados dentro de cada prueba. El identificador que vamos a utilizar aquí para lograr este requisito es el requestId campo, que está disponible tanto en los objetos de solicitud como de respuesta de iniciación, y contiene el mismo valor en cada caso (como, si recuerda, el requestId en la solicitud se pasa directamente a la respuesta en la lógica de manejo de solicitudes del controlador.
6 En el método de pre-envío, podemos proporcionar cualquier lógica de implementación adicional que consideremos útil antes de que se envíe el mensaje. Típicamente, esto implicará establecer identificadores en el contexto de prueba que estarán disponibles durante la ejecución de la prueba. En nuestro caso, vamos a pasar el requestId desde la solicitud de iniciación hasta el clientRequestId campo en el contexto de la prueba. Lo utilizaremos para fines de correlación en los pasos posteriores de BDD.

Ahora pasemos a definir la "respuesta de iniciación":

@Bean
MessageDefinition<InitiationResponse> initiationResponseMessageDefinition() {
    return new DefaultMessageDefinition. Builder<InitiationResponse>()
            .withType(InitiationTypes. INITIATION_RESPONSE) (1)
            .withDocumentTypeClass(InitiationResponse.class) (2)
            .withCorrelatingIdGet(message -> Optional.ofNullable(message.getDocument().getRequestId())) (3)
            .build();
}

Puede ver que esta definición es mucho más simple que la "solicitud de iniciación", lo cual es resultado del hecho de que el IPF application está generando este objeto en la prueba, en lugar del test framework, y el papel del test framework Aquí solo se afirma que este mensaje fue generado en la prueba.

Nuevamente, revisemos el message definition aquí:

1 Esta vez nuestra definición es para la "respuesta de iniciación" message type.
2 El java tipo de la message definition is InitiationResponse aquí.
3 Como se menciona en la definición de "solicitud de iniciación", estamos utilizando el requestId en la respuesta para correlacionarla con la solicitud correspondiente.

Eso es nuestro message definition se ha hecho. El paso final en nuestra configuración de inicio es definir un transporte; es decir, necesitamos proporcionar el test framework con un mecanismo para realizar una llamada al controlador de iniciación. Para ello, vamos a utilizar otro test framework utilidad, el HttpSenderTestTransporter. Vamos a crear un bean para el transporte como sigue:

@Bean
public MessageTransport initiationTransport(MessageDefinition<InitiationRequest> initiationRequestMessageDefinition, MessageDefinition<InitiationResponse> initiationResponseMessageDefinition) {
    return new HttpSenderTestTransporter. Builder<InitiationRequest, InitiationResponse>()
            .withIdentifier("initiation") (1)
            .withRequestMessageDefinition(initiationRequestMessageDefinition) (2)
            .withResponseMessageDefinition(initiationResponseMessageDefinition) (3)
            .build();
}

En esta definición simple, estamos construyendo un nuevo HttpSenderTestTransporter instancia y proporcionando:

1 un identificador único para el transporte (si tenemos múltiples transportes de remitente, cada uno necesitará un id único - Puede dejar esto en blanco ya que solo estamos creando uno aquí, pero para rastrear cualquier problema, proporcionar un nombre siempre es una buena idea.
2 acceso a la solicitud message definition configuramos anteriormente.
3 acceso a la respuesta message definition configuramos anteriormente.

El transporte extraerá toda la otra información que requiera de la proporcionada.message definition s!

Eso es todo lo que se ha hecho ahora para nuestra clase de configuración de iniciación de pagos, así que pasemos a lo siguiente.

Archivo de configuración

Lo siguiente que necesitamos hacer es crear un archivo de configuración en nuestra carpeta de recursos de prueba y proporcionar un valor para el application.base.url que definimos en la solicitud de inicio message definition anteriormente. Por defecto, la aplicación tutorial escucha en el puerto 8080, así que vamos a crear un nuevo application.conf archivar bajo src/test/resources y añada la siguiente línea:

application.base.url="http://localhost:8080"

Ejecutando nuestra Prueba

Ahora estamos casi listos para ejecutar nuestra prueba. Antes de poder hacer esto, primero necesitamos añadir el junit-vintage-engine dependencia a nuestro módulo. Esto es necesario porque jBehave utiliza JUnit4 en lugar de JUnit5, y típicamente, para spring boot Los proyectos, las pruebas se escriben utilizando JUnit5, lo que significa que este es el motor que se incluye en el classpath por defecto:

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
</dependency>

A continuación, a medida que la aplicación envía processing data a la operational data store over HTTP, necesitaremos proporcionar un mock punto final que manejará estas solicitudes en nuestra prueba. Usted hará esto añadiendo un simple consumidor de wiremock que siempre devolverá un 200 cuando sea llamado por la aplicación.

Agregue la dependencia requerida al pom.xml:

<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock-standalone</artifactId>
</dependency>

y luego cree un DummyODSConsumer clase con la siguiente definición y agréguela a la com.iconsolutions.ipf.tutorial.test.config paquete:

@Configuration
public class DummyODSConsumer {

    @Bean
    public WireMockServer odsMock() {
        WireMockServer wireMockServer = new WireMockServer(
                new WireMockConfiguration()
                        .port(8093)
                        .needClientAuth(true)
        );

        wireMockServer.start();

        wireMockServer.stubFor(WireMock.post(WireMock.urlEqualTo("/ipf-processing-data"))
                .willReturn(WireMock.aResponse()
                        .withStatus(200)));

        return wireMockServer;
    }

}

Finalmente, añadimos nuestra nueva clase de configuración de iniciación y el DummyODSConsumer clase como importaciones en nuestro ejecutor

@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest. WebEnvironment. DEFINED_PORT)
@Import({AllTestConfig.class, InitiationConfig.class, DummyODSConsumer.class})
public class FeatureTestRunner extends IPFFeatureTestRunner implements KafkaIntegrationTestSupport, MongoIntegrationTestSupport {..}

Ahora podemos ejecutar nuestra prueba haciendo clic derecho en nuestro FeatureTestRunner clase y haciendo clic en ejecutar.

Después de ejecutar, la salida debería verse algo así:

add test payment init test run output

¡Tenemos una prueba en ejecución que está pasando! Ahora proceda a agregar los pasos de ejecución de pago a nuestro escenario de prueba.

Ejecutando un Pago

4. El flujo de iniciación crea un nuevo flujo Ipftutorialflow V2

Después de que se haya iniciado el Flujo de Inicio, inmediatamente comienza un nuevo flujo Ipftutorialflow V2 (Flujo de Ejecución). Como antes, porque este comportamiento se encuentra dentro del IPF application, ampliaremos el anterior Then paso y utilice el And palabra clave de nuevo aquí:

And a new 'Ipftutorialflow V2' flow is started
5. El ipftutorialv2 flow calls el sistema de sanciones

Después de una serie de etapas sin operación (verificación de duplicados, validación de cuentas), el flujo de Ejecución envía una solicitud al sistema de sanciones. A medida que el test framework está actuando como el sistema de sanciones aquí (es decir, está proporcionando un sistema de sanciones stub), el paso BDD declara que el sistema de sanciones recibe la solicitud. Nuevamente utilizamos el And la palabra clave aquí, ya que el envío de la solicitud se realiza mediante la aplicación tutorial:

And Sanctions receives a 'sanctions request'

Al igual que con la Solicitud de Inicio, estamos definiendo el message type de la solicitud el test framework está esperando recibir aquí, lo que es una "solicitud de sanciones"

6. El sistema de sanciones devuelve una respuesta

El test framework envía una respuesta de sanciones de vuelta a la aplicación tutorial después de recibir la solicitud, y por lo tanto utilizamos el When palabra clave para esta definición de paso BDD:

When Sanctions sends a 'sanctions response'

Aquí estamos definiendo el message type de la respuesta que test framework tendrá que construir y enviar de vuelta a la aplicación tutorial, que es una "respuesta a sanciones"

7. Si el valor del pago < 30, se realiza una llamada al sistema de fraude

Bajo las condiciones adecuadas (valor < 30), el flujo de ejecución envía una solicitud al sistema de fraude. El test framework espera recibir una "solicitud de fraude" message type, y, dado que la aplicación tutorial está enviando la solicitud aquí, utilizamos el Then palabra clave nuevamente en nuestra definición de paso:

Then Fraud receives a 'fraud request'
8. El sistema de fraude devuelve una respuesta

El test framework construirá y enviará una "respuesta de fraude" message type regreso a la aplicación del tutorial después de recibir la solicitud, y así que nuevamente utilizamos el When palabra clave aquí:

When Fraud sends a 'fraud response'
9. El flujo Ipftutorialflow V2 se completa

Después de la etapa de limpieza y liquidación sin operación, el Flujo de Ejecución se mueve al terminal.Complete state. Este comportamiento se encuentra dentro de la IPF application, y por lo tanto utilizamos el Then palabra clave en esta definición de paso:

Then the 'Ipftutorialflow V2' flow is in state 'Complete'
10. El flujo de iniciación se completa.

Después de que el Flujo de Ejecución se complete, devuelve inmediatamente el control al Flujo de Iniciación, que también se completa. Como antes, extendemos el anterior Then paso, y utilice el And palabra clave aquí:

And the 'InitiationFlow' flow is in state 'Complete'

¡Juntando todo esto, ahora tenemos nuestra primera prueba completa de BDD!

Narrative:
This test covers the basic tutorial happy path flow.

Scenario: Basic Happy Path

Given the IPF service is healthy
When the channel sends a 'initiation request' with values:
| requestId | fraud-happy-request-id |
| value     | 25                     |
Then a new 'InitiationFlow' flow is started
And the channel receives a 'initiation response'
And a new 'IpftutorialflowV2' flow is started
And Sanctions receives a 'sanctions request'
When Sanctions sends a 'sanctions response'
Then Fraud receives a 'fraud request'
When Fraud sends a 'fraud response'
Then the 'IpftutorialflowV2' flow is in state 'Complete'
And the 'InitiationFlow' flow is in state 'Complete'

Antes de que podamos ejecutarlo, necesitaremos agregar toda la configuración relevante de la misma manera en que lo hicimos anteriormente. Así que procedamos a hacerlo.

Implementación de Pruebas (Parte 2)

La Clase de Configuración del Sistema de Sanciones

Para la configuración del sistema de sanciones, los pasos son esencialmente los mismos que seguimos para la configuración de la iniciación de pagos, a saber:

  1. Cree el message type s

  2. Cree las definiciones para la solicitud y la respuesta.

  3. Defina el transporte.

La única diferencia esta vez es que, en lugar de utilizar un HttpSenderTransport, utilizaremos un KafkaMessageTransport. Para esto, necesitaremos agregar otra dependencia a nuestro pom.xml:

<dependency>
    <groupId>com.iconsolutions.ipf.core.test</groupId>
    <artifactId>ipf-test-fw-connectors</artifactId>
</dependency>

Ahora, comencemos creando un nuevo SanctionsConfig clase en el com.iconsolutions.ipf.tutorial.test.config paquete. Como antes, primero necesitaremos agregar el message type s para la "solicitud de sanciones" y "respuesta a sanciones":

public class SanctionsConfig {

    enum SanctionsTypes implements MessageType {

        SANCTIONS_REQUEST("sanctions request"),
        SANCTIONS_RESPONSE("sanctions response");

        private final String name;

        SanctionsTypes(String name) {
            this.name = name;
        }

        @Override
        public String getName() {
            return name();
        }

        @Override
        public Set<String> getAliases() {
            return Set.of(name);
        }
    }

}

Ahora pensemos en nuestra "solicitud de sanciones" message definition. El IPF application envía esta solicitud y el test framework lo recibe. A medida que el test framework consumirá la solicitud de un Kafka tema, necesitaremos proporcionar un mecanismo para convertir la cadena serializada sobre el tema en el actual SanctionsRequest objeto. Veamos cómo se ve la definición de la solicitud cuando juntamos todo esto:

@Bean
MessageDefinition<SanctionsRequest> receiveSanctionsRequest() {
    return new DefaultMessageDefinition. Builder<SanctionsRequest>()
            .withType(SanctionsTypes. SANCTIONS_REQUEST) (1)
            .withDocumentTypeClass(SanctionsRequest.class) (2)
            .withSource("SANCTIONS_REQUEST") (3)
            .withFromStringMapper(serialisedString -> SerializationHelper.stringToObject(serialisedString, SanctionsRequest.class)) (4)
            .withCorrelatingIdGet(message -> Optional.ofNullable(ContextUtils.getClientRequestId())) (5)
            .build();
}
1 Aquí nuevamente definimos nuestro message type.
2 Y el java objeto.
3 El campo "source" representa el Kafka tema del que vamos a leer. Tenga en cuenta que se llama "fuente" aquí como message definition s son independientes del protocolo, y este campo también podría ser utilizado para definir un JMS cola como la fuente de este mensaje.
4 El fromStringMapper define cómo vamos a convertir de la versión de cadena serializada de nuestro mensaje a la correspondiente java clase. En nuestro caso, vamos a utilizar un predefinido stringToObject función que es proporcionada por el Icon SerializationHelper y realizará una simple Jackson mapping de la cadena.
5 Nuevamente, utilizamos esta función para indicar el test framework cómo correlacionar esta solicitud con el respuesta a sanciones subsecuentes (que crearemos a continuación). Estamos utilizando el clientRequestId del contexto de prueba que establecimos como parte de la solicitud de iniciación de pago message definition anteriormente, ya que este identificador es específico de nuestra ejecución de prueba individual y, por lo tanto, puede ser utilizado para recuperar los mensajes que han sido generados por nuestra prueba desde el message bucket.

A continuación se presenta nuestra definición de respuesta. Similar a la solicitud de iniciación de pago, la clave aquí es que necesitaremos proporcionar una implementación de función generadora para construir la respuesta de sanciones que se envía por el test framework a la aplicación tutorial en la prueba.

@Bean
MessageDefinition<SanctionsResponse> sendSanctionsResponse() {
    return new DefaultMessageDefinition. Builder<SanctionsResponse>()
            .withType(SanctionsTypes. SANCTIONS_RESPONSE)
            .withDocumentTypeClass(SanctionsResponse.class)
            .withDestination("SANCTIONS_RESPONSE")
            .withGenerator(props -> {
                SanctionsResponse sanctionsResponse = new SanctionsResponse(); (1)
                sanctionsResponse.setHeader(HeaderUtils.makeHeader("Sanctions", ContextUtils.getClientRequestId())); (2)
                sanctionsResponse.getHeader().getTechnical().setOriginalEventId(((SanctionsRequest) PreviousMessages.getLastMessage(SanctionsTypes. SANCTIONS_REQUEST, false).getDocument()).getHeader().getTechnical().getEventId()); (3)
                sanctionsResponse.setPayload(new SanctionsResponsePayload()); (4)
                return sanctionsResponse;
            })
            .withCorrelatingIdGet(doc -> Optional.ofNullable(ContextUtils.getClientRequestId()))
            .build();
}
1 El primer paso en la construcción de la implementación del generador implica crear un nuevo SanctionsResponse utilizando el constructor predeterminado sin argumentos.
2 Establecemos el encabezado en la respuesta utilizando los sistemas de muestra proporcionados.HeaderUtils clase, pasando el contexto de la prueba clientRequestId como un transactionId.
3 Luego establecemos el originalEventId en el Technical objeto dentro del SanctionsResponse Header. Utilizamos la capacidad "PreviousMessages" del test framework, que nos permite recuperar el mensaje de solicitud de sanciones creado anteriormente, para pasar el Header. Technical. EventId desde la solicitud de sanciones a la originalEventId aquí. Si recuerda, en CON2 - Escribiendo su propio conector (Kafka), almacenamos el Header. Technical. EventId desde la solicitud de sanciones en el almacén de correlación como parte de la sanctionsSendConnector definición para que pudiéramos correlacionar la respuesta asíncrona subsiguiente con la solicitud. Luego correlacionamos la respuesta como parte de la sanctionsReceiveConnector definición utilizando el Header. Technical. OriginalEventId valor, que es igual a la Header. Technical. EventId sobre la solicitud de sanciones. Por lo tanto, estamos replicando ese comportamiento aquí y asegurando que la etapa de correlación en el sanctionsReceiveConnector¡no falla en la prueba! (Es hora de un café después de leer todo eso, creo).
4 El paso final en la implementación del generador es crear un nuevo SanctionsResponsePayload utilizando el constructor predeterminado sin argumentos y añada esto al SanctionsResponse.

Una cosa más que mencionar aquí es que utilizamos el destination campo para definir a qué tema vamos a producir la respuesta (en este caso, "SANCTIONS_RESPONSE"). Nuevamente, como message definition s son independientes del protocolo, si estuviéramos utilizando un JMS transporte para la prueba, proporcionaríamos un JMS nombre de la cola a este campo.

Finalmente, nuevamente necesitamos considerar cómo se implementará el transporte de prueba. Como se mencionó anteriormente, vamos a utilizar el proporcionado KafkaTestTransporter:

@Bean
public MessageTransport sanctionsKafkaTransport(MessageDefinition<SanctionsRequest> sanctionsRequestMessageDefinition,
MessageDefinition<SanctionsResponse> sanctionsResponseMessageDefinition,
ClassicActorSystemProvider actorSystem) {
    return new KafkaTestTransporter. Builder<SanctionsRequest,SanctionsResponse>()
            .withIdentifier("sanctions") (1)
            .withPropertiesPath("sanctions") (2)
            .withReceiveMessageDefinition(sanctionsRequestMessageDefinition) (3)
            .withSendMessageDefinition(sanctionsResponseMessageDefinition) (4)
            .withActorSystem(actorSystem) (5)
            .build();
}

Examinemos las partes clave de esto:

1 Nuevamente, proporcionamos un identificador único para el transporte
2 Vamos a recuperar nuestra configuración para el Kafka transporte utilizando la configuración bajo la ruta "sanciones" en el ipf-tutorial-app módulo application.conf archivo
3 Aquí pasamos nuestra recepción message definition, que desde el test framework la perspectiva de, es la solicitud message definition
4 Aquí pasamos nuestro envío message definition, que desde el test framework la perspectiva de, es la respuesta message definition
5 Finalmente, luego pasamos el sistema de actores

Eso es todo lo relacionado con la configuración de nuestro sistema de sanciones, así que pasemos al sistema de fraude.

La Clase de Configuración del Sistema de Fraude

Como antes, comenzaremos creando un FraudConfig clase en el com.iconsolutions.ipf.tutorial.test.config paquete y luego añada el fraude message type s y definiciones según sea necesario.

Para la "respuesta al fraude" message definition, podemos utilizar una implementación de generador similar a la utilizada para la "respuesta a sanciones", pero no debemos preocuparnos por la correlación de envío/recepción aquí, ya que el sistema de fraude se comunica de manera sincrónica (a través de HTTP).

.withGenerator(params -> {
    var fraudResponse = new OlafResponse();
    fraudResponse.setHeader(HeaderUtils.makeHeader("Fraud", ContextUtils.getClientRequestId()));
    fraudResponse.setPayload(new FraudFeedbackPayload(new FraudFeedback(new FraudFeedback. PaymentStatusChanged("FraudFeedbackOK", "0"), null)));
    return fraudResponse;
    })
}

Con esta implementación del generador proporcionada, vea si ahora puede crear el message type y definiciones en el FraudConfig Clasifíquese. Cuando esté listo, compare su intento con la solución proporcionada a continuación:

public class FraudConfig {

    @Bean
    MessageDefinition<OlafResponse> fraudResponseMessageDefinition() {
        return new DefaultMessageDefinition. Builder<OlafResponse>()
                .withType(FraudTypes. FRAUD_RESPONSE)
                .withCausedByType(FraudTypes. FRAUD_REQUEST)
                .withDocumentTypeClass(OlafResponse.class)
                .withCorrelatingIdGet(response -> Optional.of(ContextUtils.getClientRequestId()))
                .withGenerator(params -> {
                    var fraudResponse = new OlafResponse();
                    fraudResponse.setHeader(HeaderUtils.makeHeader("Fraud", ContextUtils.getClientRequestId()));
                    fraudResponse.setPayload(new FraudFeedbackPayload(new FraudFeedback(new FraudFeedback. PaymentStatusChanged("FraudFeedbackOK", "0"), null)));
                    return fraudResponse;
                })
                .build();
    }

    @Bean
    MessageDefinition<OlafRequest> fraudRequestMessageDefinition() {
        return new DefaultMessageDefinition. Builder<OlafRequest>()
                .withType(FraudTypes. FRAUD_REQUEST)
                .withDocumentTypeClass(OlafRequest.class)
                .withFromStringMapper(serialisedString -> SerializationHelper.stringToObject(serialisedString, OlafRequest.class))
                .withCorrelatingIdGet(fraudRequest -> Optional.of(ContextUtils.getClientRequestId()))
                .build();
    }

    enum FraudTypes implements MessageType {

        FRAUD_REQUEST("fraud request"),
        FRAUD_RESPONSE("fraud response");

        private final String name;

        FraudTypes(String name) {
            this.name = name;
        }

        @Override
        public String getName() {
            return name();
        }

        @Override
        public Set<String> getAliases() {
            return Set.of(name);
        }
    }

}

El paso final en la configuración es, nuevamente, definir el transporte relevante para manejar esta solicitud/respuesta. Aunque vamos a utilizar un HTTP message transport de nuevo, a diferencia del caso de iniciación de pago, el test framework está recibiendo una solicitud de la aplicación tutorial y enviando una respuesta en este escenario. Por lo tanto, necesitamos utilizar un "consumidor" HTTP transporte aquí (en lugar del "remitente" utilizado anteriormente):

@Bean
public MessageTransport fraudTransport(@Value("${fraud.http.client.port}") String port,
                                       MessageDefinition<OlafRequest> fraudRequestMessageDefinition,
                                       MessageDefinition<OlafResponse> fraudResponseMessageDefinition,
                                       ClassicActorSystemProvider actorSystem) {
    return new HttpConsumerTestTransporter. Builder()
            .withIdentifier("fraud")
            .withPort(Integer.parseInt(port)) (1)
            .withOperation(new HttpOperation. Builder<>("v1", fraudRequestMessageDefinition, fraudResponseMessageDefinition).withHttpMethod(HttpMethod. POST).build()) (2)
            .withActorSystem(actorSystem)
            .build();
}

La implementación del transporte aquí es muy similar a los otros transportes de prueba que hemos realizado anteriormente. Las principales diferencias son:

1 Necesitamos definir el puerto en el que el transporte estará escuchando. En este caso, estamos inyectando el puerto utilizando Spring @Value anotación y referencia al valor de configuración definido en el ipf-tutorial-app módulo application.conf archivo
2 Necesitamos configurar un HttpOperation para el transporte. Para construir el HttpOperation, utilizamos el constructor y especificamos estos cuatro parámetros:
  • El punto final/URL

  • El tipo de método que aceptará (que será POST en este caso)

  • La solicitud message type

  • La respuesta message type.

Eso es todo, nuestra configuración del sistema de fraude ya está completa.

Ejecutando Nuestra Prueba (Nuevamente)

Ahora estamos casi listos para ejecutar nuestra prueba nuevamente. Como antes, necesitamos primero agregar nuestros nuevos archivos de configuración del sistema de sanciones y fraude al ejecutor:

@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest. WebEnvironment. DEFINED_PORT)
@Import({AllTestConfig.class, FraudConfig.class, SanctionsConfig.class, InitiationConfig.class, DummyODSConsumer.class})
public class FeatureTestRunner extends IPFFeatureTestRunner implements KafkaIntegrationTestSupport, MongoIntegrationTestSupport {..}

¡Y eso es todo! Intente ejecutar la prueba nuevamente haciendo clic derecho en la clase FeatureTestRunner y seleccionando ejecutar.

Debería ver algo como esto después de que la prueba haya finalizado:

add test entire bdd test run

Afirmando Valores

Todas las verificaciones hasta este punto han sido sobre flujos que se inician, solicitudes enviadas y respuestas recibidas. También podemos afirmar el contenido de las solicitudes y respuestas. Por ejemplo, podemos mejorar la solicitud de sanciones recibidas verificando sus valores:

And Sanctions receives a 'sanctions request' with values:
| payload.filteringRequest.amount.currency | US |

Puede continuar desarrollando estas verificaciones y afirmaciones según su IPF flow y mensajes que se están intercambiando.

Ejecutando Su Prueba en Maven

Finalmente, si deseamos ejecutar nuestras pruebas como parte de la Maven Para construir, necesitamos añadir un plugin de construcción adicional para asegurar que se ejecuten. Para hacer esto, necesitamos añadir el Maven plugin de seguridad para nuestro pom.xml como sigue:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <includes>
                        <include>**/*Runner.java</include>
                    </includes>
                    <excludes>
                        <exclude>**/*Test.java</exclude>
                        <exclude>**/*Tests.java</exclude>
                        <exclude>**/*InProgressRunner.java</exclude>
                        <exclude>**/*RepeatRunner.java</exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

Ejercicio

Como ejercicio, intente agregar pasos en la historia del camino feliz para las siguientes etapas sin operación en el viaje de pago que no incluimos en nuestro BDD, que son:

  • la verificación de duplicados ha sido superada

  • la validación de la cuenta ha sido exitosa

  • claro y resuelto pasado

Algunos consejos:

  • Necesitará utilizar el “a {event name} event is raised” step name format. Think about whether this is a ` Entonces ` or ` Cuando` tipo de paso considerando la perspectiva del test framework.

  • Dado que estas etapas no envían/reciben ningún mensaje, no será necesario crear ninguna configuración de mensajes adicional aquí.

Cuando esté listo, puede comparar su solución con la definición de la historia en el add_tests solución.

Conclusiones

En esta sección, hemos configurado y ejecutado un BDD básico.integration test usando el Test Framework de Icon.