Pruebas unitarias sucintamente ¿Cómo funcionan las pruebas unitarias?

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

Un motor de prueba de unidad en un lenguaje reflexivo (como cualquier lenguaje .NET) tiene tres partes:

  • Carga de ensamblajes que contienen las pruebas..
  • Usando la reflexión para encontrar los métodos de prueba..
  • Invocando los métodos y validando los resultados..

Este artículo proporciona ejemplos de código de cómo funciona esto, armando un motor de prueba de unidad simple. Si no está interesado en la investigación de motores de prueba unitaria bajo el capó, no dude en omitir este artículo..

El código aquí asume que estamos escribiendo un motor de prueba contra los atributos de prueba de unidad de Visual Studio definidos en el conjunto Microsoft.VisualStudio.QualityTools.UnitTestFramework. Otros motores de prueba de unidad pueden usar otros atributos para los mismos propósitos.

Cargar asambleas

Arquitectónicamente, las pruebas unitarias deben residir en un ensamblaje separado del código que se está probando, o como mínimo, solo deben incluirse en el ensamblaje si se compila en el modo "Depurar". La ventaja de colocar las pruebas unitarias en un ensamblaje separado es que también puede probar la versión de producción optimizada, sin depuración, del código.

Dicho esto, el primer paso es cargar el ensamblaje:

static bool LoadAssembly (string assemblyFilename, out Ensamblaje de ensamblaje, out problema de cadena) bool ok = true; problema = String.Empty; assy = nulo; intente assy = Assembly.LoadFile (assemblyFilename);  catch (Exception ex) número = "Error al cargar el ensamblaje:" + ex.Message; ok = falso;  devolver bien; 

Tenga en cuenta que los motores de prueba unitaria profesionales cargan los ensamblajes en un dominio de aplicación separado para que el ensamblaje se pueda descargar o volver a cargar sin reiniciar el motor de prueba de la unidad. Esto también permite que el ensamblaje de prueba de la unidad y los ensamblajes dependientes se vuelvan a compilar sin apagar primero el motor de prueba de la unidad.

Usando la reflexión para encontrar métodos de prueba de unidad

El siguiente paso es reflexionar sobre el ensamblaje para identificar las clases que se designan como un "accesorio de prueba", y dentro de esas clases, para identificar los métodos de prueba. Un conjunto básico de cuatro métodos admite los requisitos mínimos del motor de prueba unitaria, el descubrimiento de dispositivos de prueba, métodos de prueba y atributos de manejo de excepciones:

///  /// Devuelve una lista de clases en el ensamblaje proporcionado que tienen un atributo "TestClass". ///  IEnumerable estático GetTestFixtures (Ensamblaje de ensamblaje) return assy.GetTypes (). Where (t => t.GetCustomAttributes (typeof (TestClassAttribute), false) .Length == 1);  ///  /// Devuelve una lista de métodos en el dispositivo de prueba que están decorados con el atributo "TestMethod". ///  IEnumerable estático GetTestMethods (Type testFixture) return testFixture.GetMethods (). Where (m => m.GetCustomAttributes (typeof (TestMethodAttribute), false) .Length == 1);  ///  /// Devuelve una lista de atributos específicos que pueden estar decorando el método. ///  IEnumerable estático GetMethodAttributes(Método MethodInfo) return method.GetCustomAttributes (typeof (AttrType), false) .Cast();  ///  /// Devuelve verdadero si el método está decorado con un atributo "ExpectedException", mientras que el tipo de excepción es la excepción esperada. ///  estática bool IsExpectedException (método MethodInfo, Exception expectedException) Escriba expectedExceptionType = expectedException.GetType (); devuelve GetMethodAttributes(método). Where (attr => attr.ExceptionType == expectedExceptionType) .Count ()! = 0; 

Métodos de invocación

Una vez que se ha compilado esta información, el motor invoca los métodos de prueba dentro de un bloque try-catch (no queremos que el motor de prueba de la unidad se bloquee):

static void RunTests (Escriba testFixture, Action resultado) IEnumerable testMethods = GetTestMethods (testFixture); if (testMethods.Count () == 0) // No haga nada si no hay métodos de prueba. regreso;  object inst = Activator.CreateInstance (testFixture); foreach (MethodInfo mi en testMethods) bool pass = false; try // Los métodos de prueba no tienen parámetros. mi.Invoke (inst, null); pass = true;  catch (Exception ex) pass = IsExpectedException (mi, ex.InnerException);  finalmente resultado (testFixture.Name + "." + mi.Name + ":" + (pass? "Pass": "Fail")); 

Finalmente, podemos poner este código en una aplicación de consola simple que toma el ensamblaje de prueba de unidad como resultado de parámetros en un motor utilizable pero simple:

utilizando el sistema; utilizando System.Collections.Generic; utilizando System.IO; utilizando System.Linq; utilizando System.Reflection; utilizando System.Text; utilizando Microsoft.VisualStudio.TestTools.UnitTesting; espacio de nombres SimpleUnitTestEngine class Program static void Main (string [] args) string issue; if (! VerifyArgs (args, out issue)) Console.WriteLine (issue); regreso;  Asamblea de la Asamblea; if (! LoadAssembly (args [0], out assy, ​​out issue)) Console.WriteLine (issue); regreso;  IEnumerable testFixtures = GetTestFixtures (assy); foreach (Escriba testFixture en testFixtures) RunTests (testFixture, t => Console.WriteLine (t));  static bool VerifyArgs (string [] args, out string string) bool ok = true; problema = String.Empty; if (args.Length! = 1) número = "Uso: SimpleUnitTestEngine "; ok = false; else string assemblyFilename = args [0]; if (! File.Exists (assemblyFilename)) número =" El nombre de archivo '"+ args [0] +"' no existe. "; ok = falso; devolver ok; ... el resto del código ... 

El resultado de ejecutar este sencillo motor de prueba se muestra en una ventana de consola, por ejemplo:

Nuestros resultados de la consola del motor de prueba simple