Escrevendo Testes
Os testes podem ser escritos de várias maneiras. Esta seção tenta fornecer um padrão e a justificativa por trás da forma como escrevemos testes.
Os testes DEVEM ser escritos usando o describe e o it métodos fornecidos pelo jest
Describing and building context
O describe o método DEVE ser usado para descrever, sempre que possível, o contexto no qual o trecho de código a ser testado será executado e DEVE construir esse contexto dentro do seu escopo.
describe('when the flag is red', () => {
...
})
describe('when the flag is blue', () => {
...
})Para descrever o contexto, DEVEMOS usar uma das seguintes palavras: when para describes de nível superior e having ou e para indicar que existe um contexto que depende de outro contexto definido anteriormente. A justificativa por trás disso é ter contextos auto-descritivos que possam ser facilmente entendidos e verificados combinando as frases do describe. Como a ideia é concatenar as descrições dos contextos, describes DEVEM ser escritas em minúsculas.
describe('when the flag is red', () => {
...
describe('and the wind is strong', () => {
// O contexto aqui é descrito pela conjunção dos describes:
// "when the red flag is red and the wind is strong"
...
})
})Múltiplos describes podem ser aninhados no mesmo nível e em níveis inferiores. Describes no mesmo nível denotam contextos diferentes e describes em níveis inferiores (describes aninhados) denotam um contexto mais específico.
Como mencionado antes, cada describe DEVE ser acompanhado por um método que construa o contexto ou um método que destrua ou altere o contexto. Os métodos que DEVEM ser usados são o beforeEach e o afterEach.
O framework Jest tem dois métodos, beforeEach e beforeAll, mas combinar ambos gera confusão e problemas de execução quando contextos aninhados estão presentes, pois a ordem em que eles são executados não é o que esperaríamos.
Contextos criados com o beforeEach método serão cada vez mais definidos à medida que avançamos na árvore de describe. Cada beforeEach especificará o contexto com mais detalhe de acordo com o que seu texto diz.
Escopo de Variáveis
O variáveis definidas nos contextos DEVEM ser declaradas de acordo com o escopo em que serão usadas. Ou seja, se uma variável é usada apenas para um contexto específico em um nível aninhado de describes, a variável DEVE ser definida somente nesse contexto específico. A justificativa por trás disso é manter as variáveis próximas ao código em que são usadas, tornando o código mais fácil de entender.
Variáveis de tipos primitivos, que não serão mudadas no contexto em que estão definidas ou em contextos subsequentes DEVEM ser definidas como const uma vez, sobre a beforeEach definição no escopo.
Variáveis de tipos primitivos que serão alterados no contexto em que estão definidos ou em contextos subsequentes DEVEM ser definidos como let, conforme a natureza do Typescript exige.
Variáveis de tipos não primitivos (objetos, arrays, etc) DEVEM ser definidos como let e seu valor DEVE ser definido em um beforeEach. A justificativa por trás disso é que objetos em JS são mutáveis, o que significa que qualquer execução de código que use esse objeto está sujeita a alterações e pode afetar outros testes de maneira involuntária.
As variáveis DEVEM ser tipadas corretamente sempre que possível. Tipos repetidos DEVEM ser abstraídos em um type.
Descrevendo expectativas e executando código
O it o método DEVE ser sempre colocado dentro do escopo de um describe e DEVE ser usado para descrever o que esperamos do código que vamos testar e DEVE conter, se possível, apenas uma asserção. Múltiplas asserções por it podem existir se houver um problema de performance, já que os testes serão executados uma vez por it (por causa do beforeEach), ou se o que é esperado puder ser descrito com clareza na descrição da expectativa.
A justificativa por trás dessa estrutura é fornecer clareza ao revisor dos testes e aos desenvolvedores que irão manter e alterar o código, já que cada contexto e expectativa está claramente enumerado, tornando mais fácil entender o que está sendo testado, como está sendo testado e o que falta testar.
Seguindo os describe descritores até os itos desenvolvedores podem facilmente entender o que está sendo testado concatenando as sentenças.
Escrevendo Expectativas Claras
A descrição das expectativas escritas nos itDEVE ser o mais descritiva possível sobre o que se espera da execução dos testes. Os desenvolvedores NÃO DEVEM usar frases abstratas ou gerais para definir expectativas, pois isso reduz a clareza do que está sendo esperado do código.
O desenvolvedor NÃO DEVE usar frases como:
"should work as expected" ⇒ O que deve funcionar como esperado?
"should return the correct value" ⇒ Qual é o valor correto?
"should resolve/return/work correctly" ⇒ Como algo funciona corretamente?
"should fail" ⇒ Como deve falhar? Qual é a mensagem que deve fornecer?
Deve-se levar em consideração que ao especificar o que é esperado nos itou qual será o contexto nos describeos desenvolvedores PODEM usar nomes de funções ou mensagens de erro exatas se for necessário para entender melhor a intenção, mas DEVEM usar uma representação textual quando possível para tornar os testes mais fáceis de manter.
O que testar
Escolher o que testar pode variar dependendo do código que está sendo executado. Diferentes fatores, desde performance até um grande domínio de entradas, podem definitivamente alterar o que deve ou não ser testado. Aqui exporemos um único conjunto de casos que o desenvolvedor DEVE testar, se possível.
A função run aqui tem alguns fluxos de execução diferentes. Funções, ou trechos de código, NÃO DEVEM ser testados apenas com base nos diferentes fluxos de execução possíveis, mas no que se espera da função, pois a função pode não estar fazendo aquilo para o qual foi escrita e os testes podem ficar fortemente acoplados ao código e não detectar problemas diferentes. Isso implica que o desenvolvedor DEVE sempre testar todos os caminhos de execução possíveis e DEVE testar outros caminhos possíveis também, de acordo com a semântica da função.
Para este caso específico, o desenvolvedor DEVE testar pelo menos os seguintes casos:
Executar a função run com uma quantidade de quilômetros maior que 1000
Executar a função run com uma quantidade de quilômetros igual a 10
Executar a função run com uma quantidade de quilômetros maior que 10 mas menor que 1000
Para o primeiro caso, o desenvolvedor precisa testar que a função run lança uma exceção. Exceções DEVEM ser verificadas quanto à sua mensagem de erro, pois qualquer outra exceção também poderia ser lançada, invalidando o propósito do teste. Se a exceção for personalizada, o desenvolvedor PODE verificar a instância da exceção.
Para o segundo caso, o desenvolvedor precisa testar que a função retornou o mesmo número de quilômetros que foi fornecido.
Para o terceiro caso, o desenvolvedor precisa testar que a função retornou o que uma função externa, beLazy retornou e, como doSomething (também executada no método) não pode ser verificada através do valor de retorno da função, o desenvolvedor DEVE testar que ela foi chamada com os argumentos corretos.
Quando mockar
Mocking dependerá principalmente do tipo de testes que o desenvolvedor está escrevendo.
Testes unitários DEVEM ter toda função externa mockada, com exceção de funções que fazem coisas simples, como formatar strings, etc.
Testes de API DEVEM ter mocks apenas para a comunicação com serviços externos, ou seja, DBs ou APIs diferentes. Se uma das operações feitas no teste afetar a performance da suíte de testes, um mock pode ser implementado para mitigar esse problema.
Todos os mocks DEVEM ser feitos usando as ferramentas fornecidas pelo Jest, com a exceção dos redux-saga-test-plan mocks.
Mocks e funções utilitárias
Quaisquer mocks do Jest DEVEM ser mockados usando seus once método quando possível, isto é,
mockReturnValueOnceoumockResolvedValueOnceé recomendado evitarmockReturnValueoumockResolvedValue. A justificativa por trás disso é prevenir que mocks indesejados executem, alterando a execução dos nossos testes.O Jest oferece diversos Types que você pode usar para diferentes tipos de mocks. Por exemplo, ao mockar um objeto você DEVE usar
jest.Mocked<typeof someObject>, para classes você DEVE usarjest.MockedClass<typeof SomeClass>, e para funções,jest.MockedFunction<typeof someFunction>.Um
afterEachDEVE ser escrito para rodar após cada teste com umjest.resetAllMockspara limpar qualquer implementação mockada de módulos mockados globalmente que possa vazar para outros testes. Você pode ignorar esse reset se estiver usandomockReturnValueOnceoumockResolvedValueOnceporque isso é feito automaticamente para você.Um diretório chamado
mocksPODE ser criado para armazenar mocks, que devem ser colocados no diretóriodiretório de testeouspec. Mocks grandes DEVEM ser armazenados em arquivos diferentes dos testes para evitar arquivos de teste extensos. Mocks pequenos, se não forem muitos, DEVEM ser mantidos nos próprios testes.Arquivos de mock DEVEM ser nomeados como a entidade que estão mockando, então por exemplo, se estivermos mockando um profile, o mock deve ser colocado em
/test/mocks/profile.tsarquivo.Caso vários mocks grandes sejam necessários, ELES DEVEM ser colocados em arquivos diferentes dentro de um diretório nomeado como a entidade a ser mockada, então por exemplo, se tivermos dois mocks de profile, iremos armazená-los como:
/test/mocks/profile/profile-with-wearables.tse/test/mocks/profile/profile-without-wearables.ts. Para facilitar o acesso a eles, VOCÊ DEVE criar umindex.tsarquivo dentro do/test/mocks/profile/diretório e exportá-los.A fim de evitar mutação de objetos mockados, mocks DEVEM ser exportados como funções que os retornem.
Atualizado