unit-testing xunit.net

unit testing - Es Assert.Fail() considerado mala práctica?



unit-testing xunit.net (13)

¿Por qué usaría Assert.Fail para decir que se debe lanzar una excepción? Eso es innecesario. ¿Por qué no usar el atributo ExpectedException?

Yo uso Assert.Fail mucho cuando hago TDD. Normalmente trabajo en una prueba a la vez, pero cuando obtengo ideas para cosas que deseo implementar más adelante, escribo rápidamente una prueba vacía donde el nombre del método de prueba indica lo que quiero implementar como una especie de lista de tareas pendientes. Para asegurarme de que no me olvido pongo un Assert.Fail () en el cuerpo.

Al probar xUnit.Net, descubrí que no habían implementado Assert.Fail. Por supuesto, siempre puedes Assert.IsTrue (false) pero esto no comunica mi intención también. Me dio la impresión de que Assert.Fail no se implementó a propósito. ¿Esto se considera una mala práctica? Si es así, ¿por qué?

@Martin Meredith Eso no es exactamente lo que hago. Primero escribo una prueba y luego implemento el código para que funcione. Usualmente pienso en varias pruebas a la vez. O pienso en una prueba para escribir cuando estoy trabajando en otra cosa. Ahí es cuando escribo una prueba de falla vacía para recordar. Para cuando escribo la prueba trabajo prolijamente primero.

@Jimmeh Eso parece una buena idea. Las pruebas ignoradas no fallan pero siguen apareciendo en una lista separada. Tengo que probarlo.

@Matt Howells Great Idea. NotImplementedException comunica la intención mejor que assert.Fail () en este caso

@Mitch Wheat Eso es lo que estaba buscando. Parece que se dejó de lado para evitar que se abuse de otra forma en que lo abuse.


Con el buen código que suelo hacer:

void goodCode() { // TODO void goodCode() throw new NotSupportedOperationException("void goodCode()"); }

Con el código de prueba usualmente hago:

@Test void testSomething() { // TODO void test Something Assert.assert("Some descriptive text about what to test") }

Si utilizo JUnit, y no quiero obtener el error, pero sí el error, generalmente lo hago:

@Test void testSomething() { // TODO void test Something throw new NotSupportedOperationException("Some descriptive text about what to test") }


Conjetura salvaje: la retención de Assert.Fail tiene la intención de evitar que piense que una buena forma de escribir el código de prueba es una gran cantidad de espagueti que conduce a un Assert.Fail en los casos malos. [Editar para agregar: las respuestas de otras personas lo confirman ampliamente, pero con las citas]

Como eso no es lo que estás haciendo, es posible que xUnit.Net esté siendo demasiado protector.

O tal vez solo piensan que es tan raro y tan poco ortodoxo como para ser innecesario.

Prefiero implementar una función llamada ThisCodeHasNotBeenWrittenYet (en realidad algo más corta, para facilitar la escritura). No se puede comunicar la intención más claramente que eso, y tienes un término de búsqueda preciso.

Si eso falla, o no se implementa (para provocar un error del enlazador), o es una macro que no se compila, se puede cambiar para adaptarse a sus preferencias actuales. Por ejemplo, cuando quiere ejecutar algo que está terminado, quiere un error. Cuando está sentado para deshacerse de todos ellos, es posible que desee un error de compilación.


Creo que deberían preguntarse qué deberían hacer las pruebas (iniciales).

En primer lugar, escribe un (conjunto de) prueba sin implementación. Tal vez, también los escenarios de día lluvioso.

Todas esas pruebas deben fallar, para ser pruebas correctas: por lo que desea lograr dos cosas: 1) Verificar que su implementación sea correcta; 2) Verifique que las pruebas de su unidad sean correctas.

Ahora, si realiza TDD por adelantado, desea ejecutar todas sus pruebas, también, las partes NYI. El resultado de la ejecución total de la prueba pasa si: 1) Todo el material implementado tiene éxito 2) Todo el material de la NYI falla

Después de todo, sería una omisión de prueba unitaria si las pruebas de su unidad tienen éxito mientras no hay implementación, ¿o no?

Desea terminar con algo de un correo electrónico de su prueba de integración continua que verifica todo el código implementado y no implementado, y se envía si falla algún código implementado, o si cualquier código no implementado tiene éxito. Ambos son resultados no deseados.

Simplemente escriba un [ignore] las pruebas no harán el trabajo. Ninguno de los dos afirma que detiene la primera falla de afirmación y no ejecuta otras líneas de prueba en la prueba.

Ahora, ¿cómo lograr esto? Creo que requiere una organización más avanzada de sus pruebas. Y requiere algún otro mecanismo y luego afirma para lograr estos objetivos.

Creo que tienes que dividir tus pruebas y crear algunas pruebas que se ejecutan completamente pero deben fallar, y viceversa.

Las ideas consisten en dividir sus pruebas en múltiples conjuntos, use la agrupación de pruebas (las pruebas ordenadas en mstest pueden hacer el trabajo).

Aún así, una versión de CI que envía correos electrónicos, si no todas las pruebas en el departamento de la JNI no es fácil y directo.


Cuidado con Assert.Fail y su influencia corruptora para hacer que los desarrolladores escriban pruebas tontas o rotas. Por ejemplo:

[TestMethod] public void TestWork() { try { Work(); } catch { Assert.Fail(); } }

Esto es una tontería, porque el try-catch es redundante. Una prueba falla si arroja una excepción.

también

[TestMethod] public void TestDivide() { try { Divide(5,0); Assert.Fail(); } catch { } }

Esto está roto, la prueba siempre pasará cualquiera que sea el resultado de la función Divide. De nuevo, una prueba falla si y solo si arroja una excepción.


Este es el patrón que utilizo cuando escribo una prueba para el código que deseo arrojar una excepción por diseño:

[TestMethod] public void TestForException() { Exception _Exception = null; try { //Code that I expect to throw the exception. MyClass _MyClass = null; _MyClass.SomeMethod(); //Code that I expect to throw the exception. } catch(Exception _ThrownException) { _Exception = _ThrownException } finally { Assert.IsNotNull(_Exception); //Replace NullReferenceException with expected exception. Assert.IsInstanceOfType(_Exception, typeof(NullReferenceException)); } }

En mi humilde opinión, esta es una mejor manera de probar las excepciones sobre el uso de Assert.Fail (). La razón de esto es que no solo pruebo que se haya lanzado una excepción, sino que también pruebo el tipo de excepción. Me doy cuenta de que esto es similar a la respuesta de Matt Howells, pero en mi humilde opinión, el uso del bloque finally es más sólido.

Obviamente, aún sería posible incluir otros métodos de Assert para probar la cadena de entrada de excepciones, etc. Le agradecería sus comentarios y opiniones sobre mi patrón.


Fue deliberadamente excluido. Esta es la respuesta de Brad Wilson sobre por qué no hay Assert.Fail ():

No pasamos por alto esto, en realidad. Encuentro Assert.Fail es una muleta que implica que probablemente falte una afirmación. A veces es solo la forma en que se estructura la prueba, y a veces es porque Assert podría usar otra afirmación.


MS Test tiene Assert.Fail () pero también tiene Assert.Inconclusive () . Creo que el uso más apropiado para Assert.Fail () es si tienes alguna lógica en línea que sería incómodo poner en una aserción, aunque no puedo pensar en ningún buen ejemplo. En su mayor parte, si el marco de prueba es compatible con algo que no sea Assert.Fail (), entonces úselo.


Para este escenario, en lugar de llamar a Assert.Fail, hago lo siguiente (en C # / NUnit)

[Test] public void MyClassDoesSomething() { throw new NotImplementedException(); }

Es más explícito que un Assert.Fail.

Parece haber un acuerdo general de que es preferible usar afirmaciones más explícitas que Assert.Fail (). La mayoría de los marcos tienen que incluirlo porque no ofrecen una mejor alternativa. Por ejemplo, NUnit (y otros) proporcionan un ExpectedExceptionAttribute para probar que algún código arroja una clase particular de excepción. Sin embargo, para probar que una propiedad de la excepción se establece en un valor particular, no se puede usar. En cambio, debes recurrir a Assert.Fail:

[Test] public void ThrowsExceptionCorrectly() { const string BAD_INPUT = "bad input"; try { new MyClass().DoSomething(BAD_INPUT); Assert.Fail("No exception was thrown"); } catch (MyCustomException ex) { Assert.AreEqual(BAD_INPUT, ex.InputString); } }

El método xUnit.Net Assert.Throws lo hace mucho más ordenado sin requerir un método Assert.Fail. Al no incluir un método Assert.Fail (), xUnit.Net alienta a los desarrolladores a encontrar y utilizar alternativas más explícitas, y para apoyar la creación de nuevas afirmaciones cuando sea necesario.


Personalmente, no tengo ningún problema con el uso de un conjunto de pruebas como lista de tareas pendientes, siempre y cuando finalmente consiga escribir la prueba antes de implementar el código para aprobar.

Habiendo dicho eso, solía usar este enfoque yo mismo, aunque ahora descubro que hacerlo me lleva por el camino de escribir demasiadas pruebas por adelantado, lo que de una manera extraña es como el problema inverso de no escribir pruebas en absoluto: terminas tomando decisiones sobre el diseño demasiado pronto en mi humilde opinión.

Incidentalmente en MSTest, la plantilla de prueba estándar usa Assert.Inclusivo al final de sus muestras.

AFAIK el framework xUnit.NET está pensado para ser extremadamente liviano y sí lo hicieron. Fail deliberadamente, para alentar al desarrollador a utilizar una condición de falla explícita.


Si está escribiendo una prueba que simplemente falla y luego escribe el código para ella, entonces escriba la prueba. Esto no es desarrollo impulsado por prueba.

Técnicamente, Assert.fail () no debería ser necesario si está utilizando el desarrollo impulsado por prueba correctamente.

¿Has pensado en utilizar una Lista de tareas o aplicar una metodología GTD a tu trabajo?


Siempre he usado Assert.Fail () para manejar casos en los que ha detectado que una prueba debe fallar por lógica más allá de la simple comparación de valores. Como ejemplo:

try { // Some code that should throw ExceptionX Assert.Fail("ExceptionX should be thrown") } catch ( ExceptionX ex ) { // test passed }

Por lo tanto, la falta de Assert.Fail () en el marco parece un error para mí. Sugiero parchear la clase Assert para incluir un método Fail () y luego enviar el parche a los desarrolladores del framework, junto con su razonamiento para agregarlo.

En cuanto a su práctica de crear pruebas que fallan intencionalmente en su área de trabajo, para recordar que debe implementarlas antes de comprometerse, eso me parece una buena práctica.


Yo uso MbUnit para mi Prueba de Unidad. Tienen la opción de Ignorar pruebas, que se muestran como Naranja (en lugar de Verde o Rojo) en el conjunto de pruebas. Quizás xUnit tenga algo similar, y eso significa que ni siquiera tiene que poner ninguna afirmación en el método, ya que aparecería en un color molestamente diferente, lo que lo haría difícil de perder.

Editar:

En MbUnit es de la siguiente manera:

[Test] [Ignore] public void YourTest() { }