unitarias unit tutorial test pruebas las herramientas funcionales español ejemplo desventajas unit-testing assert

unit testing - tutorial - Las mejores prácticas para depurar aseveraciones durante pruebas unitarias



pruebas unitarias php (11)

¿Quiere decir que C ++ / Java afirma para las aserciones de "programación por contrato", o afirma CppUnit / JUnit? Esa última pregunta me lleva a creer que es la primera.

Una pregunta interesante, porque entiendo que esas afirmaciones a menudo se desactivan en tiempo de ejecución cuando se despliega en producción. (Un poco derrota el propósito, pero esa es otra pregunta).

Diría que deberían quedar en tu código cuando lo pruebes. Usted escribe pruebas para asegurarse de que las condiciones previas se apliquen correctamente. La prueba debe ser una "caja negra"; deberías actuar como cliente de la clase cuando realices la prueba. Si los apaga en producción, no invalida las pruebas.

¿El uso intensivo de pruebas unitarias desalienta el uso de afirmaciones de depuración? Parece que una activación de depuración de depuración en el código bajo prueba implica que la prueba de unidad no debería existir o que la depuración no debería existir. "Puede haber solo uno" parece un principio razonable. ¿Es esta la práctica común? ¿O desactiva sus afirmaciones de depuración cuando se realizan pruebas unitarias, para que puedan estar disponibles para las pruebas de integración?

Editar: actualicé ''Assert'' para depurar la afirmación para distinguir una afirmación en el código bajo prueba de las líneas en la prueba unitaria que verifican el estado después de que se haya ejecutado la prueba.

También aquí hay un ejemplo que creo que muestra el dilema: una prueba de unidad pasa entradas inválidas para una función protegida que afirma que sus entradas son válidas. ¿La prueba de unidad no debería existir? No es una función pública. ¿Quizás revisar las entradas mataría a perf? ¿O debería la afirmación no existir? La función está protegida, no es privada, por lo que debería verificar sus entradas por seguridad.


Una buena configuración de prueba de unidad tendrá la capacidad de atrapar aseveraciones. Si se activa una afirmación, la prueba actual debería fallar y la siguiente se ejecutará.

En nuestras bibliotecas, la funcionalidad de depuración de bajo nivel, como TTY / ASSERTS, tiene controladores llamados. El controlador predeterminado imprimirá f / break, pero el código del cliente puede instalar controladores personalizados para un comportamiento diferente.

Nuestro framework UnitTest instala sus propios manejadores que registran mensajes y lanzan excepciones en las afirmaciones. El código UnitTest detectará estas excepciones si ocurren y las registrará como un error, junto con la declaración afirmada.

También puede incluir pruebas afirmativas en la prueba de su unidad, por ej.

CHECK_ASSERT (someList.getAt (someList.size () + 1); // pasa la prueba si se produce una afirmación


Las afirmaciones en su código son (deberían ser) declaraciones para el lector que dicen "esta condición siempre debe ser cierta en este punto". Hechos con cierta disciplina, pueden ser parte de garantizar que el código sea correcto; la mayoría de las personas los usan como declaraciones de impresión de depuración. Las pruebas unitarias son un código que demuestra que su código realiza correctamente un caso de prueba en particular; no hagas bien, pueden documentar los requisitos y aumentar tu confianza en que el código es realmente correcto.

Obtener la diferencia? Las aserciones del programa lo ayudan a corregirlo, las pruebas de la unidad lo ayudan a desarrollar la confianza de otra persona de que el código es correcto.


Primero, para tener tanto afirmaciones de diseño por contrato como pruebas unitarias, su marco de prueba unitaria deberá ser capaz de captar las aserciones. Si las pruebas de su unidad abortan debido a un aborto DbC, entonces simplemente no puede ejecutarlos. La alternativa aquí es deshabilitar esas aserciones mientras se ejecuta (leer compilando) las pruebas de su unidad.

Dado que está probando funciones no públicas, ¿cuál es el riesgo de tener una función invocada con argumento no válido? ¿Las pruebas de su unidad no cubren ese riesgo? Si escribe su código siguiendo la técnica TDD (Desarrollo controlado por prueba), deberían hacerlo.

Si realmente quiere / necesita esas afirmaciones de tipo Dbc en su código, entonces puede eliminar las pruebas unitarias que pasan los argumentos inválidos a los métodos que tienen esos asertos.

Sin embargo, las afirmaciones de tipo Dbc pueden ser útiles en las funciones de nivel inferior (que las pruebas unitarias no invocan directamente) cuando tiene pruebas unitarias de grano grueso.


Como los otros han mencionado, las instrucciones Debug.Assert deben ser siempre ciertas , incluso si los argumentos son incorrectos, la afirmación debe ser cierta para que la aplicación entre en un estado no válido, etc.

Debug.Assert(_counter == somethingElse, "Erk! Out of wack!");

No deberías poder probar esto (¡y probablemente no quieras porque no hay nada que puedas hacer realmente!)

Podría estar muy lejos, pero me da la impresión de que tal vez las afirmaciones de las que puede estar hablando son más adecuadas como "excepciones argumentales", por ejemplo

if (param1 == null) throw new ArgumentNullException("param1", "message to user")

Ese tipo de "afirmación" en su código todavía es muy comprobable.

PK :-)


Esta es una pregunta perfectamente válida.

En primer lugar, muchas personas sugieren que estás usando aserciones erróneamente. Creo que muchos expertos en depuración estarían en desacuerdo. Aunque es una buena práctica verificar invariantes con aserciones, las aserciones no deben limitarse a invariantes del estado. De hecho, muchos depuradores expertos le pedirán que afirme cualquier condición que pueda causar una excepción, además de verificar invariantes.

Por ejemplo, considere el siguiente código:

if (param1 == null) throw new ArgumentNullException("param1");

Esta bien. Pero cuando se lanza la excepción, la pila se desenrolla hasta que algo maneje la excepción (probablemente algún manejador predeterminado de nivel superior). Si la ejecución se detiene en ese punto (puede tener un cuadro de diálogo de excepción modal en una aplicación de Windows), tiene la posibilidad de adjuntar un depurador, pero probablemente haya perdido mucha información que podría haberle ayudado a solucionar el problema, porque la mayor parte de la pila ha sido desenrollada.

Ahora considera lo siguiente:

if (param1 == null) { Debug.Fail("param1 == null"); throw new ArgumentNullException("param1"); }

Ahora, si ocurre el problema, aparece el cuadro de diálogo modal assert. La ejecución está pausada instantáneamente. Puede adjuntar el depurador elegido e investigar exactamente qué hay en la pila y todo el estado del sistema en el punto exacto de falla. En una compilación de lanzamiento, todavía obtienes una excepción.

¿Ahora cómo manejamos sus pruebas unitarias?

Considere una prueba unitaria que pruebe el código anterior que incluye la afirmación. Desea comprobar que la excepción se produce cuando param1 es nulo. Espera que esa afirmación particular falle, pero cualquier otra falla de afirmación indicaría que algo está mal. Desea permitir fallas de afirmación particulares para pruebas particulares.

La forma en que resuelva esto dependerá de los idiomas, etc., que esté utilizando. Sin embargo, tengo algunas sugerencias si está usando .NET (en realidad no lo he intentado, pero lo haré en el futuro y actualizo la publicación):

  1. Compruebe Trace.Listeners. Busque cualquier instancia de DefaultTraceListener y establezca AssertUiEnabled en falso. Esto evita que aparezca el cuadro de diálogo modal. También puede borrar la colección de oyentes, pero no obtendrá ningún seguimiento en absoluto.
  2. Escriba su propio TraceListener que registre las afirmaciones. Cómo se registran las afirmaciones depende de usted. Es posible que la grabación del mensaje de error no sea lo suficientemente buena, por lo que es posible que desee recorrer la pila para encontrar el método del que proviene la afirmación y registrarlo también.
  3. Una vez que finaliza una prueba, verifique que las únicas fallas de aserción que ocurrieron fueron las que estaba esperando. Si ocurrieron otros, falle la prueba.

Para un ejemplo de un TraceListener que contiene el código para hacer una caminata como esa, buscaría SuperAssertListener de SUPERASSERT.NET y verificaría su código. (También vale la pena integrar SUPERASSERT.NET si realmente quieres depurar usando aserciones).

La mayoría de los marcos de prueba de unidades admiten métodos de configuración / desmontaje de prueba. Es posible que desee agregar código para restablecer el escucha de rastreo y afirmar que no hay fallas de afirmación inesperadas en esas áreas para minimizar la duplicación y evitar errores.

ACTUALIZAR:

Aquí hay un ejemplo de TraceListener que se puede usar para verificar las afirmaciones de la unidad. Debe agregar una instancia a la colección Trace.Listeners. Probablemente también desees proporcionar alguna forma sencilla para que tus pruebas puedan controlar al oyente.

NOTA: Esto le debe muchísimo a SUPERASSERT.NET de John Robbins.

/// <summary> /// TraceListener used for trapping assertion failures during unit tests. /// </summary> public class DebugAssertUnitTestTraceListener : DefaultTraceListener { /// <summary> /// Defines an assertion by the method it failed in and the messages it /// provided. /// </summary> public class Assertion { /// <summary> /// Gets the message provided by the assertion. /// </summary> public String Message { get; private set; } /// <summary> /// Gets the detailed message provided by the assertion. /// </summary> public String DetailedMessage { get; private set; } /// <summary> /// Gets the name of the method the assertion failed in. /// </summary> public String MethodName { get; private set; } /// <summary> /// Creates a new Assertion definition. /// </summary> /// <param name="message"></param> /// <param name="detailedMessage"></param> /// <param name="methodName"></param> public Assertion(String message, String detailedMessage, String methodName) { if (methodName == null) { throw new ArgumentNullException("methodName"); } Message = message; DetailedMessage = detailedMessage; MethodName = methodName; } /// <summary> /// Gets a string representation of this instance. /// </summary> /// <returns></returns> public override string ToString() { return String.Format("Message: {0}{1}Detail: {2}{1}Method: {3}{1}", Message ?? "<No Message>", Environment.NewLine, DetailedMessage ?? "<No Detail>", MethodName); } /// <summary> /// Tests this object and another object for equality. /// </summary> /// <param name="obj"></param> /// <returns></returns> public override bool Equals(object obj) { var other = obj as Assertion; if (other == null) { return false; } return this.Message == other.Message && this.DetailedMessage == other.DetailedMessage && this.MethodName == other.MethodName; } /// <summary> /// Gets a hash code for this instance. /// Calculated as recommended at http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx /// </summary> /// <returns></returns> public override int GetHashCode() { return MethodName.GetHashCode() ^ (DetailedMessage == null ? 0 : DetailedMessage.GetHashCode()) ^ (Message == null ? 0 : Message.GetHashCode()); } } /// <summary> /// Records the assertions that failed. /// </summary> private readonly List<Assertion> assertionFailures; /// <summary> /// Gets the assertions that failed since the last call to Clear(). /// </summary> public ReadOnlyCollection<Assertion> AssertionFailures { get { return new ReadOnlyCollection<Assertion>(assertionFailures); } } /// <summary> /// Gets the assertions that are allowed to fail. /// </summary> public List<Assertion> AllowedFailures { get; private set; } /// <summary> /// Creates a new instance of this trace listener with the default name /// DebugAssertUnitTestTraceListener. /// </summary> public DebugAssertUnitTestTraceListener() : this("DebugAssertUnitTestListener") { } /// <summary> /// Creates a new instance of this trace listener with the specified name. /// </summary> /// <param name="name"></param> public DebugAssertUnitTestTraceListener(String name) : base() { AssertUiEnabled = false; Name = name; AllowedFailures = new List<Assertion>(); assertionFailures = new List<Assertion>(); } /// <summary> /// Records assertion failures. /// </summary> /// <param name="message"></param> /// <param name="detailMessage"></param> public override void Fail(string message, string detailMessage) { var failure = new Assertion(message, detailMessage, GetAssertionMethodName()); if (!AllowedFailures.Contains(failure)) { assertionFailures.Add(failure); } } /// <summary> /// Records assertion failures. /// </summary> /// <param name="message"></param> public override void Fail(string message) { Fail(message, null); } /// <summary> /// Gets rid of any assertions that have been recorded. /// </summary> public void ClearAssertions() { assertionFailures.Clear(); } /// <summary> /// Gets the full name of the method that causes the assertion failure. /// /// Credit goes to John Robbins of Wintellect for the code in this method, /// which was taken from his excellent SuperAssertTraceListener. /// </summary> /// <returns></returns> private String GetAssertionMethodName() { StackTrace stk = new StackTrace(); int i = 0; for (; i < stk.FrameCount; i++) { StackFrame frame = stk.GetFrame(i); MethodBase method = frame.GetMethod(); if (null != method) { if(method.ReflectedType.ToString().Equals("System.Diagnostics.Debug")) { if (method.Name.Equals("Assert") || method.Name.Equals("Fail")) { i++; break; } } } } // Now walk the stack but only get the real parts. stk = new StackTrace(i, true); // Get the fully qualified name of the method that made the assertion. StackFrame hitFrame = stk.GetFrame(0); StringBuilder sbKey = new StringBuilder(); sbKey.AppendFormat("{0}.{1}", hitFrame.GetMethod().ReflectedType.FullName, hitFrame.GetMethod().Name); return sbKey.ToString(); } }

Puede agregar Assertions a la colección AllowedFailures al comienzo de cada prueba para las aserciones que espera.

Al final de cada prueba (es de esperar que el marco de prueba de la unidad sea compatible con un método de prueba de desmontaje):

if (DebugAssertListener.AssertionFailures.Count > 0) { // TODO: Create a message for the failure. DebugAssertListener.ClearAssertions(); DebugAssertListener.AllowedFailures.Clear(); // TODO: Fail the test using the message created above. }


Debe mantener sus afirmaciones de depuración, incluso con pruebas de unidad en su lugar.

El problema aquí no es diferenciar entre Errores y Problemas.

Si una función comprueba sus argumentos que son erróneos, no debería dar como resultado una aserción de depuración. En su lugar, debe devolver un valor de error. Fue un error llamar a la función con parámetros incorrectos.

Si una función recibe datos correctos, pero no puede funcionar correctamente debido a que el tiempo de ejecución se ha quedado sin memoria, entonces el código debería emitir una afirmación de depuración debido a este problema. Ese es un ejemplo de suposiciones fundamentales que, si no son válidas, "todas las apuestas están apagadas", por lo que debe finalizar.

En su caso, escriba la prueba de unidad que proporciona valores erróneos como argumentos. Debería esperar un valor de retorno de error (o similar). Obtener una afirmación? - refactorizar el código para producir un error en su lugar.

Tenga en cuenta que un problema sin errores todavía puede desencadenar aseveraciones; por ejemplo, el hardware podría romperse. En su pregunta, usted mencionó las pruebas de integración; de hecho, afirmar contra sistemas integrados incorrectamente compuestos es afirmar territorio; por ejemplo, versión de biblioteca incompatible cargada.

Tenga en cuenta que el motivo de "depuración" es una compensación entre ser diligente / seguro y ser rápido / pequeño.


IMHO debug.asserts rock. Este excelente artículo muestra cómo evitar que interrumpan la prueba de su unidad agregando un app.config a su proyecto de prueba de la unidad y deshabilitando el cuadro de diálogo:

<?xml version="1.0" encoding="utf-8"?> <configuration> <system.diagnostics> <assert assertuienabled="false"/> </system.diagnostics>


Ha pasado un tiempo desde que se formuló esta pregunta, pero creo que tengo una forma diferente de verificar las llamadas a Debug.Assert () desde una prueba unitaria usando el código C #. Tenga en cuenta el #if DEBUG ... #endif , que es necesario para omitir la prueba cuando no se ejecuta en la configuración de depuración (en cuyo caso, Debug.Assert () no se desencadenará de todos modos).

[TestClass] [ExcludeFromCodeCoverage] public class Test { #region Variables | private UnitTestTraceListener _traceListener; private TraceListenerCollection _originalTraceListeners; #endregion #region TestInitialize | [TestInitialize] public void TestInitialize() { // Save and clear original trace listeners, add custom unit test trace listener. _traceListener = new UnitTestTraceListener(); _originalTraceListeners = Trace.Listeners; Trace.Listeners.Clear(); Trace.Listeners.Add(_traceListener); // ... Further test setup } #endregion #region TestCleanup | [TestCleanup] public void TestCleanup() { Trace.Listeners.Clear(); Trace.Listeners.AddRange(_originalTraceListeners); } #endregion [TestMethod] public void TheTestItself() { // Arrange // ... // Act // ... Debug.Assert(false, "Assert failed"); // Assert #if DEBUG // NOTE This syntax comes with using the FluentAssertions NuGet package. _traceListener.GetWriteLines().Should().HaveCount(1).And.Contain("Fail: Assert failed"); #endif } }

La clase UnitTestTraceListener tiene el siguiente aspecto:

[ExcludeFromCodeCoverage] public class UnitTestTraceListener : TraceListener { private readonly List<string> _writes = new List<string>(); private readonly List<string> _writeLines = new List<string>(); // Override methods public override void Write(string message) { _writes.Add(message); } public override void WriteLine(string message) { _writeLines.Add(message); } // Public methods public IEnumerable<string> GetWrites() { return _writes.AsReadOnly(); } public IEnumerable<string> GetWriteLines() { return _writeLines.AsReadOnly(); } public void Clear() { _writes.Clear(); _writeLines.Clear(); } }


¿El uso intensivo de pruebas unitarias desalienta el uso de afirmaciones de depuración?

No. Lo opuesto. Las pruebas unitarias hacen que Debug sea mucho más valioso al verificar dos veces el estado interno mientras ejecuta las pruebas de la caja blanca que ha escrito. La habilitación de Debug.Assert durante la prueba de la unidad es esencial, ya que rara vez se envía un código compatible con DEBUG (a menos que el rendimiento no sea importante en absoluto). El único código de DEBUG que se ejecuta dos veces es cuando estás 1) haciendo ese pequeño fragmento de prueba de integración que realmente haces, todas las buenas intenciones de lado, y 2) ejecutando pruebas unitarias.

Es fácil instrumentar el código con Debug.Assertar pruebas para verificar invariantes a medida que lo escribe. Estas verificaciones sirven como verificaciones de cordura cuando se ejecutan pruebas unitarias.

Las otras cosas que Assert hace es señalar exactamente el primer punto del código donde las cosas salieron mal. Esto puede reducir en gran medida el tiempo de depuración cuando la prueba unitaria encuentra el problema.

Esto aumenta el valor de las pruebas unitarias.

Parece que una activación de depuración de depuración en el código bajo prueba implica que la prueba de unidad no debería existir o que la depuración no debería existir.

Caso en punto. Esta pregunta es acerca de una cosa real que sucede. ¿Derecha? Por lo tanto, necesita afirmaciones de depuración en su código, y necesita que se activen durante las pruebas unitarias. La posibilidad de que una afirmación de depuración se active durante una prueba unitaria demuestra claramente que los asertos de depuración deben habilitarse durante las pruebas unitarias.

Una activación afirmativa significa que sus pruebas están usando su código interno incorrectamente (y deberían ser corregidas), o que parte del código bajo prueba llama a otro código interno incorrectamente, o que en alguna parte una suposición fundamental es incorrecta. No escribes pruebas porque piensas que tus suposiciones son incorrectas, tú ... en realidad, lo haces. Usted escribe pruebas porque al menos algunas de sus suposiciones son probablemente incorrectas. La redundancia está bien en esta situación.

"Puede haber solo uno" parece un principio razonable. ¿Es esta la práctica común? ¿O desactiva sus afirmaciones de depuración cuando se realizan pruebas unitarias, para que puedan estar disponibles para las pruebas de integración?

La redundancia no hace más daño que el tiempo de ejecución de las pruebas de su unidad. Si realmente tiene una cobertura del 100%, el tiempo de ejecución podría ser un problema. De lo contrario, no estoy totalmente en desacuerdo. No hay nada de malo en verificar su suposición automáticamente en el medio de una prueba. Esa es prácticamente la definición de "prueba".

También aquí hay un ejemplo que creo que muestra el dilema: una prueba de unidad pasa entradas inválidas para una función protegida que afirma que sus entradas son válidas. ¿La prueba de unidad no debería existir? No es una función pública. ¿Quizás revisar las entradas mataría a perf? ¿O debería la afirmación no existir? La función está protegida, no es privada, por lo que debería verificar sus entradas por seguridad.

Por lo general, no es el propósito de un marco de prueba unitario probar el comportamiento de su código cuando se han violado las suposiciones invariables. En otras palabras, si la documentación que escribió dice "si pasa nulo como parámetro, los resultados no están definidos", no necesita verificar que los resultados sean impredecibles. Si los resultados de la falla están claramente definidos, no están indefinidos, y 1) no debe ser un Debug.Assert, 2) debe definir exactamente cuáles son los resultados, y 3) probar ese resultado. Si necesita probar la calidad de sus afirmaciones internas de depuración, entonces 1) El enfoque de Andrew Grant de hacer que los marcos de Aserción sean un activo comprobable probablemente debería ser verificado como la respuesta, y 2) ¡guau tiene una excelente cobertura de prueba! Y creo que esto es en gran medida una decisión personal basada en los requisitos del proyecto. Pero todavía creo que las afirmaciones de depuración son esenciales y valiosas.

En otras palabras: Debug.Assert () aumenta en gran medida el valor de las pruebas unitarias, y la redundancia es una característica.


Como otros han mencionado, Debug afirma que están destinados a cosas que siempre deberían ser ciertas . (El término sofisticado para esto es invariantes ).

Si su prueba de unidad está pasando datos falsos que están disparando la afirmación, entonces tiene que hacerse la pregunta: ¿por qué está sucediendo eso?

  • Si se supone que la función bajo prueba trata con los datos falsos, entonces claramente esa afirmación no debería estar allí.
  • Si la función no está equipada para tratar con ese tipo de datos (como lo indica la afirmación), entonces, ¿por qué pruebas unitarias para ello?

El segundo punto es uno en el que parecen bastar algunos desarrolladores. Prueba la unidad de todas las cosas para las que está diseñado tu código, y afirma o lanza excepciones para todo lo demás. Después de todo, si tu código NO está construido para manejar esas situaciones y tú las causas, ¿qué haces? esperas que pase?
¿Conoces esas partes de la documentación de C / C ++ que hablan sobre "comportamiento indefinido"? Eso es todo. Fianza y fianza difícil.

Actualizar para aclarar: La otra cara de esto es que terminas dándote cuenta de que solo debes usar Debug.Assert cosas internas para llamar a otras cosas internas. Si su código está expuesto a terceros (es decir, es una biblioteca o algo así), no hay límite para la entrada que puede esperar, y por lo tanto debe validar adecuadamente y lanzar excepciones o lo que sea, y también debe probar la unidad para eso