Essa pergunta me incomoda há alguns dias e parece que várias práticas se contradizem.
Exemplo
Iteração 1
public class FooDao : IFooDao
{
private IFooConnection fooConnection;
private IBarConnection barConnection;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooConnection = fooConnection;
this.barConnection = barConnection;
}
public Foo GetFoo(int id)
{
Foo foo = fooConection.get(id);
Bar bar = barConnection.get(foo);
foo.bar = bar;
return foo;
}
}
Agora, ao testar isso, eu falsificaria o IFooConnection e o IBarConnection e usaria a Injection de Dependência (DI) ao instanciar o FooDao.
Eu posso alterar a implementação, sem alterar a funcionalidade.
Iteração 2
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooBuilder = new FooBuilder(fooConnection, barConnection);
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Agora, não vou escrever este construtor, mas imagine que ele faça a mesma coisa que o FooDao fez antes. Isso é apenas uma refatoração, portanto, obviamente, isso não altera a funcionalidade e, portanto, meu teste ainda passa.
O IFooBuilder é Interno, pois só existe para trabalhar na biblioteca, ou seja, não faz parte da API.
O único problema é que não estou mais em conformidade com a Inversão de Dependências. Se eu reescrevi isso para corrigir esse problema, pode ser assim.
Iteração 3
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooBuilder fooBuilder)
{
this.fooBuilder = fooBuilder;
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Isso deve servir. Eu mudei o construtor, então meu teste precisa mudar para suportar isso (ou o contrário), mas esse não é o meu problema.
Para que isso funcione com o DI, o FooBuilder e o IFooBuilder precisam ser públicos. Isso significa que eu deveria escrever um teste para o FooBuilder, pois de repente ele se tornou parte da API da minha biblioteca. Meu problema é que os clientes da biblioteca só devem usá-lo através do meu design de API pretendido, o IFooDao, e meus testes atuam como clientes. Se eu não seguir a Inversão de dependência, meus testes e API estarão mais limpos.
Em outras palavras, tudo o que me interessa como cliente ou como teste é obter o Foo correto, não como ele é construído.
Soluções
Simplesmente não devo me importar, escreva os testes para o FooBuilder, mesmo que seja apenas público agradar a DI? - Suporta iteração 3
Devo perceber que expandir a API é uma desvantagem da Inversão de Dependência e ser muito claro sobre o motivo pelo qual optei por não cumpri-la aqui? - Suporta iteração 2
Coloco muita ênfase em ter uma API limpa? - Suporta iteração 3
EDIT: Quero deixar claro que meu problema não é "Como testar os internos?", Mas sim algo como "Posso mantê-lo interno e ainda estar em conformidade com o DIP, devo?".
IFooBuilder
precisa ser público? FooBuilder
só precisa ser exposto ao sistema DI através de algum tipo de método "obter implementação padrão" que retorne um IFooBuilder
e assim possa permanecer interno.
public
APIs de biblioteca e public
testes". Ou na outra forma "Desejo manter os métodos privados para evitar que eles apareçam na API, como faço para testar esses métodos particulares?". As respostas são sempre "viva com a API pública mais ampla", "viva com testes pela API pública" ou "torne métodos internal
e torne-os visíveis para o código de teste".