Duas maneiras de fazer testes de unidade confiáveis

Acredita-se que testes de unidade não sejam necessários. Que apenas metade da verdade está escondida neles. E essas informações genuínas sobre o comportamento do programa serão reveladas somente quando as coletarmos em um teste de integração.

Há uma razão para isso, mas os testes de unidade são realmente incompletos e podem ser mais confiáveis? Quantas razões para a sua incompletude?

Suponha que tenhamos dois componentes cobertos por teste de unidade, Caller e Callee. O chamador chama Callee com um argumento e, de alguma forma, usa o objeto retornado. Cada um dos componentes possui seu próprio conjunto de dependências, as quais absorvemos.

Quantos cenários em que esses componentes se comportam inesperadamente durante a integração?

O primeiro cenário é um problema externo aos dois componentes . Por exemplo, ambos dependem do estado do banco de dados, autorização, variáveis ​​de ambiente, variáveis ​​globais, cookies, arquivos etc. Julgar isso é bastante simples, porque mesmo em programas muito grandes geralmente há um número limitado de tais pontos de discórdia.

O problema pode ser resolvido, obviamente, através de um redesenho com dependências reduzidas,
ou simulamos diretamente um possível erro em um cenário de nível superior, ou seja, apresentamos o componente CallingStrategy (OffendingCaller, OffendedCallee) {} e simulamos a falha e a manipulação de erros do Callee no CallingStrategy. Para isso, testes de integração não são necessários, mas é necessário entender que o comportamento específico de um dos componentes representa um risco para o outro componente, e seria bom isolar esse cenário em um componente.

Segundo cenário: o problema está na interface de objetos integráveis, ou seja, um estado desnecessário de um dos objetos vazou para outro .

De fato, essa é uma falha na interface que permite isso. A solução para o problema também é bastante óbvia - digitação e estreitamento de interfaces, validação antecipada de parâmetros.

Como podemos ver, ambas as razões são extremamente comuns, mas seria bom articular claramente que não existem outras.

Portanto, se verificamos em nossas classes 1) estado interno e 2) dependências externas, não temos motivos para duvidar da confiabilidade dos testes de unidade.

(Em algum lugar no canto, um programador funcional está chorando baixinho com as palavras "eu te disse", mas não sobre isso agora).

Mas podemos simplesmente esquecer ou perder algum tipo de dependência!

Pode ser estimado com grosseria. Suponha que haja dez cenários em cada componente. Nós pulamos um cenário em cada dez. Por exemplo, Callee repentinamente retorna nulo e Caller repentinamente recebe uma NullPointerException. Precisamos cometer um erro duas vezes, o que significa que a probabilidade de cair em algum lugar é 1/100. É difícil imaginar que o cenário de integração para os dois elementos consiga entender isso. Para muitos componentes chamados sucessivamente no teste de integração, a probabilidade de detectar alguns dos erros aumenta, o que significa que quanto mais longa a pilha do teste de integração e mais cenários, mais justificado.

(A matemática real de acumular erros é, é claro, muito mais complicada, mas o resultado não varia muito).

No processo de execução do teste de integração, no entanto, podemos esperar um aumento significativo no ruído devido a dependências desfeitas e tempo significativo gasto na busca de um bug, eles também são proporcionais ao comprimento da pilha.

Ou seja, os testes de integração são necessários se os testes de unidade estiverem ruins ou ausentes . Por exemplo, quando apenas um script válido é verificado em cada teste de unidade, quando eles usam interfaces muito amplas e não analisam dependências gerais.

Source: https://habr.com/ru/post/pt473720/


All Articles