Herramientas del oficio

Las pruebas automatizadas son, básicamente, código que arrojará o causará un error hay un problema. La mayoría de las bibliotecas o frameworks de prueba ofrecen que facilitan la escritura de las pruebas.

Como se mencionó en la sección anterior, estas primitivas casi siempre incluyen un para definir pruebas independientes (denominadas casos de prueba) y proporcionar aserciones. Las aserciones son una forma de combinar la verificación de un resultado y arrojar una si algo anda mal, y puede considerarse la primitiva básica de todos de prueba de primitivas.

En esta página, se describe un enfoque general para estas primitivas. El marco de trabajo que elegiste probablemente tenga algo así, pero no es una referencia exacta.

Por ejemplo:

import { fibonacci, catalan } from '../src/math.js';
import { assert, test, suite } from 'a-made-up-testing-library';

suite('math tests', () => {
  test('fibonacci function', () => {
    // check expected fibonacci numbers against our known actual values
    // with an explanation if the values don't match
    assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
    assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
  });
  test('relationship between sequences', () => {
    // catalan numbers are greater than fibonacci numbers (but not equal)
    assert.isAbove(catalan(4), fibonacci(4));
  });
  test('bugfix: check bug #4141', () => {
    assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
  })
});

En este ejemplo, se crea un grupo de pruebas (a veces llamado paquete) llamado “matemática test” y define tres casos de prueba independientes en los que cada uno ejecuta algunas aserciones. Estos casos de prueba generalmente se pueden abordar o ejecutar de manera individual, por ejemplo, con un de filtro en el ejecutor de pruebas.

Asistentes de aserción como primitivos

La mayoría de los frameworks de prueba, incluido Vitest, incluyen una colección de aserciones auxiliares en un objeto assert que te permiten verificar rápidamente los valores que se devuelven o otros estados en contra de ciertas expectativas. Esa expectativa suele ser de “bueno conocimiento” de salida. En el ejemplo anterior, sabemos que el número de Fibonacci número 13 debería ser 233, de modo que podamos confirmarlo directamente con assert.equal.

También puedes tener la expectativa de que un valor toma cierta forma, o es mayor que otro valor o tiene alguna otra propiedad. En este curso, no abarcan la gama completa de posibles asistentes de aserción, pero los frameworks de prueba proporciona siempre al menos las siguientes verificaciones básicas:

  • Una "verdad" de Google, que se suele describir como "aceptable" Verificar que una condición sea verdadera que coincida con la forma en que podrías escribir un if que verifique si algo funciona correctamente o correcto Se suele proporcionar como assert(...) o assert.ok(...). recibe un solo valor y un comentario opcional.

  • Una verificación de igualdad, como en el ejemplo de prueba matemática, en la que esperas que el valor que se devuelve o el estado de un objeto para que sea igual a un valor bueno conocido. Son para igualdad primitiva (por ejemplo, para números y cadenas) o igualdad referencial (son el mismo objeto). Bajo la superficie, estas son solo una "verdad" verificar con una comparación de == o ===

    • JavaScript distingue entre igualdad parcial (==) y estricta (===). La mayoría de las bibliotecas de prueba te proporcionan los métodos assert.equal y assert.strictEqual, respectivamente.
  • Comprobaciones profundas de igualdad, que extienden las verificaciones de igualdad para incluir la comprobación del contenidos de objetos, arrays y otros tipos de datos más complejos, así como el la lógica interna para recorrer objetos y compararlos. Son importantes porque JavaScript no tiene una forma integrada de comparar el contenido de dos objetos o arrays. Por ejemplo, [1,2,3] == [1,2,3] siempre es falso. Realiza pruebas los frameworks suelen incluir asistentes de deepEqual o deepStrictEqual.

Asistentes de aserción que comparan dos valores (en lugar de solo una verificación de “verdad”) normalmente tomar dos o tres argumentos:

  • Es el valor real, tal como se generó a partir del código bajo prueba o que describe la el estado que se validará.
  • El valor esperado, generalmente hard-coded (por ejemplo, un número literal o cadena).
  • Un comentario opcional que describe lo que se esperaba o lo que podría haber fallado. que se incluirá si falla la línea.

También es una práctica bastante común combinar aserciones para construir una variedad de ya que es raro que se confirme correctamente el estado de tu sistema por su cuenta. Por ejemplo:

  test('JWT parse', () => {
    const json = decodeJwt('eyJieSI6InNhbXRob3Ii');

    assert.ok(json.payload.admin, 'user should be admin');
    assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
    assert.equal(json.header.alg, 'RS265')
    assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
  });

Vitest usa la biblioteca de aserciones de Chai a nivel interno para brindar ayuda. Puede ser útil revisar su referencia para ver qué aserciones y asistentes podrían ser más adecuados para tu código.

Aserciones fluidas y de BDD

Algunos desarrolladores prefieren un estilo de aserción que se base en el comportamiento. (BDD) o Estilo fluido aserciones. También se denominan “expectativos” auxiliares, porque el punto de entrada a verificar expectativas es un método llamado expect().

Los asistentes esperados se comportan de la misma manera que las aserciones escritas como un método simple. llamadas como assert.ok o assert.strictDeepEquals, pero para algunos desarrolladores es más fácil de leer. Una aserción de BDD puede tener el siguiente aspecto:

// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);

// or a simpler form
expect(result).toBe('array').toContainEqual(42);

// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');

Este estilo de aserciones funciona debido a una técnica llamada encadenamiento de métodos en el que el objeto que muestra expect se puede encadenar continuamente con llamadas de método adicionales. Algunas partes de la llamada, como to.be y that.does ejemplo anterior, no tienen función y solo se incluyen para realizar la llamada más fáciles de leer y, potencialmente, generar un comentario automático si la prueba falló. (En particular, expect normalmente no admite un comentario opcional porque el encadenamiento debe describir la falla claramente).

Muchos frameworks de prueba son compatibles con las aserciones regulares y Fluent/BDD. Vitest por ejemplo, exporta Chai se acerca y tiene su propio enfoque un poco más conciso para el BDD. Jest por otro lado, solo incluye un espera método de forma predeterminada.

Agrupar pruebas en todos los archivos

Cuando escribimos pruebas, tendemos a proporcionar agrupaciones implícitas, en lugar de que todas las pruebas en un archivo, es común escribir pruebas en varios archivos. De hecho, los ejecutores de pruebas solo suelen saber que un archivo es de prueba de un filtro predefinido o una expresión regular (vitest, por ejemplo, incluye todas archivos en tu proyecto que terminan con una extensión, como “.test.jsx” o “.spec.ts” (".test" y ".spec" más una serie de extensiones válidas).

Las pruebas de componentes suelen ubicarse en un archivo similar al del componente que se está probando como en la siguiente estructura de directorios:

Una lista de archivos en un
  incluidos UserList.tsx y UserList.test.tsx.
Un archivo de componentes y un archivo de prueba relacionado.

Del mismo modo, las pruebas de unidades tienden a colocarse junto al código que se está probando. Las pruebas de extremo a extremo pueden estar cada una en su propio archivo, y las pruebas de integración pueden incluso colocarse en sus propias carpetas únicas. Estas estructuras pueden ser útiles cuando los casos de prueba complejos crecen y requieren sus propios archivos de asistencia que no son de prueba, como bibliotecas de soporte necesarias solo para una prueba.

Agrupar pruebas en archivos

Como se utilizó en los ejemplos anteriores, es común colocar pruebas dentro de una llamada a suite(), que agrupa las pruebas que configuraste con test(). Por lo general, las suites no son pruebas por sí mismas, pero ayudan a proporcionar estructura agrupando pruebas relacionadas o objetivos llamando al método pasado. Para test(), el método pasado describe las acciones de la prueba.

Al igual que con las aserciones, existe una equivalencia bastante estándar en Fluent/BDD para grupos de prueba. Algunos ejemplos típicos se comparan en el siguiente código:

// traditional/TDD
suite('math tests', () => {
  test('handle zero values', () => {
    assert.equal(fibonacci(0), 0);
  });
});

// Fluent/BDD
describe('math tests', () => {
  it('should handle zero values', () => {
    expect(fibonacci(0)).toBe(0);
  });
})

En la mayoría de los frameworks, suite y describe se comportan de manera similar, al igual que test y it, a diferencia de las mayores diferencias entre el uso de expect y assert para escribir aserciones.

Otras herramientas tienen enfoques sutilmente diferentes para organizar los paquetes y las pruebas. Para Por ejemplo, el ejecutor de pruebas integrado de Node.js admite llamadas anidadas a test() para crear implícitamente una jerarquía de prueba. Sin embargo, Vitest solo permite este tipo de la anidación con suite() y no ejecutará un test() definido dentro de otro test().

Al igual que con las aserciones, recuerda que la combinación exacta de agrupamiento métodos que proporciona tu pila tecnológica no es tan importante. En este curso, abordaremos en resumen, pero tendrás que descifrar cómo se aplican a tu la variedad de herramientas.

Métodos del ciclo de vida

Un motivo para agrupar las pruebas, incluso implícitamente en el nivel superior dentro de una es proporcionar métodos de configuración y desmontaje que se ejecuten en cada prueba o una vez para un grupo de pruebas. La mayoría de los frameworks proporcionan cuatro métodos:

Para cada `test()` o `it()` Una vez que llegue el paquete
Antes de que se ejecute la prueba `beforeEach()` `beforeAll()`
Después de las ejecuciones de prueba `afterEach()` `afterAll()`

Por ejemplo, tal vez quieras completar previamente una base de datos de usuarios virtuales antes de cada probar y borrarlo después:

suite('user test', () => {
  beforeEach(() => {
    insertFakeUser('bob@example.com', 'hunter2');
  });
  afterEach(() => {
    clearAllUsers();
  });

  test('bob can login', async () => {  });
  test('alice can message bob', async () => {  });
});

Esto puede ser útil para simplificar las pruebas. Puedes compartir herramientas de configuración y desmontar el código, en lugar de duplicarlo en cada prueba. Además, si los de configuración y desmontaje, y el código arroja un error que puede indicar que no implican fallas en las pruebas.

Consejo general

Estos son algunos consejos para tener en cuenta cuando pienses en estas primitivas.

Los datos primitivos son una guía

Recuerda que estas herramientas y primitivas, y en las próximas páginas, no coinciden exactamente con Vitest, Jest, Mocha, Web Test Runner o cualquier otro en un framework específico. Si bien hemos usado Vitest como guía general, asegúrate de asignar a tu elección de framework.

Combina y combina aserciones según sea necesario

Las pruebas son, en esencia, código que puede generar errores. Cada ejecutor proporcionará un primitivo, probablemente test(), para describir casos de prueba distintos.

Pero si ese ejecutor también proporciona assert(), expect() y asistentes de aserción, Recuerda que esta parte tiene más que ver con la conveniencia y que puedes omitirla si que necesiten. Puedes ejecutar cualquier código que pueda arrojar un error, incluido otros de aserción o una sentencia if antigua.

La configuración del IDE puede ser vital

Garantizar que tu IDE, como VSCode, tenga acceso al autocompletado y sobre las herramientas de prueba elegidas puede aumentar tu productividad. Para Por ejemplo, hay más de 100 métodos en assert en la aserción de Chai biblioteca y contar con documentación para el que aparece intercalados puede ser conveniente.

Esto puede ser muy importante para algunos frameworks de prueba que propagan el global con sus métodos de prueba. Esta es una diferencia sutil, pero Por lo general, es posible usar bibliotecas de prueba sin importarlas si son automáticamente al espacio de nombres global:

// some.test.js
test('using test as a global', () => {  });

Recomendamos importar los asistentes incluso si se admiten automáticamente. ya que le brinda al IDE una forma clara de buscar estos métodos. (Es posible que tengas este problema al compilar React, ya que algunas bases de código tienen un lenguaje React es global, pero algunos no lo son y requieren que se importe en todos los archivos con React.)

// some.test.js
import { test } from 'vitest';
test('using test as an import', () => {  });