¿Cómo se prueban los métodos privados con NUnit?

104

Me pregunto cómo usar NUnit correctamente. Primero, creé un proyecto de prueba separado que usa mi proyecto principal como referencia. Pero en ese caso, no puedo probar métodos privados. ¡¿Mi suposición fue que necesito incluir mi código de prueba en mi código principal ?! - Esa no parece ser la forma correcta de hacerlo. (No me gusta la idea de enviar un código con pruebas).

¿Cómo se prueban los métodos privados con NUnit?

Señor Fox
fuente

Respuestas:

74

Generalmente, las pruebas unitarias se dirigen a la interfaz pública de una clase, en la teoría de que la implementación es inmaterial, siempre que los resultados sean correctos desde el punto de vista del cliente.

Por lo tanto, NUnit no proporciona ningún mecanismo para probar miembros no públicos.

arpón
fuente
1
+1 Acabo de encontrarme con este problema y, en mi caso, hay un algoritmo de "mapeo" que ocurre entre lo privado y lo público, lo que significa que si tuviera que realizar una prueba unitaria en el público, en realidad sería una prueba de integración. En este escenario, creo que está tratando de escribir los puntos de prueba para un problema de diseño en el código. en cuyo caso probablemente debería crear una clase diferente que realice ese método "privado".
Andy
140
No estoy de acuerdo completamente con este argumento. La prueba unitaria no se trata de la clase, se trata del código . Si tuviera una clase con un método público y diez privados usados ​​para crear el resultado del público, no tengo forma de asegurar que cada método privado funcione de la manera prevista. Podría intentar crear casos de prueba en el método público que golpea el método privado correcto de la manera correcta. Pero entonces no tengo idea de si el método privado fue llamado realmente, a menos que confíe en que el método público nunca cambiará. Los métodos privados están destinados a ser privados para los usuarios de producción del código, no para el autor.
Quango
3
@Quango, en una configuración de prueba estándar, el "autor" es un usuario de producción del código. Sin embargo, si no huele un problema de diseño, tiene opciones para probar métodos no públicos sin alterar la clase. System.Reflectionle permite acceder e invocar métodos no públicos utilizando indicadores vinculantes, por lo que podría piratear NUnit o configurar su propio marco. O (más fácil, creo), podría configurar un indicador de tiempo de compilación (#if TESTING) para cambiar los modificadores de acceso, lo que le permite usar los marcos existentes.
harpo
23
Un método privado es un detalle de implementación. Gran parte del objetivo de tener pruebas unitarias es permitir la refactorización de la implementación sin cambiar el comportamiento . Si los métodos privados se vuelven tan complicados que uno quisiera cubrirlos con pruebas individualmente, entonces este lema se puede usar: "Un método privado siempre puede ser un método público (o interno) de una clase separada". Es decir, si una parte de la lógica está lo suficientemente avanzada como para ser sometida a prueba, probablemente sea candidata a ser su propia clase, con sus propias pruebas.
Anders Forsgren
6
@AndersForsgren Pero el objetivo de las pruebas unitarias , a diferencia de las pruebas funcionales o de integración , es precisamente probar esos detalles. Y la cobertura de código se considera una métrica importante de prueba unitaria, por lo que, en teoría, cada pieza de lógica está "lo suficientemente avanzada para estar sujeta a prueba".
Kyle Strand
57

Si bien estoy de acuerdo en que el enfoque de las pruebas unitarias debe ser la interfaz pública, obtendrá una impresión mucho más granular de su código si también prueba métodos privados. El marco de prueba de MS permite esto mediante el uso de PrivateObject y PrivateType, NUnit no. Lo que hago en cambio es:

private MethodInfo GetMethod(string methodName)
{
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("methodName cannot be null or whitespace");

    var method = this.objectUnderTest.GetType()
        .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} method not found", methodName));

    return method;
}

De esta manera, no tiene que comprometer la encapsulación a favor de la capacidad de prueba. Tenga en cuenta que deberá modificar sus BindingFlags si desea probar métodos estáticos privados. El ejemplo anterior es solo un ejemplo de métodos.

usuario1039513
fuente
7
La prueba de esos pequeños métodos auxiliares captura la parte unitaria en la prueba unitaria. No todos son adecuados para clases de servicios públicos.
Johan Larsson
12
Esto es exactamente lo que necesitaba. ¡Gracias! Todo lo que necesitaba era comprobar que un campo privado se estaba configurando correctamente, y NO necesité pasar 3 horas para averiguar cómo determinar eso a través de la interfaz pública. Este es un código existente que no se puede refactorizar por capricho. Es curioso cómo se puede responder una pregunta simple con un dogma idealista ...
Droj
5
Esta debería ser la respuesta seleccionada, ya que es la única que realmente responde a la pregunta.
Bavaza
6
Convenido. La respuesta elegida tiene sus méritos, pero es la respuesta a "debería probar métodos privados", no la pregunta del OP.
chesterbr
2
Funciona ¡Gracias! Esto es lo que muchas personas están buscando. Esta debería ser la respuesta seleccionada,
Able Johnson
47

Un patrón común para escribir pruebas unitarias es probar solo métodos públicos.

Si encuentra que tiene muchos métodos privados que desea probar, normalmente esto es una señal de que debe refactorizar su código.

Sería incorrecto hacer públicos estos métodos en la clase donde viven actualmente. Eso rompería el contrato que quieres que tenga esa clase.

Puede ser correcto moverlos a una clase de ayuda y hacerlos públicos allí. Es posible que su API no exponga esta clase.

De esta manera, el código de prueba nunca se mezcla con su código público.

Un problema similar es probar clases privadas, es decir. clases que no exporta desde su ensamblado. En este caso, puede hacer explícitamente que su ensamblado de código de prueba sea un amigo del ensamblado de código de producción utilizando el atributo InternalsVisibleTo.

morechilli
fuente
1
+ 1 ¡sí! Acabo de llegar a esa conclusión mientras escribía mis pruebas, ¡buen consejo!
Andy
1
Mover los métodos privados que desea probar pero no exponer a los usuarios de la API a una clase diferente que no está expuesta y hacerlos públicos es exactamente lo que estaba buscando.
Thomas N
gracias @morechilli. Nunca había visto InternalsVisibleTo antes. Hizo las pruebas mucho más fáciles.
Andrew MacNaughton
Hola. Recientemente recibió algunos votos negativos sin comentarios. Proporcione algunos comentarios para explicar sus pensamientos o inquietudes.
morechilli
6
Entonces, si tiene un método privado al que se llama en varios lugares dentro de una clase para hacer algo muy específico que solo necesita esa clase y no debe exponerse como parte de la API pública ... está diciendo que lo ponga en un asistente clase solo para probar? También podría hacerlo público para la clase.
Daniel Macias
21

Es posible probar métodos privados declarando su ensamblado de prueba como ensamblado amigo del ensamblado de destino que está probando. Consulte el enlace a continuación para obtener más detalles:

http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx

Esto puede ser útil ya que en su mayoría separa su código de prueba de su código de producción. Yo nunca he usado este método, ya que nunca lo he necesitado. Supongo que podría usarlo para probar y probar casos de prueba extremos que simplemente no puede replicar en su entorno de prueba para ver cómo lo maneja su código.

Sin embargo, como se ha dicho, realmente no debería necesitar probar métodos privados. Es más que probable que desee refactorizar su código en bloques de construcción más pequeños. Un consejo que podría ayudarte cuando llegues a refactorizar es intentar pensar en el dominio con el que se relaciona tu sistema y pensar en los objetos 'reales' que habitan en este dominio. Sus objetos / clases en su sistema deben relacionarse directamente con un objeto real, lo que le permitirá aislar el comportamiento exacto que debe contener el objeto y también limitar las responsabilidades de los objetos. Esto significará que está refactorizando lógicamente en lugar de solo hacer posible probar un método en particular; podrá probar el comportamiento de los objetos.

Si aún siente la necesidad de realizar pruebas internas, es posible que también desee considerar burlarse en sus pruebas, ya que es probable que desee centrarse en una pieza de código. La burla es donde se inyectan las dependencias de un objeto, pero los objetos inyectados no son los objetos "reales" o de producción. Son objetos ficticios con comportamiento codificado para facilitar el aislamiento de errores de comportamiento. Rhino.Mocks es un popular marco de trabajo gratuito que esencialmente escribirá los objetos por usted. TypeMock.NET (un producto comercial con una edición comunitaria disponible) es un marco más potente que puede simular objetos CLR. Muy útil para burlarse de las clases SqlConnection / SqlCommand y Datatable, por ejemplo, al probar una aplicación de base de datos.

Esperamos que esta respuesta le brinde un poco más de información para informarle sobre las pruebas unitarias en general y ayudarlo a obtener mejores resultados de las pruebas unitarias.

Dafydd Giddins
fuente
12
Es posible probar métodos internos , no privados (con NUnit).
TrueWill
6

Estoy a favor de tener la capacidad de probar métodos privados. Cuando se inició xUnit, estaba destinado a probar la funcionalidad después de que se escribió el código. Probar la interfaz es suficiente para este propósito.

Las pruebas unitarias han evolucionado hasta convertirse en un desarrollo impulsado por pruebas. Tener la capacidad de probar todos los métodos es útil para esa aplicación.

Mark Glass
fuente
5

Esta pregunta está en sus años avanzados, pero pensé en compartir mi forma de hacer esto.

Básicamente, tengo todas mis clases de prueba unitaria en el ensamblado que están probando en un espacio de nombres 'UnitTest' debajo del 'predeterminado' para ese ensamblado; cada archivo de prueba está envuelto en un:

#if DEBUG

...test code...

#endif

block, y todo eso significa que a) no se está distribuyendo en una versión yb) puedo usar declaraciones internal/ Friendlevel sin saltar el aro.

La otra cosa que ofrece, más pertinente a esta pregunta, es el uso de partialclases, que se pueden usar para crear un proxy para probar métodos privados, por ejemplo, para probar algo como un método privado que devuelve un valor entero:

public partial class TheClassBeingTested
{
    private int TheMethodToBeTested() { return -1; }
}

en las clases principales del ensamblado y la clase de prueba:

#if DEBUG

using NUnit.Framework;

public partial class TheClassBeingTested
{
    internal int NUnit_TheMethodToBeTested()
    {
        return TheMethodToBeTested();
    }
}

[TestFixture]
public class ClassTests
{
    [Test]
    public void TestMethod()
    {
        var tc = new TheClassBeingTested();
        Assert.That(tc.NUnit_TheMethodToBeTested(), Is.EqualTo(-1));
    }
}

#endif

Obviamente, debe asegurarse de no usar este método durante el desarrollo, aunque una versión de lanzamiento pronto indicará una llamada inadvertida si lo hace.

Stuart Wood
fuente
4

El objetivo principal de las pruebas unitarias es probar los métodos públicos de una clase. Esos métodos públicos utilizarán esos métodos privados. Las pruebas unitarias probarán el comportamiento de lo que está disponible públicamente.

Maxime Rouiller
fuente
3

Disculpe si esto no responde a la pregunta, pero soluciones como usar la reflexión, # si las declaraciones #endif o hacer visibles los métodos privados no resuelven el problema. Puede haber varias razones para no hacer visibles los métodos privados ... ¿qué pasa si es código de producción y el equipo está escribiendo pruebas unitarias retrospectivamente, por ejemplo?

Para el proyecto en el que estoy trabajando, solo MSTest (lamentablemente) parece tener una forma, usando accesores, para probar unitarios métodos privados.

Ritesh
fuente
2

No pruebas funciones privadas. Hay formas de utilizar la reflexión para entrar en métodos y propiedades privados. Pero eso no es realmente fácil y desaconsejo esta práctica.

Simplemente no debes probar nada que no sea público.

Si tiene algunos métodos y propiedades internos, debería considerar cambiar eso a público o enviar sus pruebas con la aplicación (algo que realmente no veo como un problema).

Si su cliente puede ejecutar un Test-Suite y ve que el código que entregó en realidad "funciona", no veo esto como un problema (siempre y cuando no revele su IP a través de esto). Las cosas que incluyo en cada lanzamiento son informes de prueba e informes de cobertura de código.

Tigraine
fuente
Algunos clientes intentan mantener un presupuesto reducido e iniciar debates sobre el alcance de las pruebas. Es difícil explicarles a estas personas que la prueba de escritura no se debe a que seas un mal programador y no confíes en tus propias habilidades.
MrFox
1

En teoría de las pruebas unitarias, solo se debe probar el contrato. es decir, solo miembros públicos de la clase. Pero en la práctica, el desarrollador generalmente quiere probar miembros internos para. - y no está mal. Sí, va en contra de la teoría, pero en la práctica puede ser útil a veces.

Entonces, si realmente desea probar miembros internos, puede usar uno de estos enfoques:

  1. Haga público su miembro. En muchos libros, los autores sugieren este enfoque tan simple
  2. Puede hacer que sus miembros sean internos y agregar InternalVisibleTo a assebly
  3. Puede hacer que los miembros de la clase estén protegidos y heredar su clase de prueba de su clase de prueba.

Ejemplo de código (pseudocódigo):

public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{

    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}
burzhuy
fuente
Parece que la cucaracha está oculta dentro de su código de ejemplo;) El método dentro del dispositivo de NUnit debe ser público, de lo contrario, obtendrá Message: Method is not public.
Gucu112
@ Gucu112 Gracias. Lo arreglé. En general, no importa, ya que el objetivo es mostrar un enfoque desde el punto de vista del diseño.
burzhuy
1

Puede hacer que sus métodos estén protegidos de forma interna y luego usarlos assembly: InternalsVisibleTo("NAMESPACE") en su espacio de nombres de prueba.

Por lo tanto, ¡NO! No puede acceder a métodos privados, pero esto es una solución.

Furgalicious
fuente
0

Haría visible el paquete de métodos privados. De esa manera, lo mantendrá razonablemente privado y al mismo tiempo podrá probar esos métodos. No estoy de acuerdo con la gente que dice que las interfaces públicas son las únicas que deberían probarse. A menudo hay un código realmente crítico en los métodos privados que no se pueden probar correctamente pasando solo por las interfaces externas.

Así que realmente se reduce a si te importa más el código correcto o la ocultación de información. Yo diría que la visibilidad del paquete es un buen compromiso, ya que para acceder a esos métodos, alguien tendría que colocar su clase en su paquete. Eso realmente debería hacerlos pensar dos veces sobre si es algo realmente inteligente.

Por cierto, soy un tipo de Java, por lo que la visibilidad del paquete podría llamarse algo completamente diferente en C #. Basta decir que es cuando dos clases tienen que estar en el mismo espacio de nombres para poder acceder a esos métodos.

Fylke
fuente