Ferramentas do comércio

Os testes automatizados são basicamente um código que vai gerar ou causar um erro se se algo está errado. A maioria das bibliotecas ou frameworks de teste oferece uma variedade que facilitam a criação de testes.

Como mencionado na seção anterior, esses primitivos quase sempre incluem uma uma maneira de definir testes independentes (chamados de casos de teste) e fornecer declarações. As declarações são uma forma de combinar a verificação de um resultado e gerar uma se algo estiver errado e pode ser considerado o primitivo básico de todos de teste.

Nesta página, apresentamos uma abordagem geral para esses primitivos. O framework escolhido provavelmente tem algo assim, mas esta não é uma referência exata.

Exemplo:

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
  })
});

Este exemplo cria um grupo de testes (às vezes chamado de pacote) chamado "matemática test" e define três casos de teste independentes, cada um executando algumas declarações. Esses casos de teste geralmente podem ser abordados ou executados individualmente, por exemplo, por um flag de filtro no executor de testes.

Auxiliares de declaração como primitivos

A maioria das estruturas de teste, incluindo o Vitest, inclui uma coleção de declarações em um objeto assert que permitem conferir rapidamente os valores de retorno ou outros estados, com alguma expectativa. Essa expectativa é muitas vezes "conhecida como boa". e a distribuição dos valores dos dados. No exemplo anterior, sabemos que o 13o número de Fibonacci deve ser 233, então podemos confirmar isso diretamente usando assert.equal.

Você também pode ter expectativas de que um valor assume uma determinada forma ou é maior que outro valor ou ter alguma outra propriedade. Este curso cobrem toda a gama de possíveis auxiliares de declaração, mas os frameworks de teste sempre realize pelo menos as seguintes verificações básicas:

  • Uma "verdade" , geralmente descrito como “ok” verifica se uma condição é verdadeira, correspondendo à forma de escrever um if que verifica se algo deu certo ou correto. Ele tende a ser fornecido como assert(...) ou assert.ok(...), e assume um único valor mais um comentário opcional.

  • Uma verificação de igualdade, como no exemplo do teste de matemática, em que você espera retorna o valor ou estado de um objeto para se igualar a um valor válido conhecido. Estes são para igualdade primitiva (como números e strings) ou igualdade referencial (são o mesmo objeto). Internamente, isso é apenas uma "verdade" checava com uma comparação de == ou ===.

    • O JavaScript distingue entre igualdade flexível (==) e rigorosa (===). A maioria das bibliotecas de teste oferece os métodos assert.equal e assert.strictEqual, respectivamente.
  • Verificações de igualdade profundas, que estendem as verificações de igualdade para incluir a verificação conteúdos de objetos, matrizes e outros tipos de dados mais complexos, bem como lógica interna para transferir objetos e compará-los. Elas são importantes porque o JavaScript não tem uma forma integrada de comparar o conteúdo dois objetos ou matrizes. Por exemplo, [1,2,3] == [1,2,3] é sempre falso. Testar estruturas geralmente incluem auxiliares deepEqual ou deepStrictEqual.

Auxiliares de declaração que comparam dois valores (em vez de apenas uma verificação de "verdade") geralmente usam dois ou três argumentos:

  • O valor real, como gerado a partir do código em teste ou que descreve o para validar.
  • O valor esperado, normalmente codificado (por exemplo, um número literal ou string).
  • Um comentário opcional descrevendo o que é esperado ou o que pode ter falhado. que será incluído em caso de falha na linha.
.

Também é uma prática bastante comum combinar declarações para criar uma variedade de porque é raro que alguém confirme corretamente o estado do seu o sistema sozinho. Exemplo:

  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')
  });

O Vitest usa a biblioteca de declaração Chai (em inglês). internamente para fornecer seus auxiliares de declaração, e pode ser útil examinar a referência dele para ver quais declarações e auxiliares podem ser adequados ao seu código.

Asserções fluentes e BDD

Alguns desenvolvedores preferem um estilo de declaração que possa ser chamado orientado por comportamento desenvolvimento (BDD) ou Estilo Fluent declarações. Também são chamadas de "espera", auxiliares, porque o ponto de entrada para verificar as expectativas é um método chamado expect().

Os auxiliares se comportam da mesma maneira que as declarações criadas como um método simples. como assert.ok ou assert.strictDeepEquals, mas alguns desenvolvedores acham mais fáceis de ler. Uma declaração BDD pode ser assim:

// 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');

Esse estilo de declarações funciona por causa de uma técnica chamada encadeamento de métodos, em que o objeto retornado por expect pode ser continuamente encadeado com outras chamadas de método. Algumas partes da chamada, incluindo to.be e that.does no exemplo anterior, não têm função e são incluídas apenas para fazer a chamada mais fácil de ler e potencialmente gerar um comentário automatizado se o teste falhou. Particularmente, expect normalmente não é compatível com um comentário opcional, porque o encadeamento precisa descrever a falha com clareza.

Muitas estruturas de teste são compatíveis com declarações fluentes/BDD e regulares. Vitest, por exemplo, exporta os dois Chai tem uma abordagem própria um pouco mais concisa do BDD. Jest por outro lado, inclui apenas uma expectativa método por padrão.

Agrupar testes em arquivos

Ao programar testes, já tendemos a fornecer agrupamentos implícitos, do que todos os testes em um único arquivo, é comum programar testes em vários . Na verdade, os executores de testes geralmente só sabem que um arquivo é para teste, porque de um filtro predefinido ou uma expressão regular (vitest, por exemplo, inclui todas arquivos em seu projeto que terminam com uma extensão como ".test.jsx" ou ".spec.ts" (".test" e ".spec", além de várias extensões válidas).

Os testes de componentes tendem a estar localizados em um arquivo semelhante ao do componente em teste. como na seguinte estrutura de diretórios:

Uma lista de arquivos em um
  , incluindo PCollection.tsx e PCollection.test.tsx.
Um arquivo de componente e um arquivo de teste relacionado.

Da mesma forma, os testes de unidade tendem a ser colocados ao lado do código em teste. Os testes completos podem estar no próprio arquivo, e os testes de integração podem até em pastas exclusivas. Essas estruturas podem ser úteis quando casos de teste complexos crescem para exigir seus próprios arquivos de suporte não relacionados a teste, como as bibliotecas de suporte necessárias apenas para um teste.

Agrupar testes em arquivos

Como nos exemplos anteriores, é comum colocar testes dentro de uma chamada para suite(), que agrupa testes configurados com test(). As suítes não costumam ser mas ajudam a fornecer estrutura agrupando testes relacionados ou metas chamando o método passado. Para test(), o método transmitido descreve as ações do teste em si.

Assim como nas declarações, há uma equivalência bastante padrão em Fluent/BDD para agrupamento de testes. Alguns exemplos típicos são comparados no seguinte 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);
  });
})

Na maioria dos frameworks, suite e describe se comportam de forma semelhante, assim como test e it, em oposição às maiores diferenças entre usar expect e assert para gravar declarações.

Outras ferramentas têm abordagens sutilmente diferentes para organizar pacotes e testes. Para exemplo, o executor de testes integrado do Node.js é compatível com o aninhamento de chamadas para test() para criar implicitamente uma hierarquia de teste. No entanto, o Vitest permite apenas esse tipo aninhamento usando suite() e não executará uma test() definida dentro de outra test().

Assim como nas declarações, lembre-se de que a combinação exata de agrupamento métodos fornecidos pelo seu conjunto de tecnologias não é tão importante. Este curso abordará no resumo, mas você precisa descobrir como elas se aplicam opções de ferramentas.

Métodos de ciclo de vida

Um motivo para agrupar seus testes, mesmo implicitamente no nível superior de um é fornecer métodos de configuração e desmontagem que são executados para cada teste ou uma vez para um grupo de testes. A maioria das bibliotecas oferece quatro métodos:

Para cada `test()` ou `it()` Uma vez para a suíte
Antes das execuções de teste "beforeperiod()" `beforeAll()`
Após execuções de teste `afterCada()` `afterAll()`

Por exemplo, você pode querer pré-preencher um banco de dados de usuários virtuais antes que cada teste e apague-o depois:

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

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

Isso pode ser útil para simplificar seus testes. É possível compartilhar configurações e desmontar código, em vez de duplicá-lo em todos os testes. Além disso, se o o próprio código de configuração e desmontagem gera um erro, que pode indicar problemas que não envolvem a falha dos testes.

Recomendações gerais

Aqui estão algumas dicas a serem lembradas ao pensar sobre esses primitivos.

Os primitivos são um guia

Lembre-se de que as ferramentas e os primitivos aqui e nas próximas páginas não corresponder exatamente a Vitest, Jest, Mocha, Web Test Runner ou qualquer outro um framework específico. Embora tenhamos usado o Vitest como um guia geral, certifique-se de mapear com a estrutura de sua escolha.

Misture e combine declarações conforme necessário

Testes são basicamente códigos que podem gerar erros. Cada corredor fornece uma primitivo, provavelmente test(), para descrever casos de teste distintos.

Mas, se esse executor também fornece assert(), expect() e auxiliares de declaração, Lembre-se de que esta parte é mais conveniente e você pode ignorá-la se quiser precisam. Você pode executar qualquer código que possa gerar um erro, incluindo outros as bibliotecas de declaração ou uma declaração if à moda antiga.

A configuração do ambiente de desenvolvimento integrado pode ser um salva-vidas

Garantir que seu ambiente de desenvolvimento integrado, como o VSCode, tenha acesso ao preenchimento automático documentação sobre as ferramentas de teste escolhidas pode aumentar sua produtividade. Para exemplo, há mais de 100 métodos em assert na declaração Chai. biblioteca e ter a documentação à direita pode ser conveniente.

Isso pode ser especialmente importante para algumas estruturas de teste que preenchem a namespace global com os métodos de teste deles. Essa é uma diferença sutil, muitas vezes é possível usar bibliotecas de teste sem importá-las se elas forem adicionados automaticamente ao namespace global:

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

Recomendamos importar os auxiliares mesmo que eles tenham suporte automático. porque isso oferece ao ambiente de desenvolvimento integrado uma maneira clara de procurar esses métodos. (Você pode ter esse problema ao compilar o React, já que algumas bases de código têm uma React global, mas alguns não, e exigem que ele seja importado em todos os arquivos usando React.)

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