A principal diferença, para mim, é que os testes de integração revelam se um recurso está funcionando ou está quebrado, pois enfatizam o código em um cenário próximo à realidade. Eles invocam um ou mais métodos ou recursos de software e testam se agem conforme o esperado.
Pelo contrário, um teste de unidade que testa um único método depende da suposição (geralmente errada) de que o restante do software está funcionando corretamente, porque zomba explicitamente de todas as dependências.
Portanto, quando um teste de unidade para um método que implementa algum recurso é verde, isso não significa que o recurso está funcionando.
Digamos que você tenha um método em algum lugar como este:
public SomeResults DoSomething(someInput) {
var someResult = [Do your job with someInput];
Log.TrackTheFactYouDidYourJob();
return someResults;
}
DoSomething
é muito importante para o seu cliente: é um recurso, a única coisa que importa. É por isso que você geralmente escreve uma especificação de pepino afirmando-a: deseja verificar e comunicar se o recurso está funcionando ou não.
Feature: To be able to do something
In order to do something
As someone
I want the system to do this thing
Scenario: A sample one
Given this situation
When I do something
Then what I get is what I was expecting for
Sem dúvida: se o teste for aprovado, você poderá afirmar que está entregando um recurso funcional. Isso é o que você pode chamar de Valor comercial .
Se você deseja escrever um teste de unidade, DoSomething
deve fingir (usando algumas simulações) que o restante das classes e métodos está funcionando (ou seja: todas as dependências que o método está usando estão funcionando corretamente) e afirmar que seu método está funcionando.
Na prática, você faz algo como:
public SomeResults DoSomething(someInput) {
var someResult = [Do your job with someInput];
FakeAlwaysWorkingLog.TrackTheFactYouDidYourJob(); // Using a mock Log
return someResults;
}
Você pode fazer isso com Injeção de Dependência, ou algum Método de Fábrica ou qualquer Mock Framework ou apenas estendendo a classe sob teste.
Suponha que haja um erro Log.DoSomething()
. Felizmente, a especificação Gherkin o encontrará e seus testes de ponta a ponta falharão.
O recurso não funcionará, porque Log
está quebrado, não porque [Do your job with someInput]
não está fazendo seu trabalho. E, a propósito, [Do your job with someInput]
é de responsabilidade exclusiva desse método.
Além disso, suponha que Log
seja usado em 100 outros recursos, em 100 outros métodos de 100 outras classes.
Sim, 100 recursos falharão. Felizmente, porém, 100 testes de ponta a ponta também estão falhando e revelando o problema. E sim: eles estão dizendo a verdade .
É uma informação muito útil: eu sei que tenho um produto quebrado. Também é uma informação muito confusa: não me diz nada sobre onde está o problema. Ele me comunica o sintoma, não a causa raiz.
No entanto, DoSomething
o teste de unidade é verde, porque ele está usando um falso Log
, construído para nunca quebrar. E sim: está claramente mentindo . Está comunicando que um recurso quebrado está funcionando. Como isso pode ser útil?
(Se DoSomething()
o teste de unidade falhar, verifique se [Do your job with someInput]
há alguns erros.)
Suponha que este seja um sistema com uma classe quebrada:
Um único bug quebrará vários recursos e vários testes de integração falharão.
Por outro lado, o mesmo bug interrompe apenas um teste de unidade.
Agora, compare os dois cenários.
O mesmo bug interrompe apenas um teste de unidade.
- Todos os seus recursos usando o quebrado
Log
são vermelhos
- Todos os seus testes de unidade são verdes, apenas o teste de unidade
Log
é vermelho
Na verdade, os testes de unidade para todos os módulos que usam um recurso quebrado são verdes porque, usando zombarias, eles removeram dependências. Em outras palavras, eles correm em um mundo ideal, completamente fictício. E esta é a única maneira de isolar bugs e procurá-los. Teste de unidade significa zombaria. Se você não está zombando, não é um teste de unidade.
A diferença
Os testes de integração informam o que não está funcionando. Mas eles não servem para adivinhar onde o problema poderia estar.
Os testes de unidade são os únicos testes que informam exatamente onde está o erro. Para desenhar essas informações, eles devem executar o método em um ambiente simulado, onde todas as outras dependências devem funcionar corretamente.
É por isso que acho que a sua frase "Ou é apenas um teste de unidade que abrange 2 classes" está de alguma forma deslocada. Um teste de unidade nunca deve abranger 2 classes.
Essa resposta é basicamente um resumo do que escrevi aqui: Os testes de unidade mentem, é por isso que eu os amo .