¿Cómo prueba los métodos privados?


479

Estoy construyendo una biblioteca de clase que tendrá algunos métodos públicos y privados. Quiero poder realizar una prueba unitaria 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?


3
Puede que me falte algo, o tal vez es solo que esta pregunta es, bueno ... pre-historicen términos de años de Internet, pero las pruebas unitarias de métodos privados ahora son fáciles y directas, ya que Visual Studio produce las clases de acceso necesarias cuando es necesario y llenar previamente la lógica de las pruebas con fragmentos muy cercanos a lo que uno puede desear para pruebas funcionales simples. Ver por ej. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
mjv

3
Esto parece casi un duplicado de stackoverflow.com/questions/34571/… .
Raedwald

El interlocutor no puede estar utilizando Visual Studio
David

3
No pruebe los componentes
Mark Seemann

Respuestas:


122

Si está usando .net, debe usar InternalsVisibleToAttribute .


86
Yuck Esto se compila en sus ensamblajes lanzados.
Jay

14
@ Jay: ¿no se puede usar #if DEBUGel InternalsVisibleToatributo para que no se aplique al código de lanzamiento?
mpontillo

20
@ Mike, podría, pero solo puede ejecutar pruebas unitarias en el código de depuración, no en el código de liberación. Dado que el código de lanzamiento está optimizado, es posible que vea diferentes comportamientos y diferentes tiempos. En el código multiproceso, esto significa que las pruebas unitarias no detectarán adecuadamente las condiciones de carrera. Mucho mejor es usar la reflexión a través de la sugerencia de @ AmazedSaint a continuación o usar el PrivateObject / PrivateType incorporado. Esto le permite ver elementos privados en las versiones de lanzamiento asumiendo que su arnés de prueba se ejecuta con plena confianza (que MSTest ejecuta localmente)
Jay

121
¿Qué me estoy perdiendo? ¿Por qué sería esta la respuesta aceptada cuando en realidad no responde la pregunta específica de probar métodos privados? InternalsVisibleTo solo expone métodos marcados como internos y no aquellos marcados como privados según lo solicitado por el OP (y la razón por la que aterricé aquí). ¿Supongo que necesito continuar usando PrivateObject según lo contestado por Seven?
Darren Lewis

66
@Jay Sé que esto es un poco tarde-venir, pero una opción es usar algo como #if RELEASE_TESTalrededor InternalsVisibleTocomo Mike sugiere, y hacer una copia de la configuración de su versión de lanzamiento que define RELEASE_TEST. Puede probar su código de lanzamiento con optimizaciones, pero cuando realmente compila para el lanzamiento, sus pruebas serán omitidas.
Shaz

349

Si desea probar un método privado, algo puede estar mal. Las pruebas unitarias son (en términos generales) destinadas a probar la interfaz de una clase, es decir, sus métodos públicos (y protegidos). Por supuesto, puede "hackear" una solución a esto (incluso si solo hace públicos los métodos), pero también puede considerar:

  1. Si realmente vale la pena probar el método que desea probar, puede valer la pena moverlo a su propia clase.
  2. 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 una parte de la interfaz pública. Si realmente realizan funciones que están ocultas para el usuario (es decir, la prueba de la unidad), esto probablemente sea malo).

38
La opción 2 hace que las pruebas unitarias tengan que tener conocimiento de la implementación subyacente de la función. No me gusta hacer eso En general, creo que las pruebas unitarias deberían probar la función sin suponer nada sobre la implementación.
Herms

15
Las desventajas de la implementación de la prueba es que las pruebas serán frágiles para romper si introduce algún cambio en la implementación. Y esto no es deseable ya que la refactorización es tan importante como escribir las pruebas en TDD.
JtR

30
Bueno, se supone que las pruebas se rompen si cambia la implementación. TDD significaría cambiar las pruebas primero.
sleske

39
@sleske - No estoy del todo de acuerdo. Si la funcionalidad no ha cambiado, entonces no hay razón para que la prueba se rompa, ya que las pruebas realmente deberían probar el comportamiento / estado, no la implementación. Esto es lo que significaba jtr al hacer sus pruebas frágiles. En un mundo ideal, debería poder refactorizar su código y que sus pruebas aún pasen, verificando que su refactorización no haya cambiado la funcionalidad de su sistema.
Alconja

26
Disculpas por la ingenuidad (todavía no hay mucha experiencia con las pruebas), pero ¿no es la idea de pruebas unitarias para probar cada módulo de código por sí solo? Realmente no entiendo por qué los métodos privados deberían excluirse de esta idea.
OMill

118

Puede que no sea ú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 debe crearse 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 ofrece a todos los miembros públicos disponibles.

Sin embargo, el mecanismo a veces es 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[] {/* ... */} );

2
¿Cómo invocas métodos estáticos privados?
StuperUser

18
Los accesos privados están en desuso en Visual Studio 2012 .
Ryan Gates

3
El método de acceso para probar los métodos privados está en desuso desde VS 2011 en adelante. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Sanjit Misra

1
Al leer los documentos que se encuentran en el sitio web de Microsoft aquí , no veo ninguna mención de que la clase PrivateObject esté en desuso. Estoy usando MSVS 2013 y funciona como se esperaba.
stackunderflow

3
@RyanGates La primera solución en el sitio al que hizo referencia indica que la solución para acceder a miembros privados es "Usar la clase PrivateObject para ayudar a acceder a las API internas y privadas en su código. Esto se encuentra en Microsoft.VisualStudio.QualityTools.UnitTestFramework". dll asamblea ".
stackunderflow

78

No estoy de acuerdo con la filosofía de "solo deberías estar interesado en probar la interfaz externa". Es un poco como decir que un taller de reparación de automóviles solo debe realizar pruebas para ver si las ruedas giran. Sí, en última instancia, estoy interesado en el comportamiento externo, pero me gusta que mis propias pruebas privadas e internas sean un poco más específicas y precisas. Sí, si refactorizo, es posible que tenga que cambiar algunas de las pruebas, pero a menos que sea un refactor masivo, 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 utilizando solo la interfaz pública y, en teoría, es posible probar todos los métodos internos (o al menos todos los que importan) por completo mediante el uso de la interfaz pública, pero es posible que deba terminar parado sobre su cabeza para lograr 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 que están diseñados para probar puede ser difícil o imposible de discernir. Una vez 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 hacer 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.


20
Me temo que aún no estoy de acuerdo contigo. Tratar cada componente como una caja negra permite que los módulos se intercambien dentro / fuera sin problemas. Si FooServicetiene que hacer algo X, lo único que debe importarle es que lo haga Xcuando se le solicite. Cómo lo hace no debería importar. Si hay problemas en la clase no discernibles a través de la interfaz (poco probable), sigue siendo válido FooService. Si se trata de un problema que es visible a través de la interfaz, una prueba sobre los miembros públicos debe detectarlo. El punto principal debe ser que, siempre que la rueda gire correctamente, se puede usar como rueda.
Básico

55
Un enfoque general es que si su lógica interna es lo suficientemente complicada como para sentir que requiere pruebas unitarias, tal vez deba extraerse en algún tipo de clase auxiliar con una interfaz pública que pueda ser probada unitariamente. Entonces su clase 'padre' simplemente puede hacer uso de este ayudante, y todos pueden ser evaluados de manera apropiada.
Mir

99
@Basic: lógica completamente incorrecta en esta respuesta. El caso clásico cuando necesita un método privado es cuando necesita que algún código sea reutilizado por métodos públicos. Pones este código en algún PrivMethod. Este método no debe exponerse al público, pero necesita pruebas para garantizar que los métodos públicos, que dependen de PrivMethod, realmente puedan confiar en él.
Dima

66
@Dima Seguramente, entonces, si hay un problema con PrivMethod, ¿una prueba en PubMethodqué llamadas PrivMethoddebería exponerlo? ¿Qué sucede cuando cambias tu SimpleSmtpServicea a GmailService? De repente, sus pruebas privadas apuntan a un código que ya no existe o que tal vez funciona de manera diferente y fallaría, a pesar de que la aplicación puede funcionar perfectamente como se diseñó. Si hay un procesamiento complejo que se aplicaría a ambos remitentes de correo electrónico, ¿tal vez debería estar en uno EmailProcessorque ambos puedan usar y probar por separado?
Básico

2
@miltonb Podemos estar viendo esto desde diferentes estilos de desarrollo. WRT las partes internas, no tiendo a probarlas unitariamente. Si hay un problema (como lo identifican las pruebas de interfaz), es fácil de rastrear adjuntando un depurador o la clase es demasiado compleja y debería dividirse (con la interfaz pública de la nueva unidad de clases probada) En mi humilde opinión
básico

51

En los raros casos en los que he querido probar funciones privadas, generalmente las modifiqué para protegerlas y escribí una subclase con una función de envoltura pública.

La clase:

...

protected void APrivateFunction()
{
    ...
}

...

Subclase para pruebas:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...

1
Incluso puede poner esa clase secundaria en el archivo de prueba de la unidad en lugar de saturar la clase real. +1 por astucia.
Tim Abell el

Siempre pongo todo el código relacionado con la prueba en el proyecto de pruebas unitarias si es posible. Esto fue solo código psuedo.
Jason Jackson

2
Esta función no es privada, está protegida, el resultado neto ... hizo que su código fuera menos seguro / expuesto a la funcionalidad privada de tipos secundarios
Guerra

22

Creo que una pregunta más fundamental que debería hacerse es: ¿por qué estás tratando de probar el método privado en primer lugar? Ese es un olor a código que está tratando de probar el método privado a través de la interfaz pública de esa clase, mientras que ese método es privado por una razón, ya que es un detalle de implementación. Uno solo debe preocuparse por el comportamiento de la interfaz pública, no por cómo se implementa bajo las cubiertas.

Si quiero probar el comportamiento del método privado, mediante el uso de refactorizaciones comunes, puedo extraer su código en otra clase (tal vez con visibilidad a nivel de paquete, así que asegúrese de que no sea parte de una API pública). Entonces puedo probar su comportamiento de forma aislada.

El producto de la refactorización significa que el método privado ahora es una clase separada que se ha convertido en colaboradora de la clase original. Su comportamiento se habrá entendido bien a través de sus propias pruebas unitarias.

Entonces puedo burlarme de su comportamiento cuando intento probar la clase original para poder concentrarme en probar el comportamiento de la interfaz pública de esa clase en lugar de tener que probar una explosión combinatoria de la interfaz pública y el comportamiento de todos sus métodos privados. .

Veo esto análogo a conducir un automóvil. Cuando conduzco un automóvil, no conduzco con el capó hacia arriba, así puedo ver que el motor está funcionando. Confío en la interfaz que proporciona el automóvil, a saber, el cuentarrevoluciones y el velocímetro para saber si el motor funciona. Confío en el hecho de que el automóvil realmente se mueve cuando presiono el acelerador. Si quiero probar el motor, puedo hacer controles de forma aislada. :RE

Por supuesto, probar métodos privados directamente puede ser un último recurso si tiene una aplicación heredada, pero preferiría que el código heredado se refactorice para permitir mejores pruebas. Michael Feathers ha escrito un gran libro sobre este mismo tema. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052


16
Lógica completamente incorrecta en esta respuesta. El caso clásico cuando necesita un método privado es cuando necesita que algún código sea reutilizado por métodos públicos. Pones este código en algún PrivMethod. Este método no debe exponerse al público, pero necesita pruebas para garantizar que los métodos públicos, que dependen de PrivMethod, realmente puedan confiar en él.
Dima

Tiene sentido durante el desarrollo inicial, pero ¿quiere pruebas para métodos privados en su traje de regresión estándar? Si es así, si la implementación cambia, puede romper el conjunto de pruebas. OTOH, si sus pruebas de regresión se centran solo en métodos públicos visibles externamente, entonces si el método privado se rompe más tarde, la suite de regresión aún debería detectar el error. Luego, si es necesario, puede desempolvar la antigua prueba privada si es necesario.
Alex Blakemore

99
No está de acuerdo, debe probar solo la interfaz pública, de lo contrario, la necesidad de métodos privados. Hágalos públicos en ese caso y pruébelos todos. Si está probando métodos privados, está rompiendo la encapsulación. Si desea probar un método privado y se utiliza en varios métodos públicos, debe moverlo a su propia clase y probarlo de forma aislada. Todos los métodos públicos deben delegar a esa nueva clase. De esa manera, todavía tiene pruebas para interfaz de clase original y puede verificar que el comportamiento no ha cambiado y tiene pruebas separadas para el método privado delegado.
Big Kahuna

@Big Kahuna: si cree que no hay ningún caso en el que necesite probar métodos privados, nunca trabajó con un proyecto lo suficientemente grande / complejo. Muchas veces, una función pública como las validaciones específicas del cliente terminan con 20 líneas que solo llaman a métodos privados muy simples para hacer que el código sea más legible, pero aún tiene que probar cada método privado individual. Probar 20 veces la función pública hará que sea muy difícil debutar cuando las pruebas unitarias fallen.
Pedro The.Kid

1
Trabajo para una empresa FTSE 100. Creo que he visto varios proyectos complejos en mi tiempo, gracias. Si necesita probar a ese nivel, entonces cada método privado como colaboradores separados debe probarse de forma aislada porque implica que tienen un comportamiento individual que necesita prueba. La prueba para el objeto mediador principal se convierte en una prueba de interacción. Simplemente prueba que se está llamando a la estrategia correcta. Su escenario parece que la clase en cuestión no está siguiendo SRP. No tiene una sola razón para cambiar, pero 20 => violación de SRP. Lee el libro GOOS o el tío Bob. YMWV
Big Kahuna

17

Los tipos privados, los internos y los miembros privados lo son por alguna razón, y a menudo no desea meterse con ellos directamente. Y si lo hace, lo más probable es que se rompa más tarde, porque no hay garantía de que los chicos que crearon esos ensamblajes mantendrán las implementaciones privadas / internas como tales.

Pero, a veces, al hacer algunos hacks / exploración de ensamblados compilados o de terceros, terminé queriendo inicializar una clase privada o una clase con un constructor privado o interno. O, a veces, cuando trato con bibliotecas heredadas precompiladas que no puedo cambiar, termino escribiendo algunas pruebas contra un método privado.

Así nació AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - es una clase de envoltura rápida que facilitará el trabajo utilizando las características dinámicas y la reflexión de C # 4.0.

Puede crear tipos internos / privados como

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();

12

Bueno, puedes probar el método privado de dos maneras

  1. puede crear una instancia de PrivateObjectclase, la sintaxis es la siguiente

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
  2. Puedes usar la reflexión.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });

Buena respuesta, pero para el n. ° 1 su sintaxis es incorrecta. PrivateClassPrimero debe declarar una instancia de y usar eso. stackoverflow.com/questions/9122708/…
SharpC

10

También he usado el método InternalsVisibleToAttribute. Vale la pena mencionar también que, si se siente incómodo al hacer que sus métodos privados anteriores sean internos para lograr esto, entonces tal vez no deberían ser objeto de pruebas unitarias directas de todos modos.

Después de todo, está probando el comportamiento de su clase, en lugar de su implementación específica : puede cambiar el último sin cambiar el primero y sus pruebas aún deben pasar.


Me encanta el punto sobre probar el comportamiento en lugar de la implementación. Si vincula sus pruebas unitarias a la implementación (métodos privados), las pruebas se volverán frágiles y deberán cambiar cuando cambie la implementación.
Bob Horn

9

Hay 2 tipos de métodos privados. Métodos privados estáticos y métodos privados no estáticos (métodos de instancia). Los siguientes 2 artículos explican cómo probar métodos privados con ejemplos.

  1. Prueba unitaria de métodos privados estáticos
  2. Prueba unitaria de métodos privados no estáticos

Proporcione algunos ejemplos, no solo proporcione un enlace
vinculis

Se ve feo. Sin inteligencia. Mala solución de MS. Estoy en shock !
alerya

La forma más fácil de probar métodos estáticos privados
Grant

8

MS Test tiene una buena característica incorporada que hace que los miembros y métodos privados estén disponibles en el proyecto mediante la creación de un archivo llamado VSCodeGenAccessors

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

Con clases que derivan de BaseAccessor

como

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }

8
Los archivos gen'd solo existen en VS2005. EN 2008 se generan detrás de escena. Y son una abominación. Y la tarea Shadow asociada es escamosa en un servidor de compilación.
Ruben Bartelink

También los accesorios quedaron en desuso en VS2012-2013.
Zephan Schroeder

5

En CodeProject, hay un artículo que trata brevemente los pros y los contras de probar métodos privados. Luego proporciona un código de reflexión para acceder a métodos privados (similar al código que Marcus proporciona anteriormente). El único problema que he encontrado con la muestra es que el código no tiene en cuenta los métodos sobrecargados.

Puede encontrar el artículo aquí:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx



4

Tiendo a no usar las directivas del compilador porque desordenan las cosas rápidamente. Una forma de mitigarlo si realmente los necesita es colocarlos en una clase parcial y hacer que su compilación ignore ese archivo .cs al hacer la versión de producción.


Incluiría los accesos de prueba en una versión de producción (para probar las optimizaciones del compilador, etc.) pero los excluiría en una versión de lanzamiento. Pero estoy dividiendo pelos y voté por esto de todos modos porque creo que es una buena idea poner esas cosas en un solo lugar. Gracias por la idea
Bloque CAD el

4

En primer lugar, no debería probar los métodos privados de su código. Deberías probar la 'interfaz pública' o API, las cosas públicas de tus clases. La API son todos los métodos públicos que expone a las personas que llaman desde el exterior.

La razón es que una vez que comienza a probar los métodos privados y los elementos internos de su clase, está acoplando la implementación de su clase (las cosas privadas) a sus pruebas. Esto significa que cuando decida cambiar los detalles de su implementación, también tendrá que cambiar sus pruebas.

Por esta razón, debe evitar usar InternalsVisibleToAtrribute.

Aquí hay una gran charla de Ian Cooper que cubre este tema: Ian Cooper: TDD, ¿dónde salió todo mal?


3

A veces, puede ser bueno probar las declaraciones privadas. Básicamente, un compilador solo tiene un método público: Compilar (string outputFileName, params string [] sourceSFileNames). ¡Estoy seguro de que comprende que sería difícil probar dicho método sin probar cada declaración "oculta"!

Es por eso que hemos creado Visual T #: para hacer pruebas más fáciles. Es un lenguaje de programación .NET gratuito (compatible con C # v2.0).

Hemos agregado el operador '.-'. Simplemente se comporta como '.' operador, excepto que también puede acceder a cualquier declaración oculta de sus pruebas sin cambiar nada en su proyecto probado.

Echar un vistazo a nuestro sitio web: descarga es gratis .


3

Me sorprende que nadie haya dicho esto todavía, pero una solución que he empleado es hacer un método estático dentro de la clase para probarse a sí mismo. Esto le da acceso a todo lo público y privado para probar.

Además, en un lenguaje de secuencias de comandos (con capacidades OO, como Python, Ruby y PHP), puede hacer que el archivo se pruebe en sí mismo cuando se ejecute. Buena forma rápida de asegurarse de que sus cambios no rompan nada. Obviamente, esto es una solución escalable para probar todas sus clases: simplemente ejecútelas todas. (también puede hacerlo en otros idiomas con un vacío principal que siempre ejecuta sus pruebas también).


1
Aunque esto es práctico, no es muy elegante. Esto puede crear un desorden de una base de código y tampoco le permite separar sus pruebas de su código real. La capacidad de probar externamente abre la capacidad de automatizar las pruebas de script en lugar de escribir métodos estáticos a mano.
Darren Reid

Esto no le impide realizar pruebas externas ... simplemente llame al método estático como desee. La base del código tampoco es desordenada ... nombra el método en consecuencia. Yo uso "runTests", pero cualquier cosa similar funciona.
rube

A su derecha, no impide realizar pruebas externas, sin embargo, genera mucho más código, es decir, hace que la base del código sea desordenada. Cada clase puede tener muchos métodos privados para probar que inicializa sus variables en uno o más de sus constructores. Para probar, tendrá que escribir al menos tantos métodos estáticos como métodos para probar, y es posible que los métodos de prueba tengan que ser grandes para inicializar los valores correctos. Esto dificultaría el mantenimiento del código. Como otros han dicho, probar el comportamiento de una clase es un mejor enfoque, el resto debería ser lo suficientemente pequeño como para depurarlo.
Darren Reid

Utilizo la misma cantidad de líneas para probar que cualquier otra persona (en realidad menos como leerá más adelante). No tiene que probar TODOS sus métodos privados. Solo los que necesitan prueba :) Tampoco necesita probar cada uno en un método separado. Lo hago con una sola llamada. Esto realmente dificulta el mantenimiento del código MENOS, ya que todas mis clases tienen el mismo método de prueba de unidad paraguas, que ejecuta todas las pruebas de unidad privadas y protegidas línea por línea. Todo el arnés de prueba llama a ese mismo método en todas mis clases y el mantenimiento reside en mi clase: pruebas y todo.
Rube

3

Quiero crear un ejemplo de código claro aquí que puede usar en cualquier clase en la que desee probar el método privado.

En su clase de caso de prueba simplemente incluya estos métodos y luego utilícelos como se indica.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this -> _ callMethod ('_ someFunctionName', array (param1, param2, param3));

Simplemente emita los parámetros en el orden en que aparecen en la función privada original


3

Para cualquiera que quiera ejecutar métodos privados sin todos los fess y mess. Esto funciona con cualquier marco de prueba de unidad utilizando nada más que el viejo Reflection.

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

Luego, en sus pruebas reales, puede hacer algo como esto:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");

2

MbUnit tiene un buen contenedor para esto llamado Reflector.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

También puede establecer y obtener valores de propiedades

dogReflector.GetProperty("Age");

En cuanto a la "prueba privada", estoy de acuerdo en que ... en el mundo perfecto. no tiene sentido hacer pruebas de unidades privadas. Pero en el mundo real, es posible que desee escribir pruebas privadas en lugar de refactorizar el código.


44
Solo para información, Reflectorha sido reemplazado por el más poderoso Mirroren Gallio / MbUnit v3.2. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
Yann Trevin

2

Aquí hay un buen artículo sobre pruebas unitarias de métodos privados. Pero no estoy seguro de qué es mejor, hacer que su aplicación esté diseñada especialmente para pruebas (es como crear pruebas solo para pruebas) o usar reflexion para pruebas. Estoy bastante seguro de que la mayoría de nosotros elegiremos la segunda forma.


2

En mi opinión, solo debes probar la API pública de tu clase.

Hacer público un método, para probarlo unitariamente, rompe la encapsulación exponiendo los detalles de implementación.

Una buena API pública resuelve un objetivo inmediato del código del cliente y lo resuelve por completo.


Esta debería ser la respuesta correcta de la OMI. Si tiene muchos métodos privados, probablemente se deba a que tiene una clase oculta que debe dividir en su propia interfaz pública.
Sunefred

2

Yo uso la clase PrivateObject . Pero como se mencionó anteriormente, es mejor evitar probar métodos privados.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);

2
CC -Dprivate=public

"CC" es el compilador de línea de comandos en el sistema que uso. -Dfoo=barhace el equivalente de #define foo bar. Por lo tanto, esta opción de compilación efectivamente cambia todas las cosas privadas a públicas.


2
¿Que es esto? ¿Esto se aplica a Visual Studio?
YeahStu

"CC" es el compilador de línea de comandos en el sistema que uso. "-Dfoo = bar" hace el equivalente de "#define foo bar". Por lo tanto, esta opción de compilación efectivamente cambia todas las cosas privadas a públicas. ¡jaja!
Mark Harrison

En Visual Studio, establezca una definición en su entorno de compilación.
Mark Harrison el

1

Aquí hay un ejemplo, primero la firma del método:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

Aquí está la prueba:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}

1

Una forma de hacerlo es tener su método protectedy escribir un accesorio de prueba que herede su clase para ser probada. De esta manera, no está cambiando su método public, pero habilita la prueba.


No estoy de acuerdo con este, porque también permitirá que sus consumidores hereden de la clase base y usen las funciones protegidas. Esto era algo que quería evitar en primer lugar al hacer que esas funciones fueran privadas o internas.
Nick N.

1

1) Si tiene un código heredado, entonces la única forma de probar métodos privados es mediante reflexión.

2) Si se trata de un código nuevo, tiene las siguientes opciones:

  • Usar reflexión (a complicado)
  • Escriba la prueba unitaria en la misma clase (hace que el código de producción sea feo al tener también el código de prueba)
  • Refactorizar y hacer público el método en algún tipo de clase util
  • Utilice la anotación @VisibleForTesting y elimine privado

Prefiero el método de anotación, el más simple y el menos complicado. El único problema es que hemos aumentado la visibilidad, lo que creo que no es una gran preocupación. Siempre deberíamos estar codificando para la interfaz, por lo que si tenemos una interfaz MyService y una implementación MyServiceImpl, entonces podemos tener las clases de prueba correspondientes que son MyServiceTest (métodos de interfaz de prueba) y MyServiceImplTest (métodos privados de prueba). De todos modos, todos los clientes deberían usar la interfaz, de modo que, aunque se haya aumentado la visibilidad del método privado, no debería importar realmente.


1

También puede declararlo como público o interno (con InternalsVisibleToAttribute) mientras construye en modo de depuración:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

Hincha el código, pero estará privateen una versión de lanzamiento.


0

Puede generar el método de prueba para el método privado desde Visual Studio 2008. Cuando crea una prueba unitaria para un método privado, se agrega una carpeta de Referencias de prueba a su proyecto de prueba y se agrega un descriptor de acceso a esa carpeta. El descriptor de acceso también se menciona en la lógica del método de prueba de la unidad. Este descriptor de acceso permite que su unidad de prueba llame a métodos privados en el código que está probando. Para más detalles echa un vistazo a

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


0

También tenga en cuenta que InternalsVisibleToAtrribute tiene el requisito de que su ensamblado tenga un nombre seguro, lo que crea su propio conjunto de problemas si está trabajando en una solución que no había tenido ese requisito antes. Yo uso el descriptor de acceso para probar métodos privados. Vea esta pregunta que para un ejemplo de esto.


2
No, el InternalsVisibleToAttributequé no requieren que sus montajes ser un nombre seguro. Actualmente lo uso en un proyecto donde ese no es el caso.
Cody Gray

1
Para aclarar esto: "Tanto el ensamblaje actual como el ensamblado amigo deben estar sin firmar, o ambos deben estar firmados con un nombre seguro". - Desde MSDN
Hari
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.