Eu tenho uma classe que é refatorada em 1 classe principal e 2 classes menores. As principais classes usam o banco de dados (como muitas das minhas classes) e envia um email. Portanto, a classe principal tem um IPersonRepository
e um IEmailRepository
injetado, que por sua vez envia para as 2 classes menores.
Agora, quero testar a unidade principal na classe principal e aprendi a não unir o funcionamento interno da classe, porque devemos poder mudar o funcionamento interno sem interromper os testes de unidade.
Mas como a classe usa oe IPersonRepository
um IEmailRepository
, EU TENHO que especificar (simulação / simulação) resultados para alguns métodos para o IPersonRepository
. A classe principal calcula alguns dados com base nos dados existentes e os retorna. Se eu quiser testar isso, não vejo como posso escrever um teste sem especificar que o IPersonRepository.GetSavingsByCustomerId
retorno é x. Mas então meu teste de unidade 'sabe' sobre o funcionamento interno, porque 'sabe' quais métodos zombar e quais não.
Como posso testar uma classe que injetou dependências, sem o teste saber sobre os internos?
fundo:
Na minha experiência, muitos testes como esse criam simulações para os repositórios e, em seguida, fornecem os dados corretos para as simulações ou testam se um método específico foi chamado durante a execução. De qualquer forma, o teste conhece os internos.
Agora eu vi uma apresentação sobre a teoria (que eu ouvi antes) de que o teste não deveria saber sobre a implementação. Primeiro porque você não está testando como ele funciona, mas também porque, quando você altera a implementação, todos os testes de unidade falham porque eles 'sabem' sobre a implementação. Embora eu goste do conceito de que os testes desconhecem a implementação, não sei como realizá-la.
fonte
IPersonRepository
objeto, essa interface e todos os métodos descritos não são mais "internos", portanto, não é realmente um problema do teste. Sua verdadeira pergunta deve ser "como refatorar classes em unidades menores sem expor muito em público". A resposta é "mantenha essas interfaces enxutas" (aderindo ao princípio de segmentação de interfaces, por exemplo). Esse é o ponto 2 do IMHO na resposta de @ DavidArno (acho que não há necessidade de repetir isso em outra resposta).Respostas:
Você está certo de que isso é uma violação do princípio "não teste internals" e é comum que as pessoas ignoram.
Existem duas soluções que você pode adotar para solucionar essa violação:
1) Forneça uma simulação completa de
IPersonRepository
. Sua abordagem atualmente descrita é associar a simulação ao funcionamento interno do método em teste, simulando apenas os métodos que ele chamará. Se você fornecer zombarias para todos os métodos deIPersonRepository
, remova esse acoplamento. O funcionamento interno pode mudar sem afetar a zombaria, tornando o teste menos frágil.Essa abordagem tem a vantagem de manter o mecanismo de DI simples, mas pode criar muito trabalho se sua interface definir muitos métodos.
2) Não injete
IPersonRepository
, injete oGetSavingsByCustomerId
método ou injete um valor de poupança. O problema de injetar implementações de interface inteiras é que você está injetando ("diga, não pergunte") e um sistema "pergunte, não diga", misturando as duas abordagens. Se você adotar uma abordagem de "DI puro", deve-se fornecer ao método (informado) o método exato a ser chamado se desejar um valor de poupança, em vez de receber um objeto (que deve ser solicitado efetivamente pelo método a ser chamado).A vantagem dessa abordagem é que ela evita a necessidade de zombarias (além dos métodos de teste que você injeta no método em teste). A desvantagem é que isso pode fazer com que as assinaturas do método sejam alteradas em resposta aos requisitos da alteração do método.
Ambas as abordagens têm seus prós e contras, então escolha a que melhor se adapte às suas necessidades.
fonte
Minha abordagem é criar versões 'falsas' dos repositórios que leem de arquivos simples que contêm os dados necessários.
Isso significa que o teste individual não 'sabe' sobre a configuração do mock, embora obviamente o projeto geral do teste faça referência ao mock e tenha os arquivos de configuração etc.
Isso evita a configuração complexa de objetos simulados exigida pelas estruturas de simulação e permite que você use os objetos 'simulados' em instâncias reais do seu aplicativo para teste de UI e similares.
Como o 'mock' está totalmente implementado, em vez de ser configurado especificamente para o cenário de teste, uma alteração na implementação, por exemplo, digamos que GetSavingsForCustomer agora também deve excluir o cliente. Não interromperá os testes (a não ser, é claro, que realmente interrompa os testes), você precisará apenas atualizar sua implementação simulada e todos os seus testes serão executados sem alterar a configuração
fonte
Testes de unidade são geralmente testes de caixa branca (você tem acesso ao código real). Portanto, não há problema em conhecer os internos até um certo ponto; no entanto, para iniciantes, é mais fácil não, porque você não deve testar o comportamento interno (como "chamar o método a primeiro, depois b e depois novamente").
Injetar uma simulação que fornece dados é aceitável, pois sua classe (unidade) depende desses dados externos (ou provedor de dados). No entanto, você deve verificar os resultados (não é o caminho para alcançá-los)! por exemplo, você fornece uma instância de Pessoa e verifica se um email foi enviado para o endereço de email correto; por exemplo, fornecendo uma simulação para o email também, essa simulação não faz nada além de armazenar o endereço de email do destinatário para acesso posterior pelo seu teste -código. (Acho que Martin Fowler os chama de Stubs em vez de Mocks, no entanto)
fonte