Eu li que usar "novo" em um construtor (para outros objetos que não sejam de valor simples) é uma prática ruim, pois torna impossível o teste de unidade (pois esses colaboradores também precisam ser criados e não podem ser ridicularizados). Como não tenho muita experiência em testes de unidade, estou tentando reunir algumas regras que aprenderei primeiro. Além disso, essa regra é geralmente válida, independentemente do idioma usado?
unit-testing
constructors
Ezoela Vacca
fonte
fonte
new
significa coisas diferentes em diferentes idiomas.new
palavra - chave. Sobre quais idiomas você está perguntando?Respostas:
Sempre há exceções, e eu discordo do 'sempre' no título, mas sim, essa diretriz geralmente é válida e também se aplica fora do construtor.
O uso de new em um construtor viola o D no SOLID (princípio de inversão de dependência). Isso torna seu código difícil de testar, porque o teste de unidade tem tudo a ver com isolamento; é difícil isolar a classe se ela tiver referências concretas.
Não se trata apenas de testes de unidade. E se eu quiser apontar um repositório para dois bancos de dados diferentes ao mesmo tempo? A capacidade de passar no meu próprio contexto me permite instanciar dois repositórios diferentes apontando para locais diferentes.
Não usar new no construtor torna seu código mais flexível. Isso também se aplica a idiomas que podem usar construções diferentes
new
da inicialização do objeto.No entanto, claramente, você precisa usar o bom senso. Há muitas vezes em que é bom usar
new
ou onde seria melhor não usar , mas você não terá consequências negativas. Em algum momento em algum lugar,new
tem que ser chamado. Apenas tenha muito cuidado ao chamarnew
dentro de uma classe da qual muitas outras classes dependem.Fazer algo como inicializar uma coleção particular vazia em seu construtor é bom, e injetar seria absurdo.
Quanto mais referências uma classe tiver, mais cuidado você deve ter para não ligar
new
de dentro dela.fonte
head = null
alguma forma melhorar o código? Por que deixar coleções incluídas como nulas e criá-las sob demanda é melhor do que fazê-lo no construtor?Embora eu seja a favor de usar o construtor apenas para inicializar a nova instância, em vez de criar vários outros objetos, os objetos auxiliares estão ok e você deve usar seu julgamento para determinar se algo é ou não um auxiliar interno.
Se a classe representa uma coleção, ela pode ter uma matriz auxiliar interna ou lista ou hashset. Seria usado
new
para criar esses auxiliares e seria considerado bastante normal. A classe não oferece injeção para usar ajudantes internos diferentes e não tem motivos para isso. Nesse caso, você deseja testar os métodos públicos do objeto, que podem acumular, remover e substituir elementos na coleção.Em certo sentido, a construção de classe de uma linguagem de programação é um mecanismo para criar abstrações de nível superior, e criamos essas abstrações para preencher a lacuna entre o domínio do problema e as primitivas da linguagem de programação. No entanto, o mecanismo de classe é apenas uma ferramenta; varia de acordo com a linguagem de programação e, em algumas linguagens, algumas abstrações de domínio simplesmente requerem vários objetos no nível da linguagem de programação.
Em resumo, você deve julgar se a abstração requer apenas um ou mais objetos internos / auxiliares, enquanto continua sendo vista pelo chamador como uma única abstração ou se os outros objetos estariam melhor expostos ao chamador para criar para controle de dependências, o que seria sugerido, por exemplo, quando o chamador vê esses outros objetos usando a classe
fonte
Nem todos os colaboradores são interessantes o suficiente para fazer o teste unitário separadamente; você pode (indiretamente) testá-los através da classe de hospedagem / instanciação. Isso pode não estar alinhado com a idéia de algumas pessoas de precisar testar cada classe, cada método público, etc., especialmente ao fazer o teste depois. Ao usar o TDD, você pode refatorar esse 'colaborador', extraindo uma classe onde ela já está totalmente em teste desde o primeiro processo de teste.
fonte
Not all collaborators are interesting enough to unit-test separately
fim da história :-), este caso é possível e ninguém se atreveu a mencionar. @Joppe Convido você a elaborar a resposta um pouco. Por exemplo, você pode adicionar alguns exemplos de classes que são meros detalhes de implementação (não adequados para substituição) e como elas podem ser extraídas posteriormente, se considerarmos necessário.new File()
para fazer algo relacionado a arquivos, não há sentido em proibir essa chamada. O que você vai fazer, escrever testes de regressão noFile
módulo stdlib ? Não é provável. Por outro lado, convocarnew
uma classe auto-inventada é mais duvidosa.new File()
, no entanto.Tenha cuidado ao aprender "regras" para problemas que você nunca encontrou. Se você se deparar com alguma "regra" ou "melhor prática", sugiro encontrar um exemplo simples de brinquedo onde essa regra "deve" ser usada e tentar resolver esse problema sozinho , ignorando o que a "regra" diz.
Nesse caso, você pode tentar criar 2 ou 3 classes simples e alguns comportamentos que eles devem implementar. Implemente as classes da maneira que parecer natural e escreva um teste de unidade para cada comportamento. Faça uma lista de todos os problemas que você encontrou, por exemplo, se você começou com as coisas funcionando de uma maneira, teve que voltar e mudar mais tarde; se você ficou confuso sobre como as coisas devem se encaixar; se você se aborreceu ao escrever clichê; etc.
Em seguida, tente resolver o mesmo problema seguindo a "regra". Mais uma vez, faça uma lista dos problemas que encontrou. Compare as listas e pense em quais situações podem ser melhores ao seguir a regra e quais não.
Quanto à sua pergunta real, eu tendem a favorecer uma abordagem de portas e adaptadores , onde fazemos uma distinção entre "lógica principal" e "serviços" (isso é semelhante à distinção entre funções puras e procedimentos eficazes).
A lógica principal é calcular as coisas "dentro" do aplicativo, com base no domínio do problema. Ela pode conter classes como
User
,Document
,Order
,Invoice
, do etc. É bom ter classes principais chamarnew
para outras classes principais, já que eles são detalhes "internas" de implementação. Por exemplo, criar umOrder
também pode criar umInvoice
e umDocument
detalhamento do que foi solicitado. Não é necessário zombar deles durante os testes, porque essas são as coisas reais que queremos testar!As portas e os adaptadores são como a lógica principal interage com o mundo externo. Isto é onde as coisas como
Database
,ConfigFile
,EmailSender
, etc. viver. Essas são as coisas que dificultam o teste; portanto, é aconselhável criá-las fora da lógica principal e transmiti-las conforme necessário (com injeção de dependência ou como argumentos de método etc.).Dessa forma, a lógica principal (que é a parte específica do aplicativo, onde a importante lógica de negócios vive e está sujeita à maior rotatividade) pode ser testada sozinha, sem a necessidade de se preocupar com bancos de dados, arquivos, emails, etc. Podemos apenas passar alguns valores de exemplo e verificar se obtemos os valores de saída corretos.
As portas e os adaptadores podem ser testados separadamente, usando simulações para o banco de dados, sistema de arquivos, etc. sem precisar se preocupar com a lógica de negócios. Podemos apenas passar alguns valores de exemplo e garantir que eles estejam sendo armazenados / lidos / enviados / etc. adequadamente.
fonte
Permita-me responder à pergunta, reunindo o que considero os pontos-chave aqui. Vou citar alguns usuários por questões de concisão.
Sim, o uso de
new
construtores internos geralmente leva a falhas de projeto (por exemplo, acoplamento rígido), o que torna nosso projeto rígido. Difícil de testar sim, mas não impossível. A propriedade em jogo aqui é a resiliência (tolerância a alterações) 1 .No entanto, a citação acima nem sempre é verdadeira. Em alguns casos, pode haver classes que devem ser fortemente acopladas . David Arno comentou um casal.
Exatamente. Algumas classes (por exemplo, classes internas) podem ser meros detalhes de implementação da classe principal. Eles devem ser testados juntamente com a classe principal e não são necessariamente substituíveis ou extensíveis.
Além disso, se nosso culto ao SOLID nos faz extrair essas classes , poderíamos estar violando outro bom princípio. A chamada Lei de Deméter . O que, por outro lado, acho realmente importante do ponto de vista do design.
Portanto, a resposta provável, como sempre, é depende . Usar
new
construtores internos pode ser uma má prática. Mas nem sempre sistematicamente.Portanto, é necessário avaliar se as classes são detalhes de implementação (na maioria dos casos, não serão) da classe principal. Se estiverem, deixe-os em paz. Caso contrário, considere técnicas como Root de Composição ou Injeção de Dependência por Contêineres IoC .
1: o principal objetivo do SOLID não é tornar nosso código mais testável. É para tornar nosso código mais tolerante a alterações. Mais flexível e, consequentemente, mais fácil de testar
Nota: David Arno, TheWhisperCat, espero que você não se importe de ter citado você.
fonte
Como um exemplo simples, considere o seguinte pseudocódigo
Uma vez que o
new
é um detalhe de implementação do puraFoo
, e ambosFoo::Bar
eFoo::Baz
são parte deFoo
, quando a unidade de teste deFoo
não há nenhum ponto em zombando partesFoo
. Você zomba apenas das peças externasFoo
quando faz o teste de unidadeFoo
.fonte
Sim, usar 'novo' nas classes raiz do aplicativo é um cheiro de código. Isso significa que você está bloqueando a classe para usar uma implementação específica e não poderá substituir outra. Sempre opte por injetar a dependência no construtor. Dessa forma, você não apenas poderá injetar facilmente dependências simuladas durante o teste, como também tornará seu aplicativo muito mais flexível, permitindo que você substitua rapidamente diferentes implementações, se necessário.
EDIT: Para downvoters - aqui está um link para um livro de desenvolvimento de software sinalizando 'novo' como um possível cheiro de código: https://books.google.com/books?id=18SuDgAAQBAJ&lpg=PT169&dq=new%20keyword%20code%20smell&pg=PT169 # v = onepage & q = new% 20keyword% 20code% 20smell & f = false
fonte
Yes, using 'new' in your non-root classes is a code smell. It means you are locking the class into using a specific implementation, and will not be able to substitute another.
Por que isso é um problema? Não cada dependência única em uma árvore de dependência deve ser aberto a substituiçãonew
palavra-chave não é uma prática ruim e nunca foi. É como você usa a ferramenta que importa. Você não usa uma marreta, por exemplo, onde um martelo de bola é suficiente.