¿Cómo se prueban unitariamente los métodos privados?
Estoy construyendo una biblioteca de clases que tendrá algunos métodos públicos y privados. Quiero poder realizar pruebas unitarias de los métodos privados (principalmente durante el desarrollo, pero también podría ser útil para futuras refactorizaciones).
¿Cuál es la forma correcta de hacer esto?
Si desea realizar una prueba unitaria de un método privado, es posible que algo esté mal. Las pruebas unitarias están (en términos generales) destinadas a probar la interfaz de una clase, es decir, sus métodos públicos (y protegidos). Por supuesto, puedes "piratear" una solución para esto (aunque solo sea haciendo públicos los métodos), pero también puedes considerar:
- Si realmente vale la pena probar el método que desea probar, puede que valga la pena moverlo a su propia clase.
- Agregue más pruebas a los métodos públicos que llaman al método privado, probando la funcionalidad del método privado. (Como indicaron los comentaristas, solo debe hacer esto si la funcionalidad de estos métodos privados es realmente parte de la interfaz pública. Si realmente realizan funciones que están ocultas para el usuario (es decir, la prueba unitaria), esto probablemente sea malo).
Si está utilizando .net, debe utilizar InternalsVisibleToAttribute .
Puede que no resulte útil probar métodos privados. Sin embargo, a veces también me gusta llamar a métodos privados desde métodos de prueba. La mayoría de las veces, para evitar la duplicación de código para la generación de datos de prueba...
Microsoft proporciona dos mecanismos para esto:
Accesorios
- Ir al código fuente de la definición de clase.
- Haga clic derecho en el nombre de la clase.
- Elija "Crear acceso privado"
- Elija el proyecto en el que se debe crear el descriptor de acceso => Terminará con una nueva clase con el nombre foo_accessor. Esta clase se generará dinámicamente durante la compilación y proporcionará acceso público a todos los miembros.
Sin embargo, el mecanismo es a veces un poco intratable cuando se trata de cambios en la interfaz de la clase original. Entonces, la mayoría de las veces evito usar esto.
Clase PrivateObject La otra forma es usar Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject
// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );
// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );
// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
No estoy de acuerdo con la filosofía de "solo debería interesarte probar la interfaz externa". Es un poco como decir que un taller de reparación de automóviles sólo debería hacer pruebas para ver si las ruedas giran. Sí, en última instancia, estoy interesado en el comportamiento externo, pero me gusta que mis pruebas internas, privadas, sean un poco más específicas y vayan al grano. Sí, si refactorizo, es posible que tenga que cambiar algunas de las pruebas, pero a menos que sea una refactorización masiva, solo tendré que cambiar algunas y el hecho de que las otras pruebas internas (sin cambios) sigan funcionando es un gran indicador de que la refactorización ha sido exitosa.
Puede intentar cubrir todos los casos internos usando solo la interfaz pública y, en teoría, es posible probar todos los métodos internos (o al menos todos los que importan) completamente usando la interfaz pública, pero es posible que tenga que terminar de cabeza para lograrlo. Esto y la conexión entre los casos de prueba que se ejecutan a través de la interfaz pública y la parte interna de la solución para la que están diseñados pueden ser difíciles o imposibles de discernir. Habiendo señalado, las pruebas individuales que garantizan que la maquinaria interna funciona correctamente bien valen los pequeños cambios de prueba que se producen con la refactorización; al menos esa ha sido mi experiencia. Si tiene que realizar grandes cambios en sus pruebas para cada refactorización, entonces tal vez esto no tenga sentido, pero en ese caso, tal vez debería repensar su diseño por completo. Un buen diseño debe ser lo suficientemente flexible como para permitir la mayoría de los cambios sin rediseños masivos.
En los raros casos en los que he querido probar funciones privadas, normalmente las he modificado para que estén protegidas y he escrito una subclase con una función contenedora pública.
La clase:
...
protected void APrivateFunction()
{
...
}
...
Subclase para pruebas:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...