Saltar a contenido

Pruebas con nf-test

Traducción asistida por IA - más información y sugerencias

Poder verificar sistemáticamente que cada parte de su workflow hace lo que se supone que debe hacer es fundamental para la reproducibilidad y el mantenimiento a largo plazo, y puede ser de gran ayuda durante el proceso de desarrollo.

Tomemos un momento para hablar sobre por qué las pruebas son tan importantes. Si está desarrollando un workflow, una de las primeras cosas que hará es obtener algunos datos de prueba que sabe que son válidos y deberían producir un resultado. Agrega el primer proceso al pipeline y lo conecta a sus entradas para que funcione. Luego, para verificar que todo funciona, lo ejecuta con los datos de prueba. Suponiendo que funciona, pasa al siguiente proceso y ejecuta los datos de prueba nuevamente. Repite este proceso hasta tener un pipeline con el que esté satisfecho.

Luego, quizás agrega un parámetro simple de verdadero o falso como --skip_process. Ahora debe ejecutar el pipeline dos veces, una con cada parámetro para asegurarse de que funciona como se espera. Pero espere, ¿cómo verificamos si --skip_process realmente omite el proceso? ¡Tenemos que revisar las salidas o verificar los archivos de registro! Esto es tedioso y propenso a errores.

A medida que desarrolla su pipeline, rápidamente se volverá tan complejo que probar manualmente cada iteración es lento y propenso a errores. Además, si encuentra un error, será muy difícil determinar exactamente de dónde proviene en su pipeline. Aquí es donde entran las pruebas.

Las pruebas le permiten verificar sistemáticamente que cada parte de su pipeline funciona como se espera. Los beneficios para un desarrollador de pruebas bien escritas son enormes:

  • Confianza: Debido a que las pruebas cubren todo el pipeline, puede estar seguro de que cambiar algo no afecta nada más
  • Confiabilidad: Cuando múltiples desarrolladores trabajan en el pipeline, saben que los otros desarrolladores no han roto el pipeline ni ninguno de sus componentes.
  • Transparencia: Las pruebas muestran dónde falla un pipeline y facilitan la identificación del problema. También funcionan como una forma de documentación, mostrando cómo ejecutar un proceso o workflow.
  • Velocidad: Debido a que las pruebas son automatizadas, pueden ejecutarse muy rápidamente y de forma repetida. Puede iterar rápidamente con menos temor de introducir nuevos errores.

Hay muchos tipos diferentes de pruebas que podemos escribir:

  1. Pruebas a nivel de módulo: Para procesos individuales
  2. Pruebas a nivel de workflow: Para un único workflow
  3. Pruebas a nivel de pipeline: Para el pipeline en su conjunto
  4. Pruebas de rendimiento: Para la velocidad y eficiencia del pipeline
  5. Pruebas de estrés: Para evaluar el rendimiento del pipeline bajo condiciones extremas y determinar sus límites

Probar procesos individuales es análogo a las pruebas unitarias en otros lenguajes. Probar el workflow o el pipeline completo es análogo a lo que se llama pruebas de integración en otros lenguajes, donde probamos las interacciones de los componentes.

nf-test es una herramienta que le permite escribir pruebas a nivel de módulo, workflow y pipeline. En resumen, le permite verificar sistemáticamente que cada parte individual del pipeline funciona como se espera, de forma aislada.

Objetivos de aprendizaje

En esta misión secundaria, aprenderá a usar nf-test para escribir una prueba a nivel de workflow para el pipeline, así como pruebas a nivel de módulo para los tres procesos que invoca.

Al finalizar esta misión secundaria, podrá usar las siguientes técnicas de manera efectiva:

  • Inicializar nf-test en su proyecto
  • Generar pruebas a nivel de módulo y a nivel de workflow
  • Agregar tipos comunes de aserciones
  • Entender cuándo usar snapshots vs. aserciones de contenido
  • Ejecutar pruebas para un proyecto completo

Estas habilidades le ayudarán a implementar una estrategia de pruebas integral en sus proyectos de pipeline, asegurando que sean más robustos y mantenibles.

Requisitos previos

Antes de abordar esta misión secundaria, debe:

  • Haber completado el tutorial Hello Nextflow o un curso equivalente para principiantes.
  • Estar familiarizado con los conceptos y mecanismos básicos de Nextflow (procesos, canales, operadores, trabajo con archivos, metadatos)

0. Primeros pasos

Abra el codespace de capacitación

Si aún no lo ha hecho, asegúrese de abrir el entorno de capacitación como se describe en la Configuración del entorno.

Open in GitHub Codespaces

Muévase al directorio del proyecto

Vamos a movernos al directorio donde se encuentran los archivos de este tutorial.

cd side-quests/nf-test

Puede configurar VSCode para que se enfoque en este directorio:

code .

Revise los materiales

Encontrará un archivo de workflow principal y un archivo CSV llamado greetings.csv que contiene la entrada al pipeline.

Directory contents
.
├── greetings.csv
└── main.nf

Para una descripción detallada de los archivos, consulte el calentamiento de Hello Nextflow.

El workflow que probaremos es un subconjunto del workflow Hello construido en Hello Workflow.

¿Qué hace el workflow Hello Nextflow?

Si no ha realizado la capacitación de Hello Nextflow, aquí tiene una descripción general de lo que hace este sencillo workflow.

El workflow toma un archivo CSV que contiene saludos, ejecuta cuatro pasos de transformación consecutivos sobre ellos y genera un único archivo de texto que contiene una imagen ASCII de un personaje divertido diciendo los saludos.

Los cuatro pasos están implementados como procesos de Nextflow (sayHello, convertToUpper, collectGreetings y cowpy) almacenados en archivos de módulo separados.

  1. sayHello: Escribe cada saludo en su propio archivo de salida (por ejemplo, "Hello-output.txt")
  2. convertToUpper: Convierte cada saludo a mayúsculas (por ejemplo, "HELLO")
  3. collectGreetings: Recopila todos los saludos en mayúsculas en un único archivo por lotes
  4. cowpy: Genera arte ASCII usando la herramienta cowpy

Los resultados se publican en un directorio llamado results/, y la salida final del pipeline (cuando se ejecuta con los parámetros predeterminados) es un archivo de texto plano que contiene arte ASCII de un personaje diciendo los saludos en mayúsculas.

En esta misión secundaria, usamos una forma intermedia del workflow Hello que solo contiene los primeros dos procesos.

El subconjunto con el que trabajaremos está compuesto por dos procesos: sayHello y convertToUpper. Puede ver el código completo del workflow a continuación.

Código del flujo de trabajo
main.nf
/*
* Parámetros del pipeline
*/
params.input_file = "greetings.csv"

/*
* Usa echo para imprimir 'Hello World!' en la salida estándar
*/
process sayHello {

    publishDir 'results', mode: 'copy'

    input:
        val greeting

    output:
        path "${greeting}-output.txt"

    script:
    """
    echo '$greeting' > '$greeting-output.txt'
    """
}

/*
* Usa una utilidad de reemplazo de texto para convertir el saludo a mayúsculas
*/
process convertToUpper {

    publishDir 'results', mode: 'copy'

    input:
        path input_file

    output:
        path "UPPER-${input_file}"

    script:
    """
    cat '$input_file' | tr '[a-z]' '[A-Z]' > UPPER-${input_file}
    """
}

workflow {

    // crea un canal para las entradas desde un archivo CSV
    greeting_ch = channel.fromPath(params.input_file).splitCsv().flatten()

    // emite un saludo
    sayHello(greeting_ch)

    // convierte el saludo a mayúsculas
    convertToUpper(sayHello.out)
}

Ejecute el workflow

Vamos a ejecutar el workflow para asegurarnos de que funciona como se espera.

nextflow run main.nf
Result of running the workflow
 N E X T F L O W   ~  version 24.10.2

Launching `main.nf` [soggy_linnaeus] DSL2 - revision: bbf79d5c31

executor >  local (6)
[f7/c3be66] sayHello (3)       | 3 of 3 ✔
[cd/e15303] convertToUpper (3) | 3 of 3 ✔

¡FELICIDADES! ¡Acaba de ejecutar una prueba!

"¿Espere, qué? ¡Acabo de ejecutar el workflow y funcionó! ¿Cómo es eso una prueba?"

¡Buena pregunta!

Analicemos lo que acaba de ocurrir.

Ejecutó el workflow con los parámetros predeterminados, confirmó que funcionó y está satisfecho con los resultados. Esta es la esencia de las pruebas. Si trabajó en el curso de capacitación Hello Nextflow, habrá notado que siempre comenzamos cada sección ejecutando el workflow que usábamos como punto de partida, para confirmar que todo está configurado correctamente.

Las pruebas de software esencialmente realizan este proceso por nosotros.

Revise la tarea

Su desafío es agregar pruebas estandarizadas a este workflow usando nf-test, con el fin de facilitar la verificación de que cada parte continúa funcionando como se espera en caso de que se realicen más cambios.

Lista de verificación de preparación

¿Cree que está listo para comenzar?

  • Entiendo el objetivo de este curso y sus requisitos previos
  • Mi codespace está en funcionamiento
  • He configurado mi directorio de trabajo correctamente
  • He ejecutado el workflow exitosamente
  • Entiendo la tarea

Si puede marcar todas las casillas, está listo para continuar.


1. Inicializar nf-test

El paquete nf-test proporciona un comando de inicialización que configura algunas cosas para que podamos comenzar a desarrollar pruebas para nuestro proyecto.

nf-test init

Esto debería producir la siguiente salida:

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr

Project configured. Configuration is stored in nf-test.config

También crea un directorio tests que contiene un archivo de configuración base.

1.1. Generar un nf-test

nf-test viene con un conjunto de herramientas para construir archivos nf-test, lo que nos ahorra la mayor parte del trabajo. Estas se encuentran bajo el subcomando generate. Vamos a generar una prueba para el pipeline:

nf-test generate pipeline main.nf
Output
> nf-test generate pipeline main.nf

Load source file '/workspaces/training/side-quests/nf-test/main.nf'
Wrote pipeline test file '/workspaces/training/side-quests/nf-test/tests/main.nf.test

SUCCESS: Generated 1 test files.

Esto creará un archivo main.nf.test dentro del directorio tests. Este es nuestro archivo de prueba a nivel de pipeline. Si ejecuta tree tests/ debería ver algo como esto:

Test directory contents
tests/
├── main.nf.test
└── nextflow.config

El archivo main.nf.test es nuestro archivo de prueba a nivel de pipeline. Vamos a abrirlo y examinar su contenido.

tests/main.nf.test
nextflow_pipeline {

    name "Test Workflow main.nf"
    script "main.nf"

    test("Should run without failures") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
        }

        then {
            assert workflow.success
        }

    }

}

Tomemos un momento para entender la estructura del archivo de prueba.

El bloque nextflow_pipeline es el punto de entrada para todas las pruebas a nivel de pipeline. Contiene lo siguiente:

  • name: El nombre de la prueba.
  • script: La ruta al script del pipeline.

El bloque test es la prueba en sí. Contiene lo siguiente:

  • when: Las condiciones bajo las cuales se debe ejecutar la prueba. Esto incluye los parámetros que se usarán para ejecutar el pipeline.
  • then: Las aserciones que se deben realizar. Esto incluye los resultados esperados del pipeline.

En términos simples, la lógica de la prueba se lee de la siguiente manera: "Cuando se proporcionan estos parámetros a este pipeline, entonces esperamos ver estos resultados."

Esta no es una prueba funcional; demostraremos cómo convertirla en una en la siguiente sección.

Una nota sobre los nombres de las pruebas

En el ejemplo anterior, usamos el nombre predeterminado "Should run without failures" que es apropiado para una prueba básica que solo verifica si el pipeline se ejecuta exitosamente. Sin embargo, a medida que agregamos casos de prueba más específicos, debemos usar nombres más descriptivos que indiquen qué estamos probando realmente. Por ejemplo:

  • "Should convert input to uppercase" - al probar funcionalidad específica
  • "Should handle empty input gracefully" - al probar casos límite
  • "Should respect max memory parameter" - al probar restricciones de recursos
  • "Should create expected output files" - al probar la generación de archivos

Los buenos nombres de prueba deben:

  1. Comenzar con "Should" para dejar claro cuál es el comportamiento esperado
  2. Describir la funcionalidad o el escenario específico que se está probando
  3. Ser lo suficientemente claros para que, si la prueba falla, sepa qué funcionalidad está rota

A medida que agreguemos más aserciones y casos de prueba específicos más adelante, usaremos estos nombres más descriptivos para dejar claro qué está verificando cada prueba.

1.2. Ejecutar la prueba

Vamos a ejecutar la prueba para ver qué sucede.

nf-test test tests/main.nf.test
nf-test pipeline fail
> nf-test test tests/main.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Workflow main.nf

  Test [693ba951] 'Should run without failures' FAILED (4.652s)

  Assertion failed:

  assert workflow.success
         |        |
         workflow false

  Nextflow stdout:

  ERROR ~ No such file or directory: /workspaces/training/side-quests/nf-test/.nf-test/tests/693ba951a20fec36a5a9292ed1cc8a9f/greetings.csv

   -- Check '/workspaces/training/side-quests/nf-test/.nf-test/tests/693ba951a20fec36a5a9292ed1cc8a9f/meta/nextflow.log' file for details
  Nextflow stderr:

FAILURE: Executed 1 tests in 4.679s (1 failed)

¡La prueba falla! ¿Qué ocurrió?

  1. nf-test intentó ejecutar el pipeline tal como está, usando la configuración del bloque when:
tests/main.nf.test
when {
    params {
        // define parameters here. Example:
        // outdir = "tests/results"
    }
}
  1. nf-test verificó el estado del pipeline y lo comparó con el bloque when:
tests/main.nf.test
then {
    assert workflow.success
}

Observe cómo nf-test ha reportado que el pipeline falló y proporcionó el mensaje de error de Nextflow:

Error
ERROR ~ No such file or directory: /workspaces/training/side-quests/nf-test/.nf-test/tests/693ba951a20fec36a5a9292ed1cc8a9f/greetings.csv

¿Cuál fue el problema? Recuerde que el pipeline tiene un archivo greetings.csv en el directorio del proyecto. Cuando nf-test ejecuta el pipeline, buscará este archivo, pero no puede encontrarlo. El archivo está ahí, ¿qué está pasando? Bueno, si miramos la ruta podemos ver que la prueba ocurre en la ruta ./nf-test/tests/longHashString/. Al igual que Nextflow, nf-test crea un nuevo directorio para cada prueba para mantener todo aislado. El archivo de datos no está ubicado allí, por lo que debemos corregir la ruta al archivo en la prueba original.

Volvamos al archivo de prueba y cambiemos la ruta al archivo en el bloque when.

Quizás se pregunte cómo vamos a apuntar a la raíz del pipeline en la prueba. Dado que esta es una situación común, nf-test tiene un conjunto de variables globales que podemos usar para facilitarnos la vida. Puede encontrar la lista completa aquí pero por ahora usaremos la variable projectDir, que representa la raíz del proyecto del pipeline.

tests/main.nf.test
1
2
3
4
5
when {
    params {
        input_file = "${projectDir}/greetings.csv"
    }
}
tests/main.nf.test
1
2
3
4
5
6
when {
    params {
        // define parameters here. Example:
        // outdir = "tests/results"
    }
}

Vamos a ejecutar la prueba nuevamente para ver si funciona.

nf-test pipeline pass
nf-test test tests/main.nf.test
Pipeline passes
> nf-test test tests/main.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Workflow main.nf

  Test [1d4aaf12] 'Should run without failures' PASSED (1.619s)


SUCCESS: Executed 1 tests in 1.626s

¡Éxito! El pipeline se ejecuta exitosamente y la prueba pasa. ¡Ejecútelo tantas veces como quiera y siempre obtendrá el mismo resultado!

Por defecto, la salida de Nextflow está oculta, pero para convencerse de que nf-test definitivamente está ejecutando el workflow, puede usar el indicador --verbose:

nf-test test tests/main.nf.test --verbose
Pipeline runs all processes
> nf-test test tests/main.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Workflow main.nf

  Test [693ba951] 'Should run without failures'
    > Nextflow 24.10.4 is available - Please consider updating your version to it
    > N E X T F L O W  ~  version 24.10.0
    > Launching `/workspaces/training/side-quests/nf-test/main.nf` [zen_ampere] DSL2 - revision: bbf79d5c31
    > [2b/61e453] Submitted process > sayHello (2)
    > [31/4e1606] Submitted process > sayHello (1)
    > [bb/5209ee] Submitted process > sayHello (3)
    > [83/83db6f] Submitted process > convertToUpper (2)
    > [9b/3428b1] Submitted process > convertToUpper (1)
    > [ca/0ba51b] Submitted process > convertToUpper (3)
    PASSED (5.206s)


SUCCESS: Executed 1 tests in 5.239s

1.3. Agregar aserciones

Una verificación simple es asegurarse de que nuestro pipeline esté ejecutando todos los procesos que esperamos y que no omita ninguno silenciosamente. Recuerde que nuestro pipeline ejecuta 6 procesos, uno llamado sayHello y uno llamado convertToUpper para cada uno de los 3 saludos.

Vamos a agregar una aserción a nuestra prueba para verificar que el pipeline ejecuta el número esperado de procesos. También actualizaremos el nombre de nuestra prueba para reflejar mejor lo que estamos probando.

tests/main.nf.test
    test("Should run successfully with correct number of processes") {

        when {
            params {
                input_file = "${projectDir}/greetings.csv"
            }
        }

        then {
            assert workflow.success
            assert workflow.trace.tasks().size() == 6
        }

    }
tests/main.nf.test
    test("Should run without failures") {

        when {
            params {
                input_file = "${projectDir}/greetings.csv"
            }
        }

        then {
            assert workflow.success
        }

    }

El nombre de la prueba ahora refleja mejor lo que realmente estamos verificando: no solo que el pipeline se ejecuta sin fallar, sino que ejecuta el número esperado de procesos.

Vamos a ejecutar la prueba nuevamente para ver si funciona.

nf-test pipeline pass
nf-test test tests/main.nf.test
Pipeline passes with assertions
🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Workflow main.nf

  Test [1d4aaf12] 'Should run successfully with correct number of processes' PASSED (1.567s)


SUCCESS: Executed 1 tests in 1.588s

¡Éxito! El pipeline se ejecuta exitosamente y la prueba pasa. Ahora hemos comenzado a probar los detalles del pipeline, además del estado general.

1.4. Probar la salida

Vamos a agregar una aserción a nuestra prueba para verificar que el archivo de salida fue creado. Lo agregaremos como una prueba separada, con un nombre informativo, para que los resultados sean más fáciles de interpretar.

tests/main.nf.test
    test("Should run successfully with correct number of processes") {

        when {
            params {
                input_file = "${projectDir}/greetings.csv"
            }
        }

        then {
            assert workflow.success
            assert workflow.trace.tasks().size() == 6
        }

    }

    test("Should produce correct output files") {

        when {
            params {
                input_file = "${projectDir}/greetings.csv"
            }
        }

        then {
            assert file("$launchDir/results/Bonjour-output.txt").exists()
            assert file("$launchDir/results/Hello-output.txt").exists()
            assert file("$launchDir/results/Holà-output.txt").exists()
            assert file("$launchDir/results/UPPER-Bonjour-output.txt").exists()
            assert file("$launchDir/results/UPPER-Hello-output.txt").exists()
            assert file("$launchDir/results/UPPER-Holà-output.txt").exists()
        }

    }
tests/main.nf.test
    test("Should run successfully with correct number of processes") {

        when {
            params {
                input_file = "${projectDir}/greetings.csv"
            }
        }

        then {
            assert workflow.success
            assert workflow.trace.tasks().size() == 6
        }

    }

Ejecute la prueba nuevamente para ver si funciona.

nf-test pipeline pass
nf-test test tests/main.nf.test
Pipeline passes with file assertions
> nf-test test tests/main.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Workflow main.nf

  Test [f0e08a68] 'Should run successfully with correct number of processes' PASSED (8.144s)
  Test [d7e32a32] 'Should produce correct output files' PASSED (6.994s)


SUCCESS: Executed 2 tests in 15.165s

¡Éxito! Las pruebas pasan porque el pipeline se completó exitosamente, el número correcto de procesos se ejecutó y los archivos de salida fueron creados. Esto también debería mostrarle cuán útil es proporcionar esos nombres informativos para sus pruebas.

Esto es solo la superficie; podemos seguir escribiendo aserciones para verificar los detalles del pipeline, pero por ahora avancemos a probar los componentes internos del pipeline.

Conclusión

Sabe cómo escribir un nf-test para un pipeline.

¿Qué sigue?

Aprenda cómo probar un proceso de Nextflow.


2. Probar un proceso de Nextflow

No tenemos que escribir pruebas para cada parte del pipeline, pero cuantas más pruebas tengamos, más exhaustivos podemos ser sobre el pipeline y más seguros podemos estar de que funciona como se espera. En esta sección vamos a probar ambos procesos del pipeline como unidades individuales.

2.1. Probar el proceso sayHello

Comencemos con el proceso sayHello.

Usemos el comando nf-test generate nuevamente para generar pruebas para el proceso.

nf-test generate process main.nf
Output
> nf-test generate process main.nf

Load source file '/workspaces/training/side-quests/nf-test/main.nf'
Wrote process test file '/workspaces/training/side-quests/nf-test/tests/main.sayhello.nf.test
Wrote process test file '/workspaces/training/side-quests/nf-test/tests/main.converttoupper.nf.test

SUCCESS: Generated 2 test files.

Por ahora centrémonos en el proceso sayhello en el archivo main.sayhello.nf.test.

Vamos a abrir el archivo y examinar su contenido.

tests/main.sayhello.nf.test
nextflow_process {

    name "Test Process sayHello"
    script "main.nf"
    process "sayHello"

    test("Should run without failures") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
            process {
                """
                // define inputs of the process here. Example:
                // input[0] = file("test-file.txt")
                """
            }
        }

        then {
            assert process.success
            assert snapshot(process.out).match()
        }

    }

}

Como antes, comenzamos con los detalles de la prueba, seguidos de los bloques when y then. Sin embargo, también tenemos un bloque process adicional que nos permite definir las entradas al proceso.

Vamos a ejecutar la prueba para ver si funciona.

nf-test pipeline pass
nf-test test tests/main.sayhello.nf.test
Process test fails
> nf-test test tests/main.sayhello.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Process sayHello

  Test [1eaad118] 'Should run without failures' FAILED (4.876s)

  Assertion failed:

  assert process.success
         |       |
         |       false
         sayHello

  Nextflow stdout:

  Process `sayHello` declares 1 input but was called with 0 arguments
  Nextflow stderr:

FAILURE: Executed 1 tests in 4.884s (1 failed)

La prueba falla porque el proceso sayHello declara 1 entrada pero fue llamado con 0 argumentos. Vamos a corregir eso agregando una entrada al proceso. Recuerde de Hello Workflow (y la sección de calentamiento anterior) que nuestro proceso sayHello toma una única entrada de valor, que necesitaremos proporcionar. También debemos corregir el nombre de la prueba para reflejar mejor lo que estamos probando.

tests/main.sayhello.nf.test
    test("Should run without failures and produce correct output") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
            process {
                """
                input[0] = "hello"
                """
            }
        }

        then {
            assert process.success
            assert snapshot(process.out).match()
        }

    }
tests/main.sayhello.nf.test
    test("Should run without failures") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
            process {
                """
                // define inputs of the process here. Example:
                // input[0] = file("test-file.txt")
                """
            }
        }

        then {
            assert process.success
            assert snapshot(process.out).match()
        }

    }

Vamos a ejecutar la prueba nuevamente para ver si funciona.

nf-test pipeline pass
> nf-test test tests/main.sayhello.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Process sayHello

  Test [f91a1bcd] 'Should run without failures and produce correct output' PASSED (1.604s)
  Snapshots:
    1 created [Should run without failures and produce correct output]


Snapshot Summary:
  1 created

SUCCESS: Executed 1 tests in 1.611s

¡Éxito! La prueba pasa porque el proceso sayHello se ejecutó exitosamente y la salida fue creada.

2.2. Examine el snapshot creado por la prueba

Si miramos el archivo tests/main.sayhello.nf.test, podemos ver que usa un método snapshot() en el bloque de aserciones:

tests/main.sayhello.nf.test
assert snapshot(process.out).match()

Esto le indica a nf-test que cree un snapshot de la salida del proceso sayHello. Vamos a examinar el contenido del archivo de snapshot.

Snapshot file contents
code tests/main.sayhello.nf.test.snap

No lo imprimiremos aquí, pero debería ver un archivo JSON que contiene detalles del proceso y sus salidas. En particular, podemos ver una línea que se ve así:

Snapshot file contents
"0": [
    "hello-output.txt:md5,b1946ac92492d2347c6235b4d2611184"
]

Esto representa las salidas creadas por el proceso sayHello, que estamos probando explícitamente. Si volvemos a ejecutar la prueba, el programa verificará que la nueva salida coincida con la salida que se registró originalmente. Esta es una forma rápida y sencilla de probar que las salidas del proceso no cambian, razón por la cual nf-test la proporciona como opción predeterminada.

Advertencia

¡Eso significa que debemos asegurarnos de que la salida que registramos en la ejecución original sea correcta!

Si, en el transcurso del desarrollo futuro, algo en el código cambia y hace que la salida sea diferente, la prueba fallará y tendremos que determinar si el cambio es esperado o no.

  • Si resulta que algo en el código se rompió, tendremos que corregirlo, con la expectativa de que el código corregido pase la prueba.
  • Si es un cambio esperado (por ejemplo, la herramienta ha sido mejorada y los resultados son mejores), entonces necesitaremos actualizar el snapshot para aceptar la nueva salida como referencia. nf-test tiene un parámetro --update-snapshot para este propósito.

Podemos ejecutar la prueba nuevamente y ver que debería pasar:

nf-test process pass with snapshot
> nf-test test tests/main.sayhello.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Process sayHello

  Test [f91a1bcd] 'Should run without failures and produce correct output' PASSED (1.675s)


SUCCESS: Executed 1 tests in 1.685s

¡Éxito! La prueba pasa porque el proceso sayHello se ejecutó exitosamente y la salida coincidió con el snapshot.

2.3. Alternativa a los snapshots: aserciones de contenido directo

Si bien los snapshots son excelentes para detectar cualquier cambio en la salida, a veces se desea verificar contenido específico sin ser tan estricto sobre que todo el archivo coincida. Por ejemplo:

  • Cuando partes de la salida pueden cambiar (marcas de tiempo, IDs aleatorios, etc.) pero cierto contenido clave debe estar presente
  • Cuando se desea verificar patrones o valores específicos en la salida
  • Cuando se desea hacer la prueba más explícita sobre qué constituye el éxito

Así es como podríamos modificar nuestra prueba para verificar contenido específico:

tests/main.sayhello.nf.test
    test("Should run without failures and contain expected greeting") {

        when {
            params {
                // define parameters here
            }
            process {
                """
                input[0] = "hello"
                """
            }
        }

        then {
            assert process.success
            assert path(process.out[0][0]).readLines().contains('hello')
            assert !path(process.out[0][0]).readLines().contains('HELLO')
        }

    }
tests/main.sayhello.nf.test
    test("Should run without failures and produce correct output") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
            process {
                """
                input[0] = "hello"
                """
            }
        }

        then {
            assert process.success
            assert snapshot(process.out).match()
        }

    }

Tenga en cuenta que nf-test ve las salidas del proceso como una lista de listas, por lo que process.out[0][0] está obteniendo la primera parte del primer elemento del canal (o 'emisión') de este proceso.

Este enfoque:

  • Deja claro exactamente qué esperamos en la salida
  • Es más resistente a cambios irrelevantes en la salida
  • Proporciona mejores mensajes de error cuando las pruebas fallan
  • Permite validaciones más complejas (patrones regex, comparaciones numéricas, etc.)

Vamos a ejecutar la prueba para ver si funciona.

nf-test pipeline pass
nf-test test tests/main.sayhello.nf.test
Process test fails
> nf-test test tests/main.sayhello.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Process sayHello

  Test [58df4e4b] 'Should run without failures and contain expected greeting' PASSED (7.196s)


SUCCESS: Executed 1 tests in 7.208s

2.4. Probar el proceso convertToUpper

Vamos a abrir el archivo tests/main.converttoupper.nf.test y examinar su contenido:

tests/main.converttoupper.nf.test
nextflow_process {

    name "Test Process convertToUpper"
    script "main.nf"
    process "convertToUpper"

    test("Should run without failures") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
            process {
                """
                // define inputs of the process here. Example:
                // input[0] = file("test-file.txt")
                """
            }
        }

        then {
            assert process.success
            assert snapshot(process.out).match()
        }

    }

}

Esta es una prueba similar a la del proceso sayHello, pero está probando el proceso convertToUpper. Sabemos que esta fallará porque, al igual que con sayHello, el proceso convertToUpper toma una única entrada de ruta, pero no hemos especificado ninguna.

Ahora necesitamos proporcionar un único archivo de entrada al proceso convertToUpper, que incluya algún texto que queramos convertir a mayúsculas. Hay muchas formas de hacer esto:

  • Podríamos crear un archivo dedicado para la prueba
  • Podríamos reutilizar el archivo data/greetings.csv existente
  • Podríamos crearlo sobre la marcha dentro de la prueba

Por ahora, reutilicemos el archivo data/greetings.csv existente usando el ejemplo que usamos con la prueba a nivel de pipeline. Como antes, podemos nombrar la prueba para reflejar mejor lo que estamos probando, pero esta vez dejemos que 'capture' el contenido en lugar de verificar cadenas específicas (como hicimos en el otro proceso).

tests/main.converttoupper.nf.test
    test("Should run without failures and produce correct output") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
            process {
                """
                input[0] = "${projectDir}/greetings.csv"
                """
            }
        }

        then {
            assert process.success
            assert snapshot(process.out).match()
        }

    }
tests/main.converttoupper.nf.test
    test("Should run without failures") {

        when {
            params {
                // define parameters here. Example:
                // outdir = "tests/results"
            }
            process {
                """
                // define inputs of the process here. Example:
                // input[0] = file("test-file.txt")
                """
            }
        }

        then {
            assert process.success
            assert snapshot(process.out).match()
        }

    }

¡Y ejecute la prueba!

nf-test pipeline pass
nf-test test tests/main.converttoupper.nf.test
nf-test process convertToUpper pass
> nf-test test tests/main.converttoupper.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Process convertToUpper

  Test [c59b6044] 'Should run without failures and produce correct output' PASSED (1.755s)
  Snapshots:
    1 created [Should run without failures and produce correct output]


Snapshot Summary:
  1 created

SUCCESS: Executed 1 tests in 1.764s

Tenga en cuenta que hemos creado un archivo de snapshot para el proceso convertToUpper en tests/main.converttoupper.nf.test.snap. Si ejecutamos la prueba nuevamente, deberíamos ver que nf-test pasa de nuevo.

nf-test process convertToUpper pass
nf-test test tests/main.converttoupper.nf.test
nf-test process convertToUpper pass
> nf-test test tests/main.converttoupper.nf.test

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Process convertToUpper

  Test [c59b6044] 'Should run without failures and produce correct output' PASSED (1.798s)


SUCCESS: Executed 1 tests in 1.811s

Conclusión

Sabe cómo escribir pruebas para un proceso de Nextflow y ejecutarlas.

¿Qué sigue?

¡Aprenda cómo ejecutar todas las pruebas a la vez!

3. Ejecutar pruebas para todo el repositorio

Ejecutar nf-test en cada componente está bien, pero es laborioso y propenso a errores. ¿No podemos simplemente probar todo a la vez?

¡Sí podemos!

Vamos a ejecutar nf-test en todo el repositorio.

3.1. Ejecutar nf-test en todo el repositorio

Podemos ejecutar nf-test en todo el repositorio ejecutando el comando nf-test test.

nf-test test .

Tenga en cuenta que simplemente usamos . para ejecutar todo desde nuestro directorio actual. ¡Esto incluirá cada prueba!

nf-test repo pass
> nf-test test .

🚀 nf-test 0.9.3
https://www.nf-test.com
(c) 2021 - 2024 Lukas Forer and Sebastian Schoenherr


Test Process convertToUpper

  Test [3d26d9af] 'Should run without failures and produce correct output' PASSED (4.155s)

Test Workflow main.nf

  Test [f183df37] 'Should run successfully with correct number of processes' PASSED (3.33s)
  Test [d7e32a32] 'Should produce correct output files' PASSED (3.102s)

Test Process sayHello

  Test [58df4e4b] 'Should run without failures and contain expected greeting' PASSED (2.614s)


SUCCESS: Executed 4 tests in 13.481s

¡Mire eso! Ejecutamos 4 pruebas, 1 para cada proceso y 2 para todo el pipeline con un solo comando. ¡Imagine lo poderoso que es esto en una base de código grande!


Resumen

En esta misión secundaria, ha aprendido a aprovechar las funcionalidades de nf-test para crear y ejecutar pruebas para procesos individuales, así como pruebas de extremo a extremo para todo el pipeline. Ahora conoce los dos enfoques principales para la validación de salidas, los snapshots y las aserciones de contenido directo, y cuándo usar cada uno. También sabe cómo ejecutar pruebas una por una o para un proyecto completo.

Aplicar estas técnicas en su propio trabajo le permitirá asegurarse de que:

  • Su código funciona como se espera
  • Los cambios no rompen la funcionalidad existente
  • Otros desarrolladores pueden contribuir con confianza
  • Los problemas pueden identificarse y corregirse rápidamente
  • El contenido de la salida cumple con las expectativas

Patrones clave

  1. Pruebas a nivel de pipeline:
  2. Prueba básica de éxito
  3. Verificación del número de procesos
  4. Verificación de la existencia de archivos de salida
  5. Pruebas a nivel de proceso
  6. Dos enfoques para la validación de salidas:
  7. Uso de snapshots para la verificación completa de la salida
  8. Uso de aserciones de contenido directo para verificaciones de contenido específico
  9. Ejecución de todas las pruebas en un repositorio con un solo comando

Recursos adicionales

Consulte la documentación de nf-test para conocer funcionalidades de prueba más avanzadas y mejores prácticas. Es posible que desee:

  • Agregar aserciones más completas a sus pruebas
  • Escribir pruebas para casos límite y condiciones de error
  • Configurar integración continua para ejecutar pruebas automáticamente
  • Aprender sobre otros tipos de pruebas como pruebas de workflow y de módulo
  • Explorar técnicas de validación de contenido más avanzadas

Recuerde: Las pruebas son documentación viva de cómo debe comportarse su código. Cuantas más pruebas escriba, y cuanto más específicas sean sus aserciones, más seguro podrá estar de la confiabilidad de su pipeline.


¿Qué sigue?

Regrese al menú de misiones secundarias o haga clic en el botón en la parte inferior derecha de la página para continuar con el siguiente tema de la lista.