Esta pregunta me ha molestado durante unos días y parece que varias prácticas se contradicen entre sí.
Ejemplo
Iteración 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;
}
}
Ahora, al probar esto, falsificaría IFooConnection e IBarConnection, y usaría Inyección de dependencia (DI) al crear instancias de FooDao.
Puedo cambiar la implementación, sin cambiar la funcionalidad.
Iteración 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);
}
}
Ahora, no escribiré este constructor, pero imagina que hace lo mismo que FooDao hizo antes. Esto es solo una refactorización, por lo que, obviamente, esto no cambia la funcionalidad, por lo que mi prueba aún pasa.
IFooBuilder es interno, ya que solo existe para trabajar para la biblioteca, es decir, no es parte de la API.
El único problema es que ya no cumplo con la Inversión de dependencia. Si reescribí esto para solucionar ese problema, podría verse así.
Iteración 3
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooBuilder fooBuilder)
{
this.fooBuilder = fooBuilder;
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Esto debería hacerlo. He cambiado el constructor, por lo que mi prueba debe cambiar para admitir esto (o al revés), sin embargo, este no es mi problema.
Para que esto funcione con DI, FooBuilder e IFooBuilder deben ser públicos. Esto significa que debería escribir una prueba para FooBuilder, ya que de repente se convirtió en parte de la API de mi biblioteca. Mi problema es que los clientes de la biblioteca solo deberían usarla a través de mi diseño de API previsto, IFooDao, y mis pruebas actúan como clientes. Si no sigo la Inversión de dependencia, mis pruebas y API están más limpios.
En otras palabras, todo lo que me importa como cliente, o como prueba, es obtener el Foo correcto, no cómo se construye.
Soluciones
¿Debería simplemente no importarme, escribir las pruebas para FooBuilder aunque solo sea público para complacer a DI? - Soporta iteración 3
¿Debo darme cuenta de que expandir la API es una desventaja de la Inversión de dependencia, y tener muy claro por qué elegí no cumplir con esto aquí? - Soporta la iteración 2
¿Pongo demasiado énfasis en tener una API limpia? - Soporta iteración 3
EDITAR: Quiero dejar en claro que mi problema no es "¿Cómo probar las partes internas?", Sino algo como "¿Puedo mantenerlo interno y seguir cumpliendo con DIP, y debería?".
IFooBuildernecesita ser público? FooBuildersolo necesita exponerse al sistema DI a través de algún tipo de método de "obtención de implementación predeterminada" que devuelve un IFooBuildery, por lo tanto, puede permanecer interno.
publicAPI de la biblioteca y las publicpruebas". O en la otra forma "Quiero mantener los métodos privados para evitar que aparezcan en la API, ¿cómo pruebo esos métodos privados?". Las respuestas son siempre "vivir con la API pública más amplia", "vivir con pruebas a través de la API pública" o "crear métodos internaly hacerlos visibles para el código de prueba".