Unidad que prueba métodos privados en C #

291

Visual Studio permite pruebas unitarias de métodos privados a través de una clase de acceso generada automáticamente. He escrito una prueba de un método privado que se compila con éxito, pero falla en tiempo de ejecución. Una versión bastante mínima del código y la prueba es:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

El error de tiempo de ejecución es:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

De acuerdo con intellisense, y por lo tanto supongo que el compilador, el destino es de tipo TypeA_Accessor. Pero en tiempo de ejecución es de tipo TypeA, y por lo tanto, la lista de agregar falla.

¿Hay alguna forma de detener este error? O, quizás más probablemente, qué otro consejo tienen otras personas (predigo que tal vez "no pruebe métodos privados" y "no haga que las pruebas unitarias manipulen el estado de los objetos").

junichiro
fuente
Necesita un descriptor de acceso para la clase privada TypeB. Accessor TypeA_Accessor proporciona acceso a métodos privados y protegidos de TypeA. Sin embargo, TypeB no es un método. Es una clase
Dima
Accessor proporciona acceso a métodos privados / protegidos, miembros, propiedades y eventos. No proporciona acceso a clases privadas / protegidas dentro de su clase. Y las clases privadas / protegidas (Tipo B) están destinadas a ser utilizadas solo por métodos de propiedad de la clase (Tipo A). Básicamente, está intentando agregar una clase privada (Tipo B) desde fuera de Tipo A a "myList", que es privada. Como está utilizando el descriptor de acceso, no hay ningún problema para acceder a myList. Sin embargo, no puede usar TypeB a través del descriptor de acceso. La solución posible sería mover el Tipo B fuera del Tipo A. Pero puede romper su diseño.
Dima
Siente que la prueba de métodos privados debe hacerse por el siguiente stackoverflow.com/questions/250692/…
nate_weldon

Respuestas:

274

Sí, no pruebe métodos privados ... La idea de una prueba unitaria es probar la unidad mediante su 'API' pública.

Si descubre que necesita probar una gran cantidad de comportamiento privado, lo más probable es que tenga una nueva 'clase' escondida dentro de la clase que está tratando de probar, extráigala y pruébela por su interfaz pública.

Un consejo / herramienta de pensamiento ... Existe la idea de que ningún método debería ser privado. Es decir, todos los métodos deberían vivir en una interfaz pública de un objeto ... si siente que necesita hacerlo privado, lo más probable es que viva en otro objeto.

Este consejo no funciona en la práctica, pero en su mayoría es un buen consejo, y a menudo empujará a las personas a descomponer sus objetos en objetos más pequeños.

Keith Nicholas
fuente
385
Estoy en desacuerdo. En OOD, los métodos y propiedades privados son una forma intrínseca de no repetirse ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). La idea detrás de la programación y encapsulación de caja negra es ocultar detalles técnicos del suscriptor. Por lo tanto, es necesario tener propiedades y métodos privados no triviales en su código. Y si no es trivial, debe ser probado.
AxD
8
no es intrínseco, algunos lenguajes OO no tienen métodos privados, las propiedades privadas pueden contener objetos con interfaces públicas que se pueden probar.
Keith Nicholas
77
El objetivo de este consejo es que si su objeto hace una cosa y está SECO, a menudo hay pocas razones para tener métodos privados. A menudo, los métodos privados hacen algo de lo que el objeto no es realmente responsable, pero es bastante útil, si no es trivial, entonces generalmente es otro objeto ya que es probable que viole el SRP
Keith Nicholas
28
Incorrecto. Es posible que desee utilizar métodos privados para evitar la duplicación de código. O para validación. O para muchos otros propósitos que el mundo público no debería conocer.
Jorj
37
Cuando te dejaron en una base de código OO tan horriblemente diseñada y te pidieron que la "mejoraras gradualmente", fue una decepción descubrir que no pude hacer algunas primeras pruebas de métodos privados. Sí, quizás en los libros de texto estos métodos no estarían aquí, pero en el mundo real tenemos usuarios que tienen requisitos de productos. No puedo simplemente hacer una refactorización masiva de "Mira el código limpio de ma" sin obtener algunas pruebas en el proyecto para construir. Se siente como otra instancia de forzar a los programadores practicantes a avenidas que ingenuamente parecen buenas, pero no tienen en cuenta la verdadera mierda desordenada.
dune.rocks
666

Puede usar la clase PrivateObject

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
Escotilla
fuente
25
Esta es la respuesta correcta, ahora que Microsoft ha agregado PrivateObject.
Zoey
44
Buena respuesta, pero tenga en cuenta que PrivateMethod necesita ser "protegido" en lugar de "privado".
HerbalMart
23
@HerbalMart: Quizás te malinterprete, pero si estás sugiriendo que PrivateObject solo puede acceder a miembros protegidos y no a miembros privados, estás equivocado.
kmote
17
@JeffPearce Para métodos estáticos, puede usar "PrivateType pt = new PrivateType (typeof (MyClass));", y luego llamar a InvokeStatic en el objeto pt como llamaría a Invoke en un objeto privado.
Steve Hibbert,
12
En caso de que alguien se preguntara a partir de MSTest.TestFramework v1.2.1 - las clases PrivateObject y PrivateType no están disponibles para proyectos dirigidos a .NET Core 2.0 - Hay un problema de github para esto: github.com/Microsoft/testfx/issues/366
shiitake
98

"No hay nada llamado estándar o mejor práctica, probablemente son solo opiniones populares".

Lo mismo es válido para esta discusión también.

ingrese la descripción de la imagen aquí

Todo depende de lo que creas que es una unidad, si crees que UNIT es una clase, solo accederás al método público. Si crees que UNIT tiene líneas de código que tocan métodos privados, no te sentirás culpable.

Si desea invocar métodos privados, puede usar la clase "PrivateObject" y llamar al método invoke. Puede ver este video de YouTube en profundidad ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) que muestra cómo usar "PrivateObject" y también discute si las pruebas de métodos privados son lógicas o no.

Shivprasad Koirala
fuente
55

Otro pensamiento aquí es extender las pruebas a clases / métodos "internos", dando más sentido de caja blanca a estas pruebas. Puede usar InternalsVisibleToAttribute en el ensamblaje para exponerlos a módulos de prueba de unidades separadas.

En combinación con la clase sellada, puede acercarse a tal encapsulación que el método de prueba sea visible solo desde el ensamblaje de la unidad de prueba de sus métodos. Considere que el método protegido en clase sellada es de facto privado.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

Y prueba unitaria:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
Jeff
fuente
No estoy seguro de entender. InternalsVisibleToAttribute hace que los métodos y atributos marcados como "internos" sean accesibles, pero mis campos y métodos son "privados". ¿Estás sugiriendo que cambie las cosas de ser privadas a internas? Creo que no lo entiendo.
junichiro
2
Sí, eso es lo que estoy sugiriendo. Es un poco "hacky", pero al menos no son "públicos".
Jeff
27
Esta es una respuesta maravillosa simplemente porque no dice "no pruebe métodos privados" pero sí, es bastante "hacky". Desearía que hubiera una solución. En mi opinión, es malo decir que "los métodos privados no deberían ser probados" porque, a mi modo de ver, es equivalente a "los métodos privados no deberían ser correctos".
MasterMastic
55
Ya sabes, también estoy confundido por aquellos que afirman que los métodos privados no deberían ser probados en pruebas unitarias. La API pública es la salida, pero a veces la implementación incorrecta también da la salida correcta. O la implementación causó algunos efectos secundarios negativos, por ejemplo, mantener recursos que no son necesarios, hacer referencia a objetos que evitan que gc ... etc. A menos que proporcionen otra prueba que pueda cubrir los métodos privados en lugar de la prueba unitaria, de lo contrario consideraría que no pueden mantener un código 100% probado.
Sr.Pony
Estoy de acuerdo con MasterMastic. Esta debería ser la respuesta aceptada.
XDS
27

Una forma de probar métodos privados es a través de la reflexión. Esto también se aplica a NUnit y XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
Jack Davidson
fuente
call methods estática y no estática ?
Kiquenet
2
La desventaja de los métodos dependientes de la reflexión es que tienden a romperse cuando cambia el nombre de los métodos con R #. Puede que no sea un gran problema en proyectos pequeños, pero en grandes bases de código se vuelve un poco molesto que las pruebas unitarias se rompan de tal manera y luego tengan que dar vueltas y corregirlas rápidamente. En este sentido, mi dinero va a la respuesta de Jeff.
XDS
2
@XDS Lástima que nameof () no funciona para obtener el nombre de un método privado desde fuera de su clase.
Gabriel Morin
12

Ermh ... Llegué aquí con exactamente el mismo problema: pruebe un método privado simple pero fundamental . Después de leer este hilo, parece ser como "Quiero perforar este simple agujero en esta simple pieza de metal, y quiero asegurarme de que la calidad cumpla con las especificaciones", y luego viene "Bien, esto no es tan fácil. En primer lugar, no existe una herramienta adecuada para hacerlo, pero podría construir un observatorio de ondas gravitacionales en su jardín. Lea mi artículo en http://foobar.brigther-than-einstein.org/ Primero, por supuesto, usted tiene que asistir a algunos cursos avanzados de física cuántica, luego necesita toneladas de nitrógeno ultrafrío, y luego, por supuesto, mi libro disponible en Amazon "...

En otras palabras...

No, lo primero es lo primero.

Todos y cada método, se pueden privada, interna, protegido, público tiene que ser comprobable. Tiene que haber una manera de implementar tales pruebas sin tanta dificultad como se presentó aquí.

¿Por qué? Exactamente por las menciones arquitectónicas hechas hasta ahora por algunos colaboradores. Quizás una simple reiteración de los principios del software pueda aclarar algunos malentendidos.

En este caso, los sospechosos habituales son: OCP, SRP y, como siempre, KIS.

Pero espera un minuto. La idea de hacer que todo esté a disposición del público es más bien menos político y una especie de actitud. Pero. Cuando se trata de código, incluso en la comunidad de código abierto, esto no es un dogma. En cambio, "ocultar" algo es una buena práctica para que sea más fácil familiarizarse con una determinada API. Ocultaría, por ejemplo, los cálculos básicos de su nuevo componente de termómetro digital, no para ocultar las matemáticas detrás de la curva real medida a lectores curiosos de códigos, sino para evitar que su código se vuelva dependiente de algunos, quizás usuarios repentinamente importantes que no pudieron resistir el uso de su código privado, interno y protegido para implementar sus propias ideas.

De que estoy hablando

privado doble TranslateMeasurementIntoLinear (doble actualMeasurement);

Es fácil proclamar la Era de Acuario o lo que se llama hoy en día, pero si mi sensor pasa de 1.0 a 2.0, la implementación de Traducir ... podría cambiar de una ecuación lineal simple que es fácilmente comprensible y "re usable "para todos, para un cálculo bastante sofisticado que usa análisis o lo que sea, y entonces rompería el código de otros. ¿Por qué? Porque no entendían los mismos principios de la codificación de software, ni siquiera KIS.

Para abreviar este cuento de hadas: necesitamos una forma simple de probar métodos privados, sin preámbulos.

Primero: ¡Feliz año nuevo a todos!

Segundo: ensaya tus lecciones de arquitecto.

Tercero: El modificador "público" es religión, no una solución.

CP70
fuente
5

Otra opción que no se ha mencionado es simplemente crear la clase de prueba unitaria como elemento secundario del objeto que está probando. NUnit Ejemplo:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Esto permitiría probar fácilmente métodos privados y protegidos (pero no heredados), y le permitiría mantener todas sus pruebas separadas del código real para que no esté implementando ensambles de prueba en producción. Cambiar sus métodos privados a métodos protegidos sería aceptable en muchos objetos heredados, y es un cambio bastante simple de hacer.

SIN EMBARGO...

Si bien este es un enfoque interesante para resolver el problema de cómo probar métodos ocultos, no estoy seguro de que recomendaría que esta sea la solución correcta al problema en todos los casos. Parece un poco extraño probar internamente un objeto, y sospecho que puede haber algunos escenarios en los que este enfoque explote. (Los objetos inmutables, por ejemplo, pueden hacer que algunas pruebas sean realmente difíciles).

Si bien menciono este enfoque, sugeriría que se trata más de una lluvia de ideas que de una solución legítima. Tómelo con un grano de sal.

EDITAR: Encuentro realmente divertido que la gente esté rechazando esta respuesta, ya que explícitamente lo describo como una mala idea. ¿Eso significa que la gente está de acuerdo conmigo? Estoy muy confundido.....

Roger Hill
fuente
Es una solución creativa pero un poco hacky.
Shinzou
3

Del libro Trabajando efectivamente con código heredado :

"Si necesitamos probar un método privado, deberíamos hacerlo público. Si hacerlo público nos molesta, en la mayoría de los casos, significa que nuestra clase está haciendo demasiado y deberíamos solucionarlo".

La forma de solucionarlo, según el autor, es creando una nueva clase y agregando el método como public.

El autor explica además:

"El buen diseño es comprobable, y el diseño que no es comprobable es malo".

Entonces, dentro de estos límites, su única opción real es hacer el método public, ya sea en la clase actual o en una nueva.

Kai Hartmann
fuente
2

TL; DR: Extraiga el método privado a otra clase, pruebe en esa clase; Lea más sobre el principio SRP (Principio de responsabilidad única)

Parece que necesita extraer el privatemétodo a otra clase; en esto debería ser public. En lugar de intentar probar el privatemétodo, debe probar el publicmétodo de esta otra clase.

Tenemos el siguiente escenario:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Necesitamos probar la lógica de _someLogic; pero parece que Class Atiene más papel del que necesita (viola el principio SRP); simplemente refactorizar en dos clases

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

De esta manera someLogicpodría probarse en A2; en A1 solo cree un falso A2 y luego inyecte al constructor para probar que A2 se llama a la función nombrada someLogic.

o0omycomputero0o
fuente
0

En VS 2005/2008, puede usar un descriptor de acceso privado para probar un miembro privado, pero de esta forma desapareció en una versión posterior de VS

Allen
fuente
1
Buena respuesta en 2008, quizás a principios de 2010. Ahora consulte las alternativas PrivateObject y Reflection (consulte varias respuestas más arriba). VS2010 tenía errores de acceso, MS lo desaprobó en VS2012. A menos que se vea obligado a permanecer en VS2010 o más (herramientas de compilación de> 18 años), ahorre tiempo evitando los accesos privados. :-).
Zephan Schroeder