Estou escrevendo um componente que, dado um arquivo ZIP, precisa:
- Descompacte o arquivo.
- Encontre uma dll específica entre os arquivos descompactados.
- Carregue essa dll através da reflexão e invoque um método nela.
Eu gostaria de testar este componente.
Estou tentado a escrever código que lida diretamente com o sistema de arquivos:
void DoIt()
{
Zip.Unzip(theZipFile, "C:\\foo\\Unzipped");
System.IO.File myDll = File.Open("C:\\foo\\Unzipped\\SuperSecret.bar");
myDll.InvokeSomeSpecialMethod();
}
Mas as pessoas costumam dizer: "Não escreva testes de unidade que dependem do sistema de arquivos, banco de dados, rede etc."
Se eu escrevesse isso de uma maneira amigável para teste de unidade, suponho que ficaria assim:
void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
string path = zipper.Unzip(theZipFile);
IFakeFile file = fileSystem.Open(path);
runner.Run(file);
}
Yay! Agora é testável; Eu posso alimentar testes duplos (zombes) para o método DoIt. Mas a que custo? Agora eu tive que definir três novas interfaces apenas para tornar isso testável. E o que exatamente estou testando? Estou testando se minha função DoIt interage corretamente com suas dependências. Não testa se o arquivo zip foi descompactado corretamente etc.
Parece que não estou mais testando a funcionalidade. Parece que estou apenas testando interações de classe.
Minha pergunta é a seguinte : qual é a maneira correta de testar algo que depende do sistema de arquivos?
edit Estou usando o .NET, mas o conceito também pode aplicar código nativo ou Java.
fonte
myDll.InvokeSomeSpecialMethod();
onde você verificaria se ele funciona corretamente em situações de sucesso e falha, para que eu não fizesse teste de unidade,DoIt
masDllRunner.Run
que disse usar mal um teste UNIT para verificar se todo o processo funciona. um uso indevido aceitável e, como seria um teste de integração que oculta um teste de unidade, as regras normais de teste de unidade não precisam ser aplicadas estritamenteRespostas:
Não há realmente nada de errado com isso, é apenas uma questão de chamá-lo de teste de unidade ou de integração. Você só precisa garantir que, se você interagir com o sistema de arquivos, não houver efeitos colaterais indesejados. Especificamente, certifique-se de limpar depois de você mesmo - excluir todos os arquivos temporários criados - e não substituir acidentalmente um arquivo existente que possuísse o mesmo nome de arquivo temporário que você estava usando. Sempre use caminhos relativos e não caminhos absolutos.
Também seria uma boa ideia
chdir()
entrar em um diretório temporário antes de executar o teste echdir()
voltar depois.fonte
chdir()
é um processo completo, portanto você pode interromper a capacidade de executar seus testes em paralelo, se sua estrutura de teste ou uma versão futura dela suportar isso.Você acertou a unha na cabeça. O que você deseja testar é a lógica do seu método, não necessariamente se um arquivo verdadeiro pode ser endereçado. Você não precisa testar (neste teste de unidade) se um arquivo está descompactado corretamente, seu método toma isso como garantido. As interfaces são valiosas por si só porque fornecem abstrações nas quais você pode programar, em vez de depender implícita ou explicitamente de uma implementação concreta.
fonte
DoIt
função testável conforme declarada nem precisa de teste. Como o questionador apontou corretamente, não resta nada de importante a ser testado. Agora é a implementação deIZipper
,IFileSystem
eIDllRunner
isso precisa ser testado, mas são exatamente as coisas que foram ridicularizadas para o teste!Sua pergunta expõe uma das partes mais difíceis dos testes para desenvolvedores que acabaram de entrar:
"O que diabos eu teste?"
Seu exemplo não é muito interessante porque apenas cola algumas chamadas de API. Se você escrevesse um teste de unidade, acabaria afirmando que os métodos foram chamados. Testes como esse acoplam fortemente seus detalhes de implementação ao teste. Isso é ruim porque agora você precisa alterar o teste toda vez que alterar os detalhes de implementação do seu método, pois alterar os detalhes da implementação interrompe o (s) seu (s) teste (s)!
Ter testes ruins é realmente pior do que não ter testes.
No seu exemplo:
Enquanto você pode passar em zombarias, não há lógica no método para testar. Se você tentar um teste de unidade para isso, pode ser algo como isto:
Parabéns, você basicamente copiou os detalhes de implementação do seu
DoIt()
método em um teste. Manutenção feliz.Quando você escreve testes, deseja testar o QUE e não o COMO . Consulte Teste da caixa preta para obter mais informações.
O QUE é o nome do seu método (ou pelo menos deveria ser). O HOW são todos os pequenos detalhes de implementação que vivem dentro do seu método. Bons testes permitem que você troque o COMO sem quebrar o QUE .
Pense desta maneira, pergunte-se:
"Se eu alterar os detalhes de implementação deste método (sem alterar o contrato público), ele quebrará meus testes?"
Se a resposta for sim, você está testando o COMO e não o QUE .
Para responder sua pergunta específica sobre o teste de código com dependências do sistema de arquivos, digamos que você tenha algo um pouco mais interessante em um arquivo e deseje salvar o conteúdo codificado em Base64 de a
byte[]
em um arquivo. Você pode usar fluxos para isso para testar se o seu código faz a coisa certa sem precisar verificar como ele o faz. Um exemplo pode ser algo assim (em Java):O teste usa um
ByteArrayOutputStream
, mas na aplicação (usando a injeção de dependência) a StreamFactory real (talvez chamado FileStreamFactory) deve retornarFileOutputStream
a partiroutputStream()
e iria escrever para umFile
.O que foi interessante sobre o
write
método aqui é que ele estava escrevendo o conteúdo codificado em Base64, e foi para isso que testamos. Para o seuDoIt()
método, isso seria testado de maneira mais apropriada com um teste de integração .fonte
Sou reticente em poluir meu código com tipos e conceitos que existem apenas para facilitar o teste de unidade. Claro, se ele torna o design mais limpo e melhor do que ótimo, mas acho que esse não é o caso.
Minha opinião é que seus testes de unidade fariam o máximo possível, o que pode não ser 100% de cobertura. De fato, pode ser apenas 10%. O ponto é que seus testes de unidade devem ser rápidos e sem dependências externas. Eles podem testar casos como "este método lança uma ArgumentNullException quando você passa nulo para esse parâmetro".
Eu adicionaria testes de integração (também automatizados e provavelmente usando a mesma estrutura de teste de unidade) que podem ter dependências externas e testar cenários de ponta a ponta como esses.
Ao medir a cobertura do código, eu medo os testes de unidade e de integração.
fonte
Não há nada de errado em acessar o sistema de arquivos, considere apenas um teste de integração e não um teste de unidade. Eu trocaria o caminho codificado por um caminho relativo e criaria uma subpasta TestData para conter os zips dos testes de unidade.
Se seus testes de integração demorarem muito para serem executados, separe-os para que não funcionem com a mesma frequência que os testes rápidos de unidade.
Eu concordo, às vezes acho que o teste baseado em interação pode causar muito acoplamento e muitas vezes acaba não fornecendo valor suficiente. Você realmente deseja testar o descompactação do arquivo aqui e não apenas verificar se está chamando os métodos certos.
fonte
Uma maneira seria escrever o método descompactar para usar InputStreams. Em seguida, o teste de unidade poderia construir um InputStream a partir de uma matriz de bytes usando ByteArrayInputStream. O conteúdo dessa matriz de bytes pode ser uma constante no código de teste da unidade.
fonte
Parece ser mais um teste de integração, pois você depende de um detalhe específico (o sistema de arquivos) que pode mudar, em teoria.
Eu abstraia o código que lida com o sistema operacional em seu próprio módulo (classe, montagem, jar, o que for). No seu caso, você deseja carregar uma DLL específica, se encontrada, portanto, faça uma interface IDllLoader e uma classe DllLoader. Seu aplicativo adquiriu a DLL do DllLoader usando a interface e testou que ... você não é responsável pelo código de descompactação afinal, certo?
fonte
Supondo que "interações do sistema de arquivos" sejam bem testadas na própria estrutura, crie seu método para trabalhar com fluxos e teste isso. Abrir um FileStream e passá-lo para o método pode ser deixado de fora de seus testes, pois o FileStream.Open é bem testado pelos criadores da estrutura.
fonte
Você não deve testar a interação de classe e a chamada de função. em vez disso, você deve considerar o teste de integração. Teste o resultado necessário e não a operação de carregamento de arquivo.
fonte
Para teste de unidade, sugiro que você inclua o arquivo de teste em seu projeto (arquivo EAR ou equivalente) e use um caminho relativo nos testes de unidade, como "../testdata/testfile".
Desde que seu projeto seja exportado / importado corretamente, o teste de unidade deve funcionar.
fonte
Como outros já disseram, o primeiro é bom como teste de integração. O segundo testa apenas o que a função deve realmente fazer, que é tudo o que um teste de unidade deve fazer.
Como mostrado, o segundo exemplo parece um pouco inútil, mas oferece a oportunidade de testar como a função responde a erros em qualquer uma das etapas. Você não tem nenhuma verificação de erro no exemplo, mas no sistema real, a injeção de dependência permite testar todas as respostas a quaisquer erros. Então o custo terá valido a pena.
fonte