A Injeção de Dependência vale a pena fora do UnitTesting

17

Dado um construtor que nunca terá que usar implementações diferentes de vários objetos que ele inicializa, ainda é prático usar o DI? Afinal, ainda podemos querer testar a unidade.

A classe em questão inicializa algumas outras classes em seu construtor e as classes que ele usa são bastante específicas. Ele nunca usará outra implementação. Estamos justificados em evitar tentar programar para uma interface?

Seph
fonte
3
KISS - é sempre a melhor abordagem.
Reactgular
5
Na minha opinião, esta questão é mais específica que a duplicata proposta. Design para futuras-changes-ou-resolvê-the-problema na mão , voto Portanto i para não fechar este
k3b
1
A testabilidade não é motivo suficiente?
ConditionRacer
Realmente não soa como duplicado, mas responde a si mesmo. Se isso nunca acontecer, projetar para ele não é projetar para mudanças futuras. Até os astronautas da arquitetura projetam apenas mudanças que nunca acontecerão por mau julgamento.
Steve314

Respostas:

13

Depende de como você está certo de que "nunca, nunca" está correto (durante o tempo em que seu aplicativo será usado).

Em geral:

  • Não modifique seu design apenas para testabilidade. Os testes de unidade estão lá para atendê-lo, não vice-versa.
  • Não se repita. Criar uma interface que terá apenas um herdeiro é inútil.
  • Se uma classe não é testável, provavelmente também não é flexível / extensível para casos de uso reais. Às vezes, tudo bem - é improvável que você precise dessa flexibilidade, mas simplicidade / confiabilidade / manutenção / desempenho / o que for mais importante.
  • Se você não souber, codifique para a interface. Alteração de requisitos.
Telastyn
fonte
12
Quase deu -1 para "não modifique seu design apenas para testabilidade". Ganhar testabilidade é IMHO uma das principais razões para mudar um design! Mas seus outros pontos estão bem.
Doc Brown
5
@docBrown - (e outros) Eu difero aqui de muitos, e talvez até da maioria. ~ 95% do tempo, tornar as coisas testáveis ​​também as torna flexíveis ou extensíveis. Você deve ter isso no seu design (ou adicioná-lo ao seu design) na maior parte do tempo - a testabilidade deve ser danificada. Os outros ~ 5% têm requisitos estranhos que impedem esse tipo de flexibilidade em favor de outra coisa, ou são tão essenciais / imutáveis ​​que você descobrirá rapidamente se estiver quebrado, mesmo sem testes dedicados.
Telastyn
4
pode ter certeza, mas sua primeira declaração acima pode ser mal interpretada com muita facilidade como "deixe código mal projetado como é quando sua única preocupação é a testabilidade".
Doc Brown
1
Por que você diria "Criar uma interface que terá apenas um herdeiro é inútil"? A interface pode funcionar como um contrato.
Kaptan
2
@ kaptan - porque o objeto concreto pode funcionar tão bem quanto um contrato. Fazer dois significa que você precisa escrever o código duas vezes (e modificá-lo duas vezes quando ele mudar). Concedido, a grande maioria das interfaces terá várias implementações (sem teste) ou você precisará se preparar para essa eventualidade. Mas, para aquele caso raro em que tudo que você precisa é de um objeto selado simples, use-o diretamente.
Telastyn 23/09
12

Estamos justificados em evitar tentar programar para uma interface?

Embora a vantagem de codificar contra uma interface seja bastante evidente, você deve se perguntar o que exatamente é ganho por não fazer isso.

A próxima pergunta é: faz parte da responsabilidade da classe em questão escolher as implementações ou não? Esse pode ou não ser o caso e você deve agir em conformidade.

Por fim, sempre existe o potencial de surgir alguma restrição pateta do nada. Você pode executar o mesmo trecho de código simultaneamente, e a possibilidade de injetar a implementação pode ajudar na sincronização. Ou depois de criar um perfil do seu aplicativo, você pode descobrir que deseja uma estratégia de alocação diferente da instanciação simples. Ou surgem preocupações transversais e você não tem suporte à AOP em mãos. Quem sabe?

YAGNI sugere que, ao escrever código, é importante não adicionar nada supérfluo. Uma das coisas que os programadores tendem a adicionar ao seu código sem nem mesmo estar cientes disso são suposições supérfluas. Como "esse método pode ser útil" ou "isso nunca vai mudar". Ambos adicionam confusão ao seu design.

back2dos
fonte
2
+1 por citar YAGNI aqui como argumento para DI, não contra.
Doc Brown
4
@ DocBrown: Considerando que o YAGNI pode ser usado aqui, também para justificar o não uso do DI. Eu acho que é mais um argumento contra YAGNI ser uma medida útil para qualquer coisa.
Steven Evers
1
+1 para suposições supérfluos ser incluído no YAGNI, que nos têm atingido na cabeça algumas vezes
Izkata
@SteveEvers: Eu acho que usar o YAGNI para justificar ignorar o princípio de inversão de dependência impede uma falta de entendimento do YAGNI ou DIP ou talvez até alguns dos princípios mais fundamentais de programação e raciocínio. Você só deve ignorar o DIP se precisar - uma circunstância em que o YAGNI simplesmente não é aplicável por sua própria natureza.
back2dos
@ back2dos: A suposição de que o DI é útil por causa de 'talvez requisitos' e "quem sabe?" é precisamente a linha de pensamento que o YAGNI pretende combater, em vez disso, você o está usando como justificativa para ferramentas e infraestrutura adicionais.
Steven Evers
7

Depende de vários parâmetros, mas aqui estão algumas razões pelas quais você ainda pode querer usar o DI:

  • o teste é uma boa razão em si, acredito
  • as classes exigidas pelo seu objeto podem ser usadas em outros lugares - se você as injetar, ele permitirá agrupar os recursos (por exemplo, se as classes em questão forem threads ou conexões com o banco de dados - você não deseja que cada uma de suas classes crie novos conexões)
  • se a inicialização desses outros objetos falhar, o código mais alto da pilha provavelmente será melhor para lidar com problemas do que a sua classe, que provavelmente simplesmente encaminhará os erros

Conclusão: você provavelmente pode viver sem o DI nesse caso, mas há chances de que o uso do DI termine em um código mais limpo.

assylias
fonte
3

Ao projetar um programa ou recurso, parte do processo de pensamento deve ser como você o testará. A injeção de dependência é uma das ferramentas de teste e deve ser considerada como parte do mix.

Os benefícios adicionais da Injeção de Dependências e do uso de interfaces em vez de classes também são benéficos quando um aplicativo é estendido, mas também podem ser adaptados ao código-fonte posteriormente com bastante facilidade e segurança usando a pesquisa e substituição. - mesmo em projetos muito grandes.

Michael Shaw
fonte
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Muitas respostas a esta pergunta podem ser debatidas como "você pode precisar porque ..." como YAGNI se você não quiser fazer uma desavença.

Se você está perguntando por que motivos, além dos unittests, é necessário programar para uma interface

Sim , a injeção de dependência vale a pena fora do UnitTesting se você precisar de inversão de controle . Por exemplo, se uma implementação de módulo precisar de outro módulo que não possa ser acessado em sua camada.

Exemplo: se você possui os módulos gui=> businesslayer=> driver=> common.

Neste cenário businesslayerpode usar, drivermas drivernão pode usar businesslayer.

Se driverprecisar de alguma funcionalidade de nível superior, você pode implementar essa funcionalidade na businesslayerqual implementa uma interface na commoncamada. driversó precisa conhecer a interface na commoncamada.

k3b
fonte
1

Sim, você é: primeiro, esqueça o teste de unidade como uma razão para projetar seu código com base nas ferramentas de teste de unidade; nunca é uma boa idéia dobrar seu design de código para se ajustar a uma restrição artificial. Se suas ferramentas forçarem você a fazer isso, obtenha ferramentas melhores (por exemplo, Microsoft Fakes / Moles que permitem muito mais opções para criar objetos simulados).

Por exemplo, você dividiria suas aulas em métodos apenas públicos apenas porque as ferramentas de teste não funcionam com métodos particulares? (Eu sei que a sabedoria predominante é fingir que você não precisa testar métodos particulares, mas acho que isso é uma reação à dificuldade de fazê-lo com as ferramentas atuais, e não uma reação genuína à falta de testes particulares).,

Em suma, tudo se resume a que tipo de TDDer você é - o "zombador" como Fowler os descreve , precisa alterar o código para se adequar às ferramentas que eles usam, enquanto os testadores "clássicos" criam testes que são mais integrados à natureza (ou seja, teste a classe como uma unidade, não cada método), para que haja menos necessidade de interfaces, especialmente se você usar as ferramentas que podem simular classes concretas.

gbjbaanb
fonte
3
Não acho que a sabedoria predominante de que métodos privados não devam ser testados tenha algo a ver com a dificuldade de testá-los. Se houve um erro em um método privado, mas não afetou o comportamento do objeto, não afetou nada. Se houve um erro em um método privado que afetou o comportamento do objeto, há um teste com falha ou ausente em pelo menos um dos métodos públicos de um objeto.
Michael Shaw
Isso se resume ao fato de você testar o comportamento ou o estado. Os métodos privados precisam ser testados de alguma forma, obviamente, mas se você é um profissional de TDD clássico, você os testará testando a classe "como um todo", as ferramentas de teste que a maioria das pessoas usa se concentram no teste de cada método individualmente ... e meu argumento é que os últimos não estão efetivamente testando corretamente, pois perdem a chance de realizar um teste completo. Então, eu concordo com você, ao tentar destacar como o TDD foi subvertido pela forma como algumas (a maioria?) Das pessoas fazem hoje testes de unidade.
Gbjbaanb
1

A injeção de dependência também deixa mais claro que o seu objeto possui dependências.

Quando outra pessoa usa seu objeto ingenuamente (sem olhar para o construtor), eles descobrirão que precisarão configurar serviços, conexões etc. Isso não era óbvio, porque eles não precisavam repassá-los ao construtor. . Passar nas dependências torna mais claro o que é necessário.

Você também está ocultando o fato de que sua classe pode estar violando o SRP. Se você está passando em muitas dependências (mais de 3), sua classe pode estar fazendo muito e deve ser refatorada. Mas se você os estiver criando no construtor, esse cheiro de código será oculto.

Schleis
fonte
0

Concordo com os dois campos aqui e, na minha opinião, o assunto ainda está aberto para debate. YAGNI exige que eu não tente mudar meu código para se ajustar à suposição. O teste de unidade não é um problema aqui, porque acredito firmemente que a dependência nunca mudará. Mas, se eu estivesse testando a unidade como pretendia, nunca teria chegado a esse ponto. Como eu acabaria entrando no DI eventualmente.

Deixe-me oferecer outra alternativa para o meu cenário especificamente

Com um padrão de fábrica, posso localizar dependências em um só lugar. Ao testar, posso alterar a implementação com base no contexto, e isso é bom o suficiente. Isso não vai contra o YAGNI por causa dos benefícios de abstrair várias construções de classe.

Seph
fonte