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?".
IFooBuilderprecisa ser público? FooBuildersó precisa ser exposto ao sistema DI através de algum tipo de método "obter implementação padrão" que retorne um IFooBuildere assim possa permanecer interno.
publicAPIs de biblioteca e publictestes". 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 internale torne-os visíveis para o código de teste".