Pruebas unitarias sucintamente Visual Studio

Este es un extracto del eBook de Unit Testing Succinctly, por Marc Clifton, amablemente proporcionado por Syncfusion.

Una prueba de unidad se compone de dos cosas:

  • Una clase que representa el accesorio de prueba.
  • Métodos en la clase que representan las pruebas unitarias..

Visual Studio creará automáticamente un código auxiliar para un proyecto de prueba, que es donde comenzaremos.

Creando un Proyecto de Prueba Unitaria en Visual Studio

Las pruebas unitarias normalmente se colocan en un proyecto separado (que resulta en un ensamblaje distinto) del código de su aplicación. En Visual Studio 2008 o 2012, puede crear un proyecto de prueba de unidad haciendo clic con el botón derecho en la solución y seleccionando Añadir seguido por Nuevo proyecto desde el menú emergente:

Añadiendo un nuevo proyecto

En el cuadro de diálogo que aparece, seleccione un Prueba proyecto:

Nuevo Proyecto de Prueba VS2008 Nuevo proyecto de prueba VS2012

Visual Studio 2008 creará un archivo de código auxiliar, "UnitTest1.cs" (si seleccionó el lenguaje C #), con una variedad de comentarios útiles en el código auxiliar. Visual Studio 2012 crea un stub mucho terser:

utilizando el sistema; utilizando Microsoft.VisualStudio.TestTools.UnitTesting; espacio de nombres VS2012UnitTestProject1 [TestClass] clase pública UnitTest1 [TestMethod] void público TestMethod1 ()  

Para usuarios de Visual Studio 2008

Visual Studio 2008 también creará una clase TestContext. Esto ya no existe en VS2012 y lo ignoraremos. En su lugar, utilizaremos el código auxiliar anterior de VS2012..

Además, elimine el archivo "ManualTest1.mht", de lo contrario se le pedirá que seleccione los resultados de la prueba e ingrese las notas de la prueba manualmente..

Accesorios de prueba

Observe que la clase está decorada con el atributo TestClass. Esto define el accesorio de prueba, una colección de métodos de prueba..

Métodos de prueba

Observe que el método está decorado con el atributo TestMethod. Esto define un método, que el dispositivo de prueba ejecutará.


La clase de afirmación

La clase assert define los siguientes métodos estáticos que se pueden usar para verificar el cálculo de un método:

  • AreEqual / AreNotEqual
  • AreSame / AreNotSame
  • IsTrue / IsFalse
  • IsNull / IsNotNull
  • IsInstanceOfType / IsNotInstanceOfType

Muchas de estas afirmaciones están sobrecargadas y se recomienda que revise toda la documentación que proporciona Microsoft..

Fundamentos de hacer una afirmación

Tenga en cuenta que los siguientes ejemplos utilizan VS2008.

Las afirmaciones son la columna vertebral de cada prueba. Hay una variedad de afirmaciones que se pueden hacer con respecto a los resultados de una prueba. Para empezar, escribiremos una afirmación simple que dice "uno es igual a uno", en otras palabras, un tópico:

[TestClass] public class UnitTest1 [TestMethod] public void TestMethod1 () Assert.IsTrue (1 == 1);  

Ejecute la prueba, que debería resultar en "Aprobado":

Aserción simple

AreEqual / AreNotEqual

Los métodos AreEqual y AreNotEqual comparan:

  • objetos
  • dobles
  • individual
  • instrumentos de cuerda
  • datos escritos

Toman la forma de comparar el valor esperado (el primer parámetro) con el valor real (el segundo parámetro). Con respecto a los valores simples y dobles, se puede especificar "dentro de una cierta precisión". Por último, todas las sobrecargas tienen la opción de mostrar un mensaje (opcionalmente formateado) si la afirmación falla.

Con respecto a la igualdad de objetos, este método compara si las instancias son idénticas:

clase pública AnObject  [TestMethod] public void ObjectEqualityTest () AnObject object1 = new AnObject (); AnObject object2 = new AnObject (); Assert.AreNotEqual (object1, object2);  

La prueba anterior pasa, ya que object1 y object2 no son iguales. Sin embargo, si la clase anula el método Equals, entonces la igualdad se basa en la comparación realizada por el método Equals implementado en la clase. Por ejemplo:

clase pública AnObject public int SomeValue get; conjunto;  Public Override bool Equals (objeto obj) return SomeValue == ((AnObject) obj) .SomeValue;  [TestMethod] public void ObjectEqualityTest () AnObject object1 = new AnObject () SomeValue = 1; AnObject object2 = new AnObject () SomeValue = 1; Assert.AreEqual (object1, object2);  

AreSame / AreNotSame

Estos dos métodos verifican que la instancias Son los mismos (o no). Por ejemplo:

[TestMethod] public void SamenessTest () AnObject object1 = new AnObject () SomeValue = 1; AnObject object2 = new AnObject () SomeValue = 1; Assert.AreNotSame (object1, object2);  

Aunque la clase AnObject anula el operador de Iguales, la prueba anterior pasa porque la instancias De los dos objetos no son lo mismo..

IsTrue / IsFalse

Estos dos métodos le permiten probar la verdad de una comparación de valores. Desde una perspectiva de legibilidad, los métodos IsTrue e IsFalse se utilizan normalmente para valor comparaciones, mientras que AreEqual y AreSame se utilizan normalmente para comparar instancias (objetos).

Por ejemplo:

[TestMethod] public void IsTrueTest () AnObject object1 = new AnObject () SomeValue = 1; Assert.IsTrue (object1.SomeValue == 1);  

Esto verifica el valor de una propiedad..

IsNull / IsNotNull

Estas dos pruebas verifican si un objeto es nulo o no:

[TestMethod] public void IsNullTest () AnObject object1 = null; Assert.IsNull (object1);  

IsInstanceOfType / IsNotInstanceOfType

Estos dos métodos verifican que un objeto es una instancia de un tipo específico (o no). Por ejemplo:

clase pública AnotherObject  [TestMethod] public void TypeOfTest () AnObject object1 = new AnObject (); Assert.IsNotInstanceOfType (object1, typeof (AnotherObject));  

Poco concluyente

El método Assert.Inconclusive se puede usar para especificar que la prueba o la funcionalidad detrás de la prueba aún no se ha implementado y, por lo tanto, la prueba no es concluyente..

Qué sucede cuando falla una afirmación?

Con respecto a las pruebas de unidad de Visual Studio, cuando falla una aserción, el método Assert lanza una AssertFailedException. Esta excepción nunca debe ser manejada por su código de prueba.


Otras clases de aserción

Hay otras dos clases de aserción:

  • CollectionAssert
  • StringAssert

Como sus nombres implican, estas afirmaciones operan en colecciones y cadenas, respectivamente.

Reclamaciones de colección

Estos métodos se implementan en la clase Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert. Tenga en cuenta que el parámetro de colección en estos métodos espera que la colección implemente ICollection (contraste esto con NUnit, que espera IEnumerable).

AllItemsAreInstanceOfType

Esta afirmación verifica que los objetos en una colección son del mismo tipo, lo que incluye los tipos derivados del tipo esperado, como se ilustra aquí:

public class A  public class B: A  [TestClass] public class CollectionTests [TestMethod] public void InstancesOfTypeTest () Puntos de lista = new List () new Point (1, 2), new Point (3, 4 ); Lista items = new List() nuevo B (), nuevo A (); CollectionAssert.AllItemsAreInstancesOfType (points, typeof (Point)); CollectionAssert.AllItemsAreInstancesOfType (items, typeof (A));   

AllItemsAreNotNull

Esta afirmación verifica que los objetos en la colección no son nulos.

AllItemsAreUnique

Esta prueba asegura que los objetos en una colección son únicos. Si comparamos estructuras:

[TestMethod] public void AreUniqueTest () List points = new List () new Point (1, 2), new Point (1, 2); CollectionAssert.AllItemsAreUnique (puntos);  

Las estructuras se comparan por valor, no por instancia: la prueba anterior falla. Sin embargo, incluso si la clase anula el método Equals:

clase pública AnObject public int SomeValue get; conjunto;  Public Override bool Equals (objeto obj) return SomeValue == ((AnObject) obj) .SomeValue;  

esta prueba pasa:

[TestMethod] public void AreUniqueObjectsTest () List items = new List() new AnObject () SomeValue = 1, new AnObject () SomeValue = 1; CollectionAssert.AllItemsAreUnique (items);   

AreEqual / AreNotEqual

Estas pruebas afirman que dos colecciones son iguales. Los métodos incluyen sobrecargas que le permiten proporcionar un método de comparación. Si un objeto anula el método Igual, ese método se utilizará para determinar la igualdad. Por ejemplo:

[TestMethod] public void AreEqualTest () List itemList1 = nueva lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; Lista itemList2 = nueva lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; CollectionAssert.AreEqual (itemList1, itemList2);   

Estas dos colecciones son iguales porque la clase AnObject anula el método Equals (ver ejemplo anterior).

Tenga en cuenta que para pasar la aserción, las listas deben ser de la misma longitud y no se consideran iguales si las listas son idénticas, excepto en un orden diferente. Compare esto con la afirmación AreEquivalent descrita a continuación.

AreEquivalent / AreNotEquivalent

Esta afirmación compara dos listas y considera que las listas de elementos iguales son equivalentes, independientemente del orden. Desafortunadamente, parece que hay un error en la implementación, ya que esta prueba falla:

[TestMethod] public void AreEqualTest () List itemList1 = nueva lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; Lista itemList2 = nueva lista() new AnObject () SomeValue = 2, new AnObject () SomeValue = 1; CollectionAssert.AreEquivalent (itemList1, itemList2);     Error de Visual Studio AreEquivalent  

con el mensaje de error:

CollectionAssert.AreEquivalent ha fallado. La colección esperada contiene 1 ocurrencia (s) de. La colección real contiene 0 ocurrencia (s). 

Considerando que la implementación de NUnit de esta afirmación pasa:

Las obras son equivalentes de NUnit correctamente

Contiene / DoesNotContain

Esta afirmación verifica que un objeto está contenido en una colección:

[TestMethod] public void ContainsTest () List itemList = nueva lista() new AnObject () SomeValue = 1, new AnObject () SomeValue = 2; CollectionAssert.Contains (itemList, new AnObject () SomeValue = 1);   

utilizando el método Equals (si está anulado) para realizar la prueba de igualdad.

IsSubsetOf / IsNotSubsetOf

Esta afirmación verifica que el primer parámetro (el subconjunto) está contenido en la colección del segundo parámetro (el superconjunto).

[TestMethod] public void SubsetTest () string subconjunto = "abc"; string superset = "1d2c3b4a"; CollectionAssert.IsSubsetOf (subset.ToCharArray (), superset.ToCharArray ());  

Tenga en cuenta que la prueba de subconjunto no prueba el orden o la secuencia, simplemente comprueba si los elementos de la lista de subconjuntos están contenidos en el superconjunto.

Aserciones de cadena

Estos métodos se implementan en la clase Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert:

  • Contiene
  • Partidos / DoesNotMatch
  • Comienza con / termina con

Estos se discuten a continuación.

Contiene

El método Contains afirma que el subconjunto (tenga en cuenta que este es el segundo parámetro) está contenido en la cadena (el primer parámetro). Por ejemplo, esta prueba pasa:

[TestClass] public class StringTests [TestMethod] public void ContainsTest () string subconjunto = "abc"; string superset = "123abc456"; StringAssert.Contains (superconjunto, subconjunto);  

Partidos / DoesNotMatch

Este método afirma que la cadena (el primer parámetro) coincide con el patrón de expresiones regulares proporcionado en el segundo parámetro.

Comienza con / termina con

Este método afirma que la cadena (el primer parámetro) comienza con o termina con otra cadena (el segundo parámetro).


Excepciones

Las excepciones se pueden probar sin escribir bloques try-catch alrededor del método de prueba. Por ejemplo, mientras puedas escribir esto:

[TestMethod] public void CatchingExceptionsTest () try Divide (5, 0);  catch (ArgumentOutOfRangeException) // Aceptar silenciosamente la excepción como válida.  

Es mucho más legible usar el atributo ExpectedException en el método de prueba:

[TestMethod] [ExpectedException (typeof (ArgumentOutOfRangeException))] public void BadParameterTest () Divide (5, 0);  

Otros atributos útiles

Hay algunos atributos adicionales que son útiles para ejecutar un conjunto de pruebas, así como pruebas individuales que mejoran la reutilización y legibilidad de la base de código de prueba de la unidad..

Configuración / Desmontaje

El motor de prueba de unidades de Visual Studio proporciona cuatro atributos de método adicionales:

  • ClassInitialize
  • ClassCleanup
  • TestInitialize
  • Prueba de limpieza

Estos atributos preceden y siguen la ejecución de todas las pruebas dentro del dispositivo (clase), así como antes y después de cada prueba en el dispositivo..

Tenga en cuenta que los métodos decorados con este atributo deben ser estáticos..

ClassInitialize

Si un método está decorado con este atributo, el código en el método se ejecuta antes de ejecutar todas las pruebas en el dispositivo. Tenga en cuenta que este método requiere un parámetro TestContext.

Este método es útil para asignar recursos o crear instancias en las que se basan todas las pruebas en el dispositivo. Una consideración importante con los recursos y objetos creados durante la inicialización del dispositivo es que estos recursos y objetos deben considerarse de solo lectura. No es recomendable que las pruebas cambien el estado de los recursos y objetos de los que dependen otras pruebas. Esto incluye las conexiones a servicios como la base de datos y los servicios web cuya conexión podría ponerse en un estado no válido como resultado de un error en una prueba, invalidando así todas las demás pruebas. Además, el orden en que se ejecutan las pruebas no está garantizado. Alterar el estado de un recurso y objeto creado en la inicialización del dispositivo puede tener efectos secundarios, según el orden en que se ejecuten las pruebas..

ClassCleanup

Un método decorado con este atributo es responsable de desasignar recursos, cerrar conexiones, etc., que se crearon durante la inicialización de la clase. Este método siempre se ejecutará después de ejecutar las pruebas dentro del dispositivo, independientemente del éxito o el fracaso de las mismas pruebas..

TestInitialize

Similar al atributo ClassInitialize, se ejecutará un método decorado con este atributo para cada prueba antes de ejecutar la prueba. Uno de los propósitos de este atributo es asegurar que los recursos u objetos asignados por el código ClassInitialize se inicialicen a un estado conocido antes de ejecutar cada prueba.

Prueba de limpieza

Como complemento del atributo TestInitialize, los métodos decorados con TestCleanup se ejecutarán al finalizar de cada prueba.

Configuración y flujo de desmontaje

El siguiente código muestra el flujo de la instalación y la configuración de la prueba y el desmontaje en relación con las pruebas reales:

[TestClass] public class SetupTeardownFlow [ClassInitialize] SetupFixture public static void (contexto de TestContext) Debug.WriteLine ("Fixture Setup.");  [ClassCleanup] public static void TeardownFixture () Debug.WriteLine ("Fixture Teardown.");  [TestInitialize] public void SetupTest () Debug.WriteLine ("Test Setup.");  [TestCleanup] public void TeardownTest () Debug.WriteLine ("Test Teardown.");  [TestMethod] public void TestA () Debug.WriteLine ("Test A.");  [TestMethod] public void TestB () Debug.WriteLine ("Test B.");  

La ejecución de este accesorio da como resultado el siguiente seguimiento de salida de depuración:

Configuración del accesorio. Configuración de prueba. Prueba A. Prueba de desmontaje. Configuración de prueba. Prueba B. Prueba de desmontaje. Desmontaje del accesorio. 

Como se ilustra en el ejemplo anterior, el dispositivo se inicializa; luego, para cada prueba, se ejecuta la configuración de la prueba y el código de desmontaje, seguido por el desmontaje del dispositivo al final.


Atributos menos utilizados

La siguiente sección describe los atributos menos utilizados.

AssemblyInitialize / AssemblyCleanup

Los métodos decorados con este atributo deben ser estáticos y se ejecutan cuando se carga el ensamblaje. Esto plantea la pregunta: ¿qué ocurre si el ensamblaje tiene más de un dispositivo de prueba??

[TestClass] public class Fixture1 [AssemblyInitialize] public static void AssemblyInit (TestContext context) // ... some operation [TestClass] public class Fixture2 [AssemblyInitialize] public static void AssemblyInit (TestContext context) // ... alguna operación  

Si lo intentas, el motor de prueba no ejecuta ninguna prueba de unidad, informando:

"UTA013: UnitTestExamplesVS2008.Fixture2: No se puede definir más de un método con el atributo AssemblyInitialize dentro de un ensamblaje".

Por lo tanto, solo puede existir un método AssemblyInitialize y un AssemblyCleanup para un ensamblaje, independientemente de la cantidad de dispositivos de prueba en ese ensamblaje. Por lo tanto, se recomienda que no se realicen pruebas reales en la clase que define estos métodos:

[TestClass] public class AssemblyFixture [AssemblyInitialize] public static void AssemblySetup (contexto de TestContext) Debug.WriteLine ("Assembly Initialize.");  [AssemblyCleanup] public static void AssemblyTeardown () Debug.WriteLine ("Assembly Cleanup.");  

resultando en la siguiente secuencia de ejecución:

Asamblea Inicializar. Configuración del accesorio. Configuración de prueba. Prueba A. Prueba de desmontaje. Configuración de prueba. Prueba B. Prueba de desmontaje. Desmontaje del accesorio. Limpieza de montaje. 

Tenga en cuenta el montaje adicional de inicialización y limpieza de llamadas.

Ignorar

Este método puede decorar métodos específicos o accesorios completos..

Ignorar un método de prueba

Si este atributo decora un método de prueba:

[TestMethod, Ignore] public void TestA () Debug.WriteLine ("Test A.");  

la prueba no se ejecutará. Desafortunadamente, el panel de resultados de prueba de Visual Studio no indica que hay pruebas actualmente ignoradas:

No se muestran las pruebas ignoradas

Compare esto con NUnit, que muestra claramente las pruebas ignoradas:

NUnit muestra pruebas ignoradas

La pantalla NUnit marca todo el árbol de prueba como "desconocido" cuando uno o más métodos de prueba están marcados como "Ignorar".

Ignorar un accesorio de prueba

Los métodos de un aparato completo se pueden ignorar usando el atributo Ignorar en el nivel de clase:

[TestClass, Ignore] public class SetupTeardownFlow ... etc ... 

Borrar el caché de prueba

Si agrega el atributo Ignorar a un método, puede observar que Visual Studio aún ejecuta la prueba. Es necesario borrar el caché de prueba para que Visual Studio recoja el cambio. Una forma de hacerlo es limpiar la solución y reconstruirla.

Propietario

Utilizado para fines de informe, este atributo describe a la persona responsable del método de prueba de unidad..

DeploymentItem

Si las pruebas unitarias se están ejecutando en una carpeta de implementación separada, este atributo se puede usar para especificar los archivos que requiere una clase de prueba o un método de prueba para ejecutarse. Puede especificar archivos o carpetas para copiar a la carpeta de implementación y, opcionalmente, especificar la ruta de destino en relación con la carpeta de implementación.

Descripción

Utilizado para informes, este atributo proporciona una descripción del método de prueba. Curiosamente, este atributo está disponible solo en los métodos de prueba y no está disponible en las clases de prueba.

HostType

Para los métodos de prueba, este atributo se usa para especificar el host en el que se ejecutará la prueba de unidad.

Prioridad

Este atributo no lo utiliza el motor de prueba, pero se podría usar, mediante reflexión, con su propio código de prueba. La utilidad de este atributo es cuestionable..

Elemento de trabajo

Si está utilizando Team Foundation Server (TFS), puede usar este atributo en un método de prueba para especificar el ID de elemento de trabajo asignado por TFS a la prueba de unidad específica.

CssIteration / CssProjectStructure

Estos dos atributos se utilizan en relación con TeamBuild y TestManagementService y le permiten especificar una iteración de proyecto a la que corresponde el método de prueba.


Pruebas parametrizadas con el atributo DataSource

El motor de prueba de unidades de Microsoft admite CSV, XML o fuentes de datos de base de datos para pruebas parametrizadas. Esto no es exactamente una prueba parametrizada verdadera (vea cómo NUnit implementa la prueba parametrizada) porque los parámetros no se pasan al método de prueba unitaria, sino que deben extraerse de la fuente de datos y pasarse al método bajo prueba. Sin embargo, la capacidad de cargar datos de prueba en un DataTable desde una variedad de fuentes es útil para impulsar la automatización de pruebas.

Fuente de datos CSV

Se puede usar un archivo de texto de valores separados por comas para un origen de datos:

Numerador, Denominador, Resultado esperado 10, 5, 2 20,5, 4 33, 3, 11 

y utilizado en un método de prueba:

[TestClass] clase pública DataSourceExamples public TestContext TestContext get; conjunto;  [TestMethod] [DataSource ("Microsoft.VisualStudio.TestTools.DataSource.CSV", "C: \\ temp \\ csvData.txt", "csvData # txt", DataAccessMethod.Sequential)] public void CsvDataSourceTest) n = Convert.ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Denominator"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r);  

Da como resultado el siguiente resultado:

n = 10, d = 5, r = 2 n = 20, d = 5, r = 4 n = 33, d = 3, r = 11 

Tenga en cuenta que la ventana de resultados de la prueba no muestra las ejecuciones del parámetro (contraste esto con NUnit):

Resultados de pruebas parametrizadas

Sin embargo, hay ventajas obvias de no mostrar cada combinación de prueba, especialmente para grandes conjuntos de datos.

Fuente de datos XML

Dado un archivo XML como:

    

Un ejemplo de uso de una fuente de datos XML para una prueba unitaria es:

[TestMethod] [DataSource ("Microsoft.VisualStudio.TestTools.DataSource.XML", "C: \\ temp \\ xmlData.xml", "Row", DataAccessMethod.Sequential)] public void XmlDataSourceTest () int n = Convert .ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Denominator"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r);  

Tenga en cuenta que, aparte de los parámetros del atributo de origen de datos, el código de prueba es el mismo.

Fuente de datos de la base de datos

Una tabla de base de datos también se puede utilizar como fuente de datos. Dada una tabla como:

Tabla de base de datos como fuente de datos

y datos:

Datos de prueba de la base de datos

Un ejemplo de método de prueba con estos datos se ve así:

[TestMethod] [DataSource ("System.Data.SqlClient", "Data Source = INTERACX-HP; Initial Catalog = UnitTesting; Integrated Security = True", "DivideTestData", DataAccessMethod.Sequential)] public void XmlDataSourceTest () int n = Convert.ToInt32 (TestContext.DataRow ["Numerator"]); int d = Convert.ToInt32 (TestContext.DataRow ["Denominator"]); int r = Convert.ToInt32 (TestContext.DataRow ["ExpectedResult"]); Debug.WriteLine ("n =" + n + ", d =" + d + ", r =" + r);  

De nuevo, observe que el código del método de prueba es el mismo; lo único que hemos hecho aquí es cambiar la definición de DataSource..

Atributo TestProperty

La documentación de MSDN para este atributo ilustra la declaración de un par nombre-valor de TestProperty y luego, utilizando la reflexión, adquiriendo el nombre y el valor. Esta parece ser una forma obtusa de crear pruebas parametrizadas.. 

Además, el código, descrito en el blog de Craig Andera, para usar el atributo TestProperty para parametrizar el proceso de inicialización de la prueba no afecta a la colección TestContext.Properties en Visual Studio 2008 o Visual Studio 2012.