Pruebas unitarias sucintamente Pruebas unitarias avanzadas

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

En este artículo, vamos a comenzar a hablar sobre algunos de los temas avanzados que vienen con la prueba de unidad. Esto incluye cosas como la complejidad ciclométrica, métodos, campos y reflexión..

Complejidad ciclométrica

La complejidad ciclométrica es una medida del número de rutas independientes a través de su código. La prueba de cobertura de código está destinada a garantizar que sus pruebas ejecuten todas las rutas de código posibles. Las pruebas de cobertura de código están disponibles en las versiones Test, Premium y Ultimate de Visual Studio. La cobertura del código no es parte de NUnit. Una solución de terceros, NCover, también está disponible.

Para ilustrar este problema, considere este código, que analiza los parámetros de la línea de comandos:

clase estática pública CommandLineParser /// /// Devuelve una lista de opciones basadas en el análisis de fuerza bruta /// de las opciones de la línea de comando. La función devuelve las /// opciones especificadas y el parámetro de opción si es necesario. /// Diccionario público estático Parse (string cmdLine) Dictionary opciones = nuevo diccionario(); string [] items = cmdLine.Split ("); int n = 0; while (n < items.Length)  string option = items[n]; string param = String.Empty; if (option[0] != '-')  throw new ApplicationException("Expected an option.");  if (option == "-f")  // Has the parameter been supplied? if (items.Length <= n + 1)  throw new ApplicationException("Filename not specified.");  param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-')  throw new ApplicationException("Filename not specified.");  ++n; // Skip the filename option parameter.  options[option] = param; ++n;  return options;   

y un par de pruebas simples (tenga en cuenta que estas pruebas omiten las rutas de código que dan lugar a excepciones):

[TestFixture] clase pública CodeCoverageTests [Test] public void CommandParserTest () Dictionary options = CommandLineParser.Parse ("- a -b"); Assert.That (options.Count == 2, "Count se espera que sea 2"); Assert.That (options.ContainsKey ("- a"), "Opción esperada '-a'"); Assert.That (options.ContainsKey ("- b"), "Opción esperada '-b'");  [Prueba] public void FilenameParsingTest () Dictionary options = CommandLineParser.Parse ("- f foobar"); Assert.That (options.Count == 1, "Count se espera que sea 1"); Assert.That (options.ContainsKey ("- f"), "Opción esperada '-f'"); Assert.That (opciones ["- f"] == "foobar");  

Ahora veamos cómo podría ser una prueba de cobertura de código, primero escribiendo al asistente de cobertura de código de un hombre pobre:

Clase estática pública Cobertura Lista de puntos de cobertura estática pública obtener; set; public static void Reset () CoveragePoints = new List ();  [Condicional ("DEBUG")] Conjunto de anulación estático público (intveragePoint) CoveragePoints.Add (coverPoint);  

También necesitaremos un método de extensión simple; Verás por qué en un minuto:

clase estática pública ListExtensions public static bool HasOrderedItems (en esta lista itemList, int [] items) int n = 0; foreach (int i en itemList) if (i! = items [n]) return false;  ++ n;  devuelve true;  

Ahora podemos agregar puntos de referencia de cobertura en nuestro código, que se compilarán en la aplicación cuando se compile en modo DEBUG (las líneas rojas en negrita se agregaron):

clase estática pública CommandLineParser /// /// Devuelve una lista de opciones basadas en el análisis de fuerza bruta /// de las opciones de la línea de comando. La función devuelve las /// opciones especificadas y el parámetro de opción si es necesario. /// Diccionario público estático Parse (string cmdLine) Dictionary opciones = nuevo diccionario(); string [] items = cmdLine.Split ("); int n = 0; while (n < items.Length)  Coverage.Set(1); // WE ADD THIS COVERAGE SET-POINT string option = items[n]; string param = String.Empty; if (option[0] != '-')  throw new ApplicationException("Expected an option.");  if (option == "-f")  Coverage.Set(2); // WE ADD THIS COVERAGE SET-POINT // Has the parameter been supplied? if (items.Length <= n + 1)  throw new ApplicationException("Filename not specified.");  param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-')  throw new ApplicationException("Filename not specified.");  ++n; // Skip the filename option parameter.  options[option] = param; ++n;  return options;   

Y ahora podemos escribir el siguiente accesorio de prueba:

[TestFixture] public class CommandParserCoverageTests [SetUp] public void CoverageSetup () Coverage.Reset ();  [Test] public void CommandParserTest () Dictionary options = CommandLineParser.Parse ("- a -b"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 1));  [Prueba] public void FilenameParsingTest () Dictionary options = CommandLineParser.Parse ("- f foobar"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 2));  

Observe cómo ahora estamos ignorando los resultados reales, pero estamos asegurando que se ejecuten los bloques de código deseados..

Prueba de caja blanca: inspección de campos y métodos protegidos y privados

Podría decirse que una prueba de unidad solo debería ocuparse de los campos y métodos públicos. El argumento en contra de esto es que para probar la implementación completa, el acceso a campos protegidos o privados, para afirmar su estado y la capacidad para realizar pruebas unitarias de métodos protegidos o privados, se requiere. Teniendo en cuenta que probablemente no es deseable exponer la mayoría de los cálculos de bajo nivel, y esos son exactamente los métodos que uno quiere probar, lo más probable es que sea necesario probar al menos métodos protegidos o privados. Hay varias opciones disponibles.

Exponer métodos y campos en el modo de prueba

Este ejemplo ilustra el concepto:

clase pública DoesSomething #if TEST public #else private #endif void SomeComputation ()  

Si bien esto es factible, produce un código fuente feo y corre el grave riesgo de que alguien realmente llame al método con el símbolo TEST definido, solo para descubrir que su código se rompe en una compilación de producción donde el símbolo TEST no está definido..

Derivando una clase de prueba

Como alternativa, si los métodos están protegidos, considere derivar una clase de prueba:

clase pública DoesSomethingElse protected void SomeComputation ()  clase pública DoesSomethingElseTesting: DoesSomethingElse public void TestSomeComputation () base.SomeComputation ();  

Esto le permite crear una instancia de la clase de prueba derivada y acceder a un método protegido a través de un método expuesto públicamente en la subclase.

Reflexión

Por último, uno puede usar la reflexión para métodos privados o clases selladas. Lo siguiente ilustra un método privado y la ejecución de ese método a través de la reflexión en una prueba:

clase pública DoesSomething private void SomeComputation ()  [TestClass] clase pública DoesSomethingTest [TestMethod] public void SomeComputationTest () DoesSomething ds = new DoesSomething (); Escriba t = ds.GetType (); MethodInfo mi = t.GetMethod ("SomeComputation", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke (ds, null);