Às vezes, os objetos só precisam ser bem acoplados. Por exemplo, uma CsvFile
classe provavelmente precisará trabalhar estreitamente com a CsvRecord
classe (ou ICsvRecord
interface).
No entanto, pelo que aprendi no passado, um dos principais princípios do desenvolvimento orientado a testes é "Nunca teste mais de uma classe de cada vez". Significando que você deve usar ICsvRecord
zombarias ou stubs em vez de instâncias reais de CsvRecord
.
No entanto, depois de tentar essa abordagem, notei que zombar da CsvRecord
classe pode ficar um pouco peludo. O que me leva a uma das duas conclusões:
- É difícil escrever testes de unidade! Isso é um cheiro de código! Refatorar!
- Zombar de todas as dependências é irracional.
Quando substituí minhas zombarias por CsvRecord
instâncias reais , as coisas foram muito mais tranqüilas. Ao procurar os pensamentos de outras pessoas, me deparei com este post do blog , que parece apoiar o item 2 acima. Para objetos naturalmente acoplados, não devemos nos preocupar tanto com zombaria.
Estou fora dos trilhos? Existem desvantagens na suposição nº 2 acima? Eu deveria realmente estar pensando em refatorar meu design?
Respostas:
Se você realmente precisa de coordenação entre essas duas classes, escreva uma
CsvCoordinator
classe que encapsule suas duas classes e teste-a.No entanto, discuto a noção que
CsvRecord
não é testável de forma independente.CsvRecord
é basicamente uma classe de DTO , não é? É apenas uma coleção de campos, talvez com alguns métodos auxiliares. ECsvRecord
pode ser usado em outros contextos alémCsvFile
; você pode ter uma coleção ou matriz deCsvRecord
s, por exemplo.Teste
CsvRecord
primeiro. Certifique-se de que ele passe em todos os seus testes. Em seguida, vá em frente e useCsvRecord
suaCsvFile
classe durante o teste. Use-o como um esboço / simulação pré-testado; preencha-o com dados de teste relevantes, passe-o paraCsvFile
e escreva seus casos de teste contra isso.fonte
CsvRecord
quebra, então obviamenteCsvData
falha; mas tudo bem, porque você faz o testeCsvRecord
primeiro e, se isso falhar, seusCsvFile
testes não terão sentido. Você ainda pode distinguir entre erros dentroCsvRecord
e dentroCsvFile
.O motivo para testar uma classe de cada vez é que você não deseja que os testes de uma classe tenham dependências no comportamento de uma segunda classe. Isso significa que, se o seu teste para a Classe A exercer alguma das funcionalidades da Classe B, você deverá zombar da Classe B para remover a dependência de determinadas funcionalidades da Classe B.
Uma classe como
CsvRecord
me parece ser principalmente para armazenamento de dados - não é uma classe com muita funcionalidade própria. Ou seja, pode ter construtores, getters, setters, mas nenhum método com lógica substancial real. Claro, acho que sim - talvez você tenha escrito uma classe chamadaCsvRecord
que faz vários cálculos complexos.Mas se
CsvRecord
não tiver uma lógica real própria, não há nada a ganhar com zombaria dela. Esta é realmente apenas a máxima antiga - "não zombe de objetos de valor" .Portanto, ao considerar a possibilidade de zombar de uma classe específica (para um teste de uma classe diferente), você deve levar em conta quanto de sua própria lógica essa classe possui e quanto dessa lógica será executada no decorrer de seu teste.
fonte
No. 2 está bem. As coisas podem ser e devem ser fortemente acopladas se seus conceitos forem fortemente acoplados. Isso deve ser raro e geralmente evitado, mas no exemplo que você forneceu, faz sentido.
fonte
As classes "acopladas" são mutuamente dependentes uma da outra. Esse não deve ser o caso no que você está descrevendo - um CsvRecord realmente não deve se importar com o CsvFile que o contém, portanto, a dependência segue apenas um caminho. Isso é bom e não é um acoplamento apertado.
Afinal, se uma classe contiver a variável String name, você não afirmaria que está fortemente associado à String, não é?
Portanto, teste de unidade o CsvRecord para o comportamento desejado.
Em seguida, use uma estrutura de simulação (o Mockito é ótimo) para testar se sua unidade está interagindo com os objetos dos quais depende corretamente. O comportamento que você deseja testar, realmente - é que o CsvFile manipula CsvRcords da maneira esperada. O funcionamento interno do CvsRecord não deve importar - é como o CvsFile se comunica com ele.
Finalmente, o TDD não é apenas sobre testes de unidade. Você certamente pode (e deve) começar com testes funcionais que analisam o comportamento funcional de como seus componentes maiores funcionam - ou seja, sua história ou cenário do usuário. Os testes de suas unidades definem as expectativas e verificam as peças; os testes funcionais fazem o mesmo para o todo.
fonte
CsvFile
está fortemente acoplado aCsvRecord
(mas não o contrário). O OP pergunta se é uma boa idéia testar testando-oCsvFile
dissociando-o deCsvRecord
umICsvRecord
, e não vice-versa.CsvFile
depende de quanto depende do funcionamento internoCsvRecord
, ou seja, da quantidade de suposições que o arquivo tem sobre o registro. As interfaces ajudam a documentar e aplicar essas suposições (ou melhor, a ausência de outras suposições), mas a quantidade de acoplamento permanece a mesma, exceto que, com uma interface, você pode conectar uma classe de registro diferenteCsvFile
. A introdução da interface apenas para você dizer que reduziu o acoplamento é bobagem.Há realmente duas perguntas aqui. A primeira é se existem situações em que zombar de um objeto é desaconselhável. Isso é indubitavelmente verdade, como mostra as outras excelentes respostas. A segunda pergunta é se o seu caso específico é uma dessas situações. Sobre essa questão, não estou convencido.
Provavelmente, o motivo mais comum para não zombar de uma classe é se é uma classe de valor. No entanto, você deve examinar o motivo por trás da regra. Não é porque a classe ridicularizada seja ruim de alguma forma, é porque será essencialmente idêntica à original. Se fosse esse o caso, seu teste de unidade não seria mais fácil usando a classe original.
Pode muito bem ser que seu código seja uma das raras exceções em que a refatoração não ajudaria, mas você deve declara-lo apenas depois que esforços diligentes de refatoração não funcionarem. Até desenvolvedores experientes podem ter problemas para encontrar alternativas ao seu próprio design. Se você não conseguir pensar em uma maneira possível de aprimorá-lo, peça a alguém experiente para dar uma segunda olhada.
A maioria das pessoas parece estar assumindo que você
CsvRecord
é uma classe de valor. Tente fazer um. Torne imutável, se puder. Se você tiver dois objetos com ponteiros um para o outro, remova um deles e descubra como fazê-lo funcionar. Procure lugares para dividir classes e funções. O melhor local para dividir uma classe nem sempre pode corresponder ao layout físico do arquivo. Tente reverter o relacionamento pai / filho das classes. Talvez você precise de uma classe separada para ler e gravar arquivos csv. Talvez você precise de classes separadas para manipular a E / S do arquivo e a interface para as camadas superiores. Há muitas coisas para tentar antes de declarar isso não-refatorável.fonte