it-swarm-es.com

¿Es estático universalmente "malo" para las pruebas unitarias? Si es así, ¿por qué Resharper lo recomienda?

He descubierto que solo hay 3 formas de unidad de prueba (simulacro/stub) dependencias que son estáticas en C # .NET:

Dado que dos de estos no son gratuitos y uno no ha llegado a la versión 1.0, burlarse de cosas estáticas no es demasiado fácil.

¿Eso tiene métodos estáticos y tal "maldad" (en el sentido de prueba de unidad)? Y si es así, ¿por qué resharper quiere que haga algo que pueda ser estático, estático? (Suponiendo que resharper no sea también "malvado").

Aclaración: Estoy hablando del escenario en el que desea probar un método por unidad y ese método llama a un método estático en un ¡diferente unidad/clase. Según la mayoría de las definiciones de pruebas unitarias, si simplemente deja que el método bajo prueba llame al método estático en la otra unidad/clase, entonces no es prueba unitaria, estás ¡integración pruebas. (Útil, pero no una prueba unitaria).

87
Vaccano

Mirando las otras respuestas aquí, creo que puede haber cierta confusión entre los métodos estáticos que mantienen el estado estático o causan efectos secundarios (lo que me parece una muy mala idea), y los métodos estáticos que simplemente devuelven un valor.

Los métodos estáticos que no tienen estado y no causan efectos secundarios deben ser fácilmente comprobables en la unidad. De hecho, considero tales métodos como una forma de programación funcional de "pobre hombre"; le das al método un objeto o valor, y devuelve un objeto o valor. Nada mas. No veo cómo dichos métodos afectarían negativamente las pruebas unitarias en absoluto.

108
Robert Harvey

Parece confuso static data y static method. Resharper, si no recuerdo mal, recomienda hacer que los métodos private dentro de una clase sean estáticos si se pueden hacer, creo que esto produce un pequeño beneficio de rendimiento. ¡no recomienda hacer estático "cualquier cosa que pueda ser"!

Los métodos estáticos no tienen nada de malo y son fáciles de probar (siempre que no cambien ningún dato estático). Por ejemplo, piense en una biblioteca de matemáticas, que es un buen candidato para una clase estática con métodos estáticos. Si tiene un método (artificial) como este:

public static long Square(int x)
{
    return x * x;
}

entonces esto es eminentemente comprobable y no tiene efectos secundarios. Simplemente verifica que cuando pasas, digamos, 20 vuelves a 400. No hay problema.

27
Dan Diplo

Si la verdadera pregunta aquí es "¿Cómo pruebo este código?":

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

Luego, simplemente refactorice el código e inyecte como de costumbre la llamada a la clase estática de esta manera:

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}
18
Sunny

Las estadísticas no son necesariamente malas, pero pueden limitar sus opciones cuando se trata de pruebas unitarias con falsificaciones/simulacros/trozos.

Hay dos enfoques generales para burlarse.

El primero (tradicional, implementado por RhinoMocks, Moq, NMock2; también se utilizan simulacros y trozos manuales en este campo) se basa en las costuras de prueba y la inyección de dependencia. Supongamos que está probando un código estático y tiene dependencias. Lo que sucede a menudo en el código diseñado de esta manera es que las estadísticas crean sus propias dependencias, invirtiendo la inversión de dependencia . Pronto descubrirá que no puede inyectar interfaces simuladas en el código bajo prueba que está diseñado de esta manera.

El segundo (simulacros, implementado por TypeMock, JustMock y Moles) se basa en .NET's Profiling API . Puede interceptar cualquiera de sus instrucciones CIL y reemplazar una porción de su código con una falsa. Esto permite que TypeMock y otros productos en este campamento se burlen de cualquier cosa: estática, clases selladas, métodos privados, cosas que no están diseñadas para ser comprobables.

Hay un debate en curso entre dos escuelas de pensamiento. Uno dice, siga principios SÓLIDOS y diseñe para la capacidad de prueba (que a menudo incluye ir fácil en las estadísticas). El otro dice, compre TypeMock y no se preocupe.

15
azheglov

Mira esto: "Los métodos estáticos son la muerte de la capacidad de prueba" . Breve resumen del argumento:

Para la prueba unitaria, debe tomar una pequeña parte de su código, volver a cablear sus dependencias y probarlo de forma aislada. Esto es difícil con los métodos estáticos, no solo en el caso de que accedan al estado global, sino incluso si solo llaman a otros métodos estáticos.

14
Rafał Dowgird

La verdad simple que rara vez se reconoce es que si una clase contiene una dependencia visible del compilador en otra clase, no puede probarse aisladamente de esa clase. Puede simular algo que parece una prueba y aparecerá en un informe como si fuera una prueba.

Pero no tendrá las propiedades definitorias clave de una prueba; fallando cuando las cosas están mal, pasando cuando están bien.

Esto se aplica a cualquier llamada estática, llamadas de constructor y cualquier referencia a métodos o campos no heredados de una clase base o interfaz . Si el nombre de la clase aparece en el código, es una dependencia visible para el compilador y no puede realizar una prueba válida sin él. Cualquier porción más pequeña simplemente es no es una unidad comprobable válida. Cualquier intento de tratarlo como si tuviera resultados no será más significativo que escribir una pequeña utilidad para emitir el XML utilizado por su marco de prueba para decir 'prueba aprobada'.

Dado eso, hay tres opciones:

  1. defina la prueba de unidad como prueba de la unidad compuesta por una clase y sus dependencias codificadas. Esto funciona, siempre que evite dependencias circulares.

  2. nunca cree dependencias en tiempo de compilación entre clases que es responsable de probar. Esto funciona, siempre que no te importe el estilo de código resultante.

  3. no prueba de unidad, prueba de integración en su lugar. Lo que funciona, siempre que no entre en conflicto con algo más para lo que necesite usar el término prueba de integración.

5
soru

No hay dos formas de hacerlo. Las sugerencias de ReSharper y varias características útiles de C # no se usarían con tanta frecuencia si estuviera escribiendo pruebas de unidades atómicas aisladas para todo su código.

Por ejemplo, si tiene un método estático y necesita desconectarlo, no puede hacerlo a menos que use un marco de aislamiento basado en perfil. Una solución alternativa compatible con llamadas es cambiar la parte superior del método para usar la notación lambda. Por ejemplo:

ANTES DE:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

DESPUÉS:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

Los dos son compatibles con llamadas. Las personas que llaman no tienen que cambiar. El cuerpo de la función sigue siendo el mismo.

Luego, en su código de Prueba de unidad, puede anular esta llamada de esta manera (suponiendo que esté en una clase llamada Base de datos):

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

Tenga cuidado de reemplazarlo con el valor original una vez que haya terminado. Puede hacerlo a través de un intento/finalmente o, en su limpieza de prueba de unidad, la que se llama después de cada prueba, escriba código como este:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

que volverá a invocar el inicializador estático de su clase.

Las funciones Lambda no son tan ricas en soporte como los métodos estáticos regulares, por lo que este enfoque tiene los siguientes efectos secundarios indeseables:

  1. Si el método estático era un método de extensión, primero debe cambiarlo a un método sin extensión. Resharper puede hacer esto por usted automáticamente.
  2. Si alguno de los tipos de datos de los métodos estáticos es un ensamblado de interoperabilidad incrustado, como para Office, debe ajustar el método, ajustar el tipo o cambiarlo para escribir 'objeto'.
  3. Ya no puede usar la herramienta de refactorización de cambio de firma de Resharper.

Pero supongamos que evita las estáticas por completo y convierte esto en un método de instancia. Todavía no se puede imitar a menos que el método sea virtual o esté implementado como parte de una interfaz.

Entonces, en realidad, cualquiera que sugiera el remedio para tropezar con métodos estáticos es convertirlos en métodos de instancia, también estarían en contra de los métodos de instancia que no son virtuales o parte de una interfaz.

Entonces, ¿por qué C # tiene métodos estáticos? ¿Por qué permite métodos de instancia no virtuales?

Si utiliza cualquiera de estas "Características", simplemente no puede crear métodos aislados.

Entonces, ¿cuándo los usas?

Úselos para cualquier código que no espere que nadie quiera borrar. Algunos ejemplos: el método Format () de la clase String, el método WriteLine () de la clase Console, el método Cosh () de la clase Math

Y una cosa más ... A la mayoría de las personas no les importará esto, pero si puede hacerlo sobre el rendimiento de una llamada indirecta, esa es otra razón para evitar los métodos de instancia. Hay casos en que es un éxito de rendimiento. Es por eso que existen métodos no virtuales en primer lugar.

4
zumalifeguard

Veo que después de mucho tiempo, nadie ha declarado un hecho realmente simple. Si resharper me dice que puedo hacer que un método sea estático, significa algo muy importante para mí, puedo escuchar su voz que me dice: "oye, estas piezas de lógica no son la RESPONSABILIDAD de manejar de la clase actual, por lo que deberían quedar fuera en alguna clase auxiliar o algo así ".

3
g1ga
  1. Creo que es en parte porque los métodos estáticos son "más rápidos" para llamar que los métodos de instancia. (Entre comillas porque esto huele a micro optimización) vea http://dotnetperls.com/static-method
  2. Le dice que no necesita estado, por lo tanto, podría llamarse desde cualquier lugar, eliminando la sobrecarga de la instancia si eso es lo único que alguien necesita.
  3. Si quiero simularlo, entonces creo que generalmente es la práctica que se declara en una interfaz.
  4. Si se declara en una interfaz, R # no le sugerirá que lo haga estático.
  5. Si se declara virtual, R # tampoco te sugerirá que lo hagas estático.
  6. Mantener el estado (campos) estáticamente es algo que siempre debe considerarse cuidadosamente . El estado estático y los hilos se mezclan como el litio y el agua.

R # no es la única herramienta que hará esta sugerencia. El análisis de código de FxCop/MS también hará lo mismo.

En general, diría que si el método es estático, en general también debería ser comprobable. Eso trae alguna consideración de diseño y probablemente más discusión de la que tengo en mis dedos en este momento, esperando pacientemente los votos y comentarios negativos ...;)

3
MIA

Si se llama al método estático desde dentro de otro método, no es posible evitar o sustituir dicha llamada. Esto significa que estos dos métodos forman una sola Unidad; La prueba de unidad de cualquier tipo los prueba a ambos.

Y si este método estático habla con Internet, conecta bases de datos, muestra ventanas emergentes de GUI o convierte la prueba de la Unidad en un desastre total, simplemente funciona sin ningún tipo de solución fácil. Un método que llama a este método estático no es comprobable sin refactorizar, incluso si tiene un montón de código puramente computacional que se beneficiaría enormemente de la prueba unitaria.

2
h22

Creo que Resharper le brinda orientación y aplica las pautas de codificación con las que se ha configurado. Cuando he usado Resharper y me ha dicho que un método debe ser estático, seguramente estará en un método privado que no actúa en ninguna variable de instancia.

Ahora, en cuanto a la capacidad de prueba, este escenario no debería ser un problema, ya que no debería probar métodos privados de todos modos.

En cuanto a la capacidad de prueba de los métodos estáticos que son públicos, las pruebas unitarias se vuelven difíciles cuando los métodos estáticos tocan el estado estático. Personalmente, mantendría esto al mínimo y usaría métodos estáticos como funciones puras tanto como sea posible cuando se pasen las dependencias al método que se pueden controlar a través de un dispositivo de prueba. Sin embargo, esta es una decisión de diseño.

0
aqwert