Nunit: La detección de AssertionException falla las pruebas desde la 3.10.

Creado en 14 mar. 2018  ·  22Comentarios  ·  Fuente: nunit/nunit

`` ``
tratar
{
Assert.True (falso, "mensaje de error");
}
captura (AssertionException e)
{
Trace.WriteLine ("Ignorar");
}

        Trace.WriteLine("Test Passed");

`` ``

En Nunit 3.9.0, esta prueba es exitosa y se marca como aprobada en el explorador de pruebas cuando se ejecuta desde el explorador de pruebas de Visual Studio. En Nunit 3.10, el caso de prueba se marca como fallido en la descripción general del explorador de pruebas. La última línea de rastreo se ejecuta en ambas versiones.

question

Comentario más útil

@fluffynuts Hace mucho tiempo, cuando escribí Assert.Catch vez, simplemente detectó cualquier excepción y no generó un error si no se lanzaba ninguna excepción. Desafortunadamente, eso cambió y ya no tenemos un equivalente "neutral" de Assert.Throws . Sin embargo, podrías escribir uno. Aquí hay una versión no probada basada en el código en Assert.Throws . Solo maneja código síncrono, pero puede extenderlo fácilmente para asíncrono usando código adicional desde Assert.Throws .

`` C #
Excepción vacía estática pública SafelyCatchAnNUnitException (código TestDelegate)
{
Excepción catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
''

El método devolverá cualquier excepción que se lance, limpiando cualquier remanente del error dejado por los métodos de aserción de NUnit. Si no se lanza ninguna excepción, devuelve nulo.

También es posible usar IsolatedContext() en un nivel superior en su código para recuperar el resultado real de la prueba. Eso le permitiría probar completamente al nivel del resultado de la prueba, sin ninguna referencia a excepciones. He pensado en escribir una biblioteca para este tipo de pruebas, que creo que sería superior a la forma en que NUnit hace actualmente sus propias pruebas.

Todos 22 comentarios

Su prueba está probando dos proposiciones sobre cómo funciona NUnit:

  1. Que todos los fallos de aserción arrojan una AssertionException.
  2. Que la captura de la excepción evita que se informe del error.

Da la casualidad de que la primera afirmación es mayoritariamente verdadera, pero no se mantiene dentro de varios bloques de aserción o cuando se emiten advertencias. El segundo no es cierto en absoluto. En el momento en que detecta la excepción, el error ya se ha registrado. Este es un cambio en el comportamiento interno de NUnit, solo observable si está detectando estas excepciones. Yo, por mi parte, le he estado diciendo a la gente que no confíe en ese comportamiento durante años, ya que es simplemente un accidente de implementación.

Me doy cuenta de que su código de muestra solo está destinado a demostrar el problema que ve. ¿Puede dar un ejemplo de lo que realmente está tratando de hacer para detectar la excepción? Estoy bastante seguro de que podemos ayudarlo a encontrar una mejor solución.

Gracias por responderme en tan poco tiempo. Utilizo el mecanismo cuando las cosas aún son inestables. Entonces, a veces, el software no funciona correctamente. En estos casos, informo de esto a los desarrolladores. Mientras tanto, ampliamos la prueba para solucionar este comportamiento irregular. Entonces, la prueba pasa en lugar de fallar en este problema.

Intentamos usar el método Warn.If () para esto, sin embargo, esto establece el resultado en "Prueba omitida" en el explorador de pruebas. Esto provoca fallas en los informes, ya que la prueba no se omite en absoluto. Por lo tanto, elegimos implementar la captura de aserción en su lugar y rastrear una advertencia. Lo sé, no es deseable, pero funcionó bastante bien. Al menos hasta la última actualización.

Tengo curiosidad por saber si este cambio fue intencional, en ese caso sé que tengo que cambiar las cosas.

Warn.If funciona bien ... __excepto__ en el Explorador de pruebas. 😢 El problema es que Test Explorer sabe sobre pasa y falla, pero no advierte. Tuvimos que traducir nuestros resultados de advertencia a algo que pudieran entender hasta el momento en que implementaran un estado de resultado de advertencia. FWIW tenemos un problema similar con nuestro estado de resultado no concluyente.

El cambio que hicimos fue intencional y se ha estado planificando durante mucho tiempo. No queremos depender de excepciones la mayor parte del tiempo y queremos informar el resultado de las afirmaciones, incluidas las aprobadas eventualmente, de alguna manera que no detenga la prueba. Hemos realizado algunos cambios para mitigar el impacto de esto para usuarios como usted, pero este no parecía tener ninguna mitigación posible.

Si tiene pruebas escamosas, la norma durante muchos años ha sido ignorarlas. Desafortunadamente, muchos equipos "ignoran las pruebas ignoradas", en cuyo caso no es una buena elección. Si puede capacitar a su equipo para que se tome las advertencias en serio, casi tan en serio, de hecho, como los errores y fallas, entonces puede usar el estado de advertencia para resaltar las fallas. Otra posibilidad es usar Ignorar con la propiedad Hasta, por lo que se convierte en una falla si no se corrige en una fecha determinada.

Por cierto, cuando entreno equipos, generalmente tengo una gran pantalla visible actualizada continuamente de pruebas ignoradas. En la mayoría de los equipos, todos saben quién es responsable de qué, por lo que no es necesario llamar a la persona responsable de la prueba inestable. Suelen solucionarse cuando todo el mundo ve el problema.

@svanimpelen, ¿ ha intentado utilizar el atributo Retry para sus pruebas inofensivas? Te permitirá ejecutarlos varias veces para que pasen.

En cuanto a las advertencias que resultan en pruebas omitidas en el explorador, hemos hablado con el equipo de Visual Studio sobre permitirnos especificar más estados de resultados que el número limitado de MSTest que admiten actualmente. Les gusta la idea, así que esperamos ver mejoras en el futuro. Hasta entonces, tendrás que sufrir con la omisión. Dicho esto, la omisión genera advertencias. Asumiría que querías que las advertencias fueran muy obvias 😄

Muchos de nuestros métodos documentan que la API arroja AssertionException en caso de falla:

https://github.com/nunit/nunit/blob/d03e93c8f25170a8ff48e80863a3fe6fd294613a/src/NUnitFramework/framework/Assert.That.cs#L40 -L43

¿No constituye esta documentación, más el comportamiento a largo plazo de NUnit, un cambio radical en 3.10?
A menos que me falte algo, al menos deberíamos eliminar los documentos ahora incorrectos.

Estoy de acuerdo en que la documentación puede ser engañosa ahora que hemos introducido varios bloques de aserción. Dicho esto, el AssertionException todavía se está lanzando en este caso, la diferencia es que la prueba ahora se informa como una falla en Visual Studio a pesar de que se detectó la excepción. No veo eso como un cambio rotundo. Los usuarios confiaban en un comportamiento indocumentado que funcionaba. Para mí, eso no es diferente a los usuarios que confían en el hecho de que las pruebas se ejecutan en orden alfabético para ordenar sus pruebas.

Probablemente deberíamos actualizar la documentación. Podríamos eliminar la información sobre el lanzamiento de una excepción y, en su lugar, simplemente declarar que una condición falsa no pasa la prueba. Y / o, ¿podríamos afirmar que la excepción no se lanza en bloques de múltiples aserciones? Personalmente, creo que el tipo de excepción que se lanza es un detalle interno, así que prefiero la primera opción.

Creo que eso es correcto. Si se va a documentar el uso de excepciones en cualquier lugar, podría ser en la sección wiki sobre aspectos internos.

Cuando la documentación del método dice que se lanzará una excepción, creo que está diciendo inequívocamente que la excepción se puede detectar en el sitio de la llamada. C # no tiene excepciones comprobadas y los documentos XML son la forma en que hemos estado comunicando esa parte del contrato del método. Dado que, hasta donde yo sé, el lenguaje "arroja X" siempre ha significado que el método no lo detecta, todavía estoy un poco preocupado de que sea así como otros lo han tomado. ¿Quizás una nota de cambio de última hora en la wiki para estar seguro?

Me gusta la idea de eliminar todas las referencias a AssertionException de los documentos XML y de crear una página wiki que advierta a las personas que no utilicen AssertionException.

💭Si detectar AssertionException siempre es un error, ¿podemos considerar hacer que ese tipo sea interno?

@ jnm2.

Estoy de acuerdo con usted en la importancia de los documentos XML, pero el hecho es que esos documentos __no__ son la forma en que hemos estado comunicando parte del contrato hasta ahora. De hecho, creo que puede ser la primera persona en sugerirlo y creo que es una buena idea.

¡Pero creo que debes reconocer que comenzar a pensar en ellos de esa manera implicará cambiar muchos comentarios erróneos! No creo que podamos tratar cada uno de estos cambios de documentación como un cambio radical. Caso por caso, podemos tratar razonablemente cualquier cambio de comportamiento subyacente como una ruptura, pero eso es diferente. Los cambios en los documentos no deberían romperse.

Esto plantea la pregunta de qué debería estar en la lista de cambios importantes. Si he entendido sus comentarios anteriores, creo que tiende a querer documentar cualquier cosa que pueda afectar a cualquier usuario. Prefiero documentar cosas que creemos que dañarían a muchos usuarios.

Así es como veo la diferencia: otros pueden estar en desacuerdo ... Documentar __todo lo que podría romperse__ me huele a CYA, como el tipo de CYA que a veces se entrega a la gerencia cuando se enfoca en quién es culpado por el fracaso en lugar de prevenir el fracaso en sí. SI documentamos todo, entonces podemos decir ... "Mira, estamos cubiertos". Si solo documentamos lo que consideramos importante, corremos el riesgo de equivocarnos y tener que disculparnos, pero le damos al usuario medio un conjunto de cambios mucho más fácil de entender. Prefiero lo último.

Hay ocasiones en las que es seguro detectar una excepción de aserción. Por ejemplo, hice un cambio hace un tiempo para que permaneciera seguro al ejecutar pruebas directamente, sin un TestExecutionContext involucrado. Si hace que la excepción sea interna, la rompe. En realidad, estaba feliz de romperlo, pero el equipo acordó que no deberíamos, así que lo arreglé. Solo tienes que decidir qué estás dispuesto a romper. Esa solución __no__ manejó el problema actual, que trata de detectar la excepción dentro de la prueba en sí, en lugar de hacerlo en el código que invoca el método.

La captura de AssertionException en una prueba es lo que yo llamaría un "olor" clásico. Probablemente esté mal, pero debes mirar el caso específico para saberlo. Les diría a los usuarios "Lo más probable es que captar AssertionException sea un error, a menos que tenga un conocimiento detallado de los aspectos internos de NUnit. Para garantizar un comportamiento adecuado, no debe intentar cambiar el resultado de la prueba y debe volver a lanzar la excepción después de hacer lo que quiera hacer con eso." Sin embargo, todavía no creo que esa información pertenezca a los documentos XML o los documentos generales del usuario. Lo pondría como una página en la sección interna.

Por cierto, mi experiencia anterior ha sido que documentar algo y luego decirle a la gente que no lo use conduce a un mayor uso. 😄

Buenos puntos, gracias! Creo que estoy pensando en parte en términos de CYA. También hay otro elemento, que es que, como consumidor de bibliotecas, personalmente agradecería que me informaran de todos los cambios importantes para poder buscar suposiciones erróneas en mi propio código. Particularmente con cambios como este que el compilador no nota. Como lo agradecería, lo proyecto en nuestros usuarios hasta cierto punto.

¿Deberíamos iniciar un nuevo problema para realizar un seguimiento de la corrección de documentos?

¿Por qué nuevo?

El título parece algo que cerraríamos como una pregunta respondida en lugar de solucionarlo. ¿Normalmente cambiaríamos el título y usaríamos este problema para realizar un seguimiento del cambio de documento?

Lo que he hecho normalmente es revisar el problema y clasificarlo ... en este caso como un problema de documentación. Es cierto que hemos pasado mucho tiempo discutiéndolo como un posible error primero. Es una cuestión de administración del proyecto, en realidad, y está fuera de mi alcance.

Lo que estoy diciendo es hacer algo lógico y coherente. 😄 Cerrar esto porque no es un error es una opción. Sin embargo, eso separa la queja de la solución. Escribir un comentario explicativo mientras lo reclasifica como un error de documentación y posiblemente edita el título mantiene la solución con la queja. En cualquier caso, el título de la edición de Problemas terminados termina en las notas de la versión, por lo que debe expresar lo que sucedió.

¿Puede dar un ejemplo de lo que realmente está tratando de hacer para detectar la excepción? Estoy bastante seguro de que podemos ayudarlo a encontrar una mejor solución.

Actualmente estoy enfrentando el mismo problema desde que mis pruebas comenzaron a fallar después de actualizar a 3.10. Ahora este es mi problema:
Tengo métodos de afirmación de prueba personalizados. Aquellos internamente llamados NUnit.Assert. Pruebo estos métodos de afirmación para asegurarme de que realmente fallan cuando espero que fallen.
Actualmente, mis pruebas detectan el AssertionException para verificar que el método de aserción personalizado hará que una prueba falle, pero después de actualizar, todas esas pruebas comienzan a fallar debido a las razones anteriores.

¿Cuál es la forma recomendada de probar aquellos con 3.10?

Gracias.

El enfoque más simple es usar Assert.Throws para detectar la excepción en lugar de hacerlo usted mismo. Assert.Throws comprende los aspectos internos de NUnit y se asegura de que no se informe de la falla.

Vi esto en mis notificaciones el otro día y no pensé mucho en ello. Sin embargo, actualizar un proyecto de biblioteca mío, que hace "Assert.That" dentro de su lógica (es un marco auxiliar de prueba, específicamente para proporcionar una manera fácil de probar la persistencia de EF al codificar contextos y entidades de EF a mano y codificar la base de datos correspondiente migraciones a mano (o con algo como FluentMigrator); en otras palabras, se supone que debe realizar afirmaciones en nombre de la persona que llama), ahora obtengo pruebas que fallan donde no deberían estar: la única prueba en particular es la afirmación que una determinada condición debería producir fallos en las pruebas, lo cual es así, pero dado que "catch" ya no funciona como siempre, estas pruebas, aunque en realidad pasan, se informan como fallidas.

Entonces, supongo que no puedo lanzar afirmaciones NUnit adecuadas, o usar Assert.That en el código de la biblioteca destinado a ayudar con el desarrollo de pruebas más rápido al realizar tareas tediosas para el usuario.

Estoy de acuerdo con declaraciones anteriores de que si el usuario no puede consumir la excepción, no debería estar disponible para que la lance el usuario; sin embargo, eso no resuelve el problema en el que tengo el código Assert.That en su lugar para el consumidor y las pruebas para demostrar que arrojan fallas porque el código hace lo que se supone que debe hacer: fallar con aserciones nunit.

¿Quizás deberíamos producir algunas de las API de resultados?

@fluffynuts Si te entiendo correctamente, la cláusula catch está en tu código de prueba, no en el código de tu biblioteca de usuario. (Si también está en la biblioteca del usuario, podemos hablar de eso, pero los problemas son ligeramente diferentes).

Por supuesto, NUnit tiene pruebas de sí mismo, por lo que tuvimos exactamente el mismo problema potencial cuando se realizó este cambio. Naturalmente, modificamos nuestras propias pruebas para que siguieran pasando. Puedes hacer lo mismo.

La primera línea de defensa es Assert.Throws . En la mayoría de los casos de prueba de comportamiento de falla, puede reemplazar try / catch(AssertionException) con Assert.Throws<AssertionException> directamente. Internamente Assert.Throws crea un TestExecutionContext desechable aislado para que los resultados reales de la prueba no estén contaminados por el error que está probando.

En algunos casos, es posible que deba crear su propio contexto aislado. Puede ver cómo hacerlo examinando el código de Assert.Throws .

Sin embargo, gran parte de las propias pruebas de fallos de NUnit utilizan un enfoque diferente. Tenemos un ensamblaje separado con pruebas que son ejecutadas por nuestros propios programas de prueba. Capturamos el resultado y luego podemos examinarlo. Esto es más trabajo pero evita poner demasiados detalles de implementación sobre NUnit en nuestras pruebas. La mayoría de los métodos que hacen esto se encuentran en nuestro ensamblaje de utilidades de prueba. Creo que a eso se refiere

Publique cualquier pregunta que tenga o contácteme fuera de línea para obtener más ayuda con esto.

Gracias por la info. Sí, las afirmaciones se capturan dentro del código de prueba. Assert.Throws normalmente funcionaría, pero la prueba específica en cuestión ejecuta el código de la biblioteca con un delta "demasiado bajo" permitido para probar un valor de fecha y hora que se debe ingresar y recuperar de una base de datos. La mayoría de las veces, la deriva experimentada para esto en localdb es de 1 ms, pero a veces aumenta más, y esta prueba solo tiene como objetivo afirmar que "a veces" ocurre. Entonces, en este momento, ejecuto el código en 4 subprocesos paralelos, 10 veces y espero al menos una falla. Assert.Throws es, desafortunadamente, demasiado determinista para este caso, por lo que necesito dejar de usar excepciones nunit (o Assert, dentro del código de la biblioteca), o necesito aprender el fu que ustedes usan. Agradecería cualquier sugerencia: puede suceder sin conexión, si lo desea.

Je, me acabo de dar cuenta de que las fallas de prueba de la bomba de mensajes para mi AsyncVoidVerificationScope que he estado mirando son precisamente el mismo problema que tiene @fluffynuts . En realidad, debido a la actualización de NUnit 3.10, no a mi [ensamblado: RestoreSynchronizationContext] ITestAction. Me sirve bien por ignorarlos mientras hago varias cosas a la vez. :D

De todos modos, parece que lo que quiero hacer es envolver mi invocación de delegado en using (new TestExecutionContext.IsolatedContext()) como lo hace Assert.Throws .

Assert.Throws embargo, Assert.ThrowsAsync no establecen contextos aislados antes de invocar a los delegados asíncronos. ¿Por qué es esto?

@fluffynuts Hace mucho tiempo, cuando escribí Assert.Catch vez, simplemente detectó cualquier excepción y no generó un error si no se lanzaba ninguna excepción. Desafortunadamente, eso cambió y ya no tenemos un equivalente "neutral" de Assert.Throws . Sin embargo, podrías escribir uno. Aquí hay una versión no probada basada en el código en Assert.Throws . Solo maneja código síncrono, pero puede extenderlo fácilmente para asíncrono usando código adicional desde Assert.Throws .

`` C #
Excepción vacía estática pública SafelyCatchAnNUnitException (código TestDelegate)
{
Excepción catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
''

El método devolverá cualquier excepción que se lance, limpiando cualquier remanente del error dejado por los métodos de aserción de NUnit. Si no se lanza ninguna excepción, devuelve nulo.

También es posible usar IsolatedContext() en un nivel superior en su código para recuperar el resultado real de la prueba. Eso le permitiría probar completamente al nivel del resultado de la prueba, sin ninguna referencia a excepciones. He pensado en escribir una biblioteca para este tipo de pruebas, que creo que sería superior a la forma en que NUnit hace actualmente sus propias pruebas.

¿Fue útil esta página
0 / 5 - 0 calificaciones