Pruebas unitarias de un archivo DLL de Visual C++ para aplicaciones de la Tienda Windows
En este tema se describe una manera de crear pruebas unitarias para un archivo DLL de C++ en aplicaciones de la Tienda Windows mediante Visual Studio Express 2012 para Windows 8 y el marco de pruebas unitarias de Microsoft para C++. El DLL RooterLib muestra las memorias imprecisas de teoría límite del cálculo mediante la implementación de una función que calcula una estimación de raíz cuadrada de un número determinado. El archivo DLL se puede incluir entonces en una aplicación de la Tienda Windows que muestra a un usuario las cosas divertidas que se pueden realizar con las matemáticas.
Nota
En los temas de esta sección se describe la funcionalidad de Visual Studio Express 2012 para Windows 8. Visual Studio Ultimate, VS Premium y VS Professional proporcionan características adicionales para pruebas unitarias.
-
En VS Ultimate, VS Premium, y VS Professional puedes usar cualquier marco de pruebas unitarias de código fuente abierto o de terceros que haya creado un adaptador complementario para el Explorador de pruebas de Microsoft. También puedes analizar y mostrar información de cobertura de código para las pruebas.
-
En VS Ultimate y VS Premium, puedes ejecutar tus pruebas después de cada compilación.
Para obtener más información, consulta Comprobar código utilizando pruebas unitarias en MSDN Library.
En este tema se muestra cómo se utilizan las pruebas unitarias como primer paso en el desarrollo. En este enfoque, primero tienes que escribir un método de prueba que compruebe un comportamiento concreto en el sistema que estés probando y, después, escribir el código que tenga que superar la prueba. Mediante la realización de cambios en el orden de los procedimientos siguientes, puedes invertir esta estrategia para escribir primero el código que deseas probar y escribe después las pruebas unitarias.
En este tema también se crea una solución única de Visual Studio y proyectos independientes para las pruebas unitarias y el DLL que desees probar. También puedes incluir las pruebas unitarias directamente en el proyecto DLL, o crear soluciones independientes para las pruebas unitarias y el .DLL. Consulta Pruebas unitarias de aplicaciones C++ existentes con el Explorador de pruebas para obtener sugerencias acerca de qué estructura se va a utilizar.
En este tema
Este tema le guía a través de las tareas siguientes:
Crear la solución y el proyecto de prueba unitaria
Comprobar que las pruebas se ejecutan en el Explorador de pruebas
Agrega el proyecto DLL a la solución.
Acoplar el proyecto de prueba al proyecto DLL
Aumentar de forma iterativa las pruebas y comprobar si se superan
Depurar una prueba que no se supera
Refactorizar el código sin cambiar pruebas
Crear la solución y el proyecto de prueba unitaria
En el menú Archivo, elige Nuevo y, a continuación, elige Nuevo proyecto.
En el cuadro de diálogo Nuevo proyecto, expande Instalado y, a continuación, expande Visual C++ y elige Tienda Windows. A continuación elige Biblioteca de pruebas unitarias (aplicaciones de la Tienda Windows) en la lista de plantillas de proyecto.
Asigne al proyecto el nombre RooterLibTests, especifica la ubicación, asigna a la solución el nombre RooterLib y asegúrate de que esté activada la opción Crear directorio para la solución.
En el nuevo proyecto, abre unittest1.cpp.
Ten en cuenta lo siguiente:
Cada prueba se define utilizando TEST_METHOD(YourTestName){...}.
No tienes que escribir una firma de función convencional. La firma se crea mediante la macro TEST_METHOD. La macro genera una función de instancia que devuelve void. También genera una función estática que devuelve información sobre el método de prueba. Esta información permite que el Explorador de pruebas busque el método.
Los métodos de prueba se agrupan en clases mediante TEST_CLASS(YourClassName){...}.
Cuando se ejecutan las pruebas, se crea una instancia de cada clase de prueba. Los métodos de prueba se invocan en un orden no especificado. Puedes definir métodos especiales que se invoquen antes y después de cada módulo, clase o método. Para obtener más información, consulta Usar Microsoft.VisualStudio.TestTools.CppUnitTestFramework en MSDN Library.
Comprobar que las pruebas se ejecutan en el Explorador de pruebas
Inserta el código de prueba:
TEST_METHOD(TestMethod1) { Assert::AreEqual(1,1); }
Observa que la clase Assert proporciona varios métodos estáticos que puedes utilizar para comprobar los resultados en los métodos de prueba.
En el menú Prueba, elige Ejecutar y, a continuación, elige Ejecutar todas.
El proyecto de prueba se compila y ejecuta. Aparece la ventana Explorador de pruebas y la prueba se muestra debajo de Pruebas superadas. El panel Resumen de la parte inferior de la ventana proporciona detalles adicionales sobre la prueba seleccionada.
Agrega el proyecto DLL a la solución.
En el Explorador de soluciones, elige el nombre de la solución. En el menú contextual, elige Agregar y, a continuación, haz clic en Agregar nuevo proyecto.
En el cuadro de diálogo Agregar nuevo proyecto, elige DLL (aplicaciones de la Tienda Windows).
Agrega el código siguiente al archivo RooterLib.h:
// The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the ROOTERLIB_EXPORTS // symbol defined on the command line. This symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // ROOTERLIB_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef ROOTERLIB_EXPORTS #define ROOTERLIB_API __declspec(dllexport) #else #define ROOTERLIB_API __declspec(dllimport) #endif //ROOTERLIB_EXPORTS class ROOTERLIB_API CRooterLib { public: CRooterLib(void); double SquareRoot(double v); };
Los comentarios explican el bloque ifdef no solo al desarrollador del DLL, sino a cualquier usuario que haga referencia al DLL en el proyecto. Puedes agregar el símbolo ROOTERLIB_EXPORTS a la línea de comandos mediante las propiedades de proyecto del DLL.
La clase CRooterLib declara un constructor y el método de perito de SqareRoot.
Agrega el símbolo ROOTERLIB_EXPORTS a la línea de comandos.
En el Explorador de soluciones, elige el proyecto RooterLib y, a continuación, elige Propiedades en el menú contextual.
En el cuadro de diálogo Página de propiedades de RooterLib, expande Propiedades de configuración, expande C++ y elige Preprocesador.
Elige <Editar...> en la lista Definiciones de preprocesador y, a continuación, agrega ROOTERLIB_EXPORTS en el cuadro de diálogo Definiciones de preprocesador.
Agrega las implementaciones mínimas de las funciones declaradas. Abre RooterLib.cpp y agrega el código siguiente:
// constructor CRooterLib::CRooterLib() { } // Find the square root of a number. double CRooterLib::SquareRoot(double v) { return 0.0; }
Acoplar el proyecto de prueba al proyecto DLL
Agrega RooterLib al proyecto RooterLibTests.
En el Explorador de soluciones, elige el proyecto RooterLibTests y, a continuación, elige Referencias... en el menú contextual.
En el cuadro de diálogo Propiedades del proyecto RooterLib, expande Propiedades comunes y elige Marco de trabajo y referencias.
Elige Agregar nueva referencia...
En el cuadro de diálogo Agregar referencia, expande Solución y, a continuación, elige Proyectos. A continuación, selecciona el elemento RouterLib.
Incluye el archivo de encabezado de RooterLib en unittest1.cpp.
Abre unittest1.cpp.
Agrega este código debajo de la línea #include "CppUnitTest.h":
#include "..\RooterLib\RooterLib.h"
Agrega una prueba que use la función importada. Agrega el código siguiente a unittest1.cpp:
TEST_METHOD(BasicTest) { CRooterLib rooter; Assert::AreEqual( // Expected value: 0.0, // Actual value: rooter.SquareRoot(0.0), // Tolerance: 0.01, // Message: L"Basic test failed", // Line number - used if there is no PDB file: LINE_INFO()); }
Compila la solución.
La nueva prueba aparece en el Explorador de pruebas en el nodo Pruebas no ejecutadas.
En el Explorador de pruebas, elige Ejecutar todas.
Has configurado los proyectos de prueba y de código, y comprobado que puedes ejecutar pruebas que ejecutan funciones en el proyecto de código. Ahora puedes empezar a escribir pruebas y código reales.
Aumentar de forma iterativa las pruebas y comprobar si se superan
Agrega una nuevo prueba:
TEST_METHOD(RangeTest) { CRooterLib rooter; for (double v = 1e-6; v < 1e6; v = v * 3.2) { double expected = v; double actual = rooter.SquareRoot(v*v); double tolerance = expected/1000; Assert::AreEqual(expected, actual, tolerance); } };
Sugerencia
Te recomendamos que no cambies las pruebas que se han superado. En su lugar, agrega una nueva prueba, actualiza el código para que la prueba se supere, agrega otra prueba, y así sucesivamente.
Cuando los usuarios cambien los requisitos, deshabilita las pruebas que no sean correctas. Escribe nuevas pruebas y haz que funcionen de una en una, de la misma manera incremental.
En el Explorador de pruebas, elige Ejecutar todas.
La prueba sufre un error.
Sugerencia
Comprueba que cada prueba sufre un error inmediatamente después de haberla escrito. Esto ayuda a evitar la fácil equivocación de escribir una prueba que nunca produce un error.
Mejora el código objeto de prueba para que la nueva prueba se supere. Agrega lo siguiente a RooterLib.cpp:
#include <math.h> ... // Find the square root of a number. double CRooterLib::SquareRoot(double v) { double result = v; double diff = v; while (diff > result/1000) { double oldResult = result; result = result - (result*result - v)/(2*result); diff = abs (oldResult - result); } return result; }
Compila la solución y, a continuación, en el Explorador de pruebas, elige Ejecutar todas.
Ambas pruebas se superan.
Sugerencia
Desarrolla el código agregando pruebas de una en una. Asegúrate de que se superen todas las pruebas después de cada iteración.
Depurar una prueba que no se supera
Agrega otra prueba a unittest1.cpp:
// Verify that negative inputs throw an exception. TEST_METHOD(NegativeRangeTest) { wchar_t message[200]; CRooterLib rooter; for (double v = -0.1; v > -3.0; v = v - 0.5) { try { // Should raise an exception: double result = rooter.SquareRoot(v); swprintf_s(message, L"No exception for input %g", v); Assert::Fail(message, LINE_INFO()); } catch (std::out_of_range ex) { continue; // Correct exception. } catch (...) { swprintf_s(message, L"Incorrect exception for %g", v); Assert::Fail(message, LINE_INFO()); } } };
En el Explorador de pruebas, elige Ejecutar todas.
La prueba sufre un error. Elige el nombre de la prueba en el Explorador de pruebas. Se resalta el error de aserción. El mensaje de error se puede ver en el panel de detalles del Explorador de pruebas.
Para ver por qué la prueba sufre un error, recorre paso a paso la función:
Establece un punto de interrupción al principio de la función SquareRoot.
En el menú contextual de la prueba no superada, elige Depurar pruebas seleccionadas.
Cuando la ejecución se detenga en el punto de interrupción, recorre paso a paso el código.
Agrega código a RooterLib.cpp para detectar la excepción:
#include <stdexcept> ... double CRooterLib::SquareRoot(double v) { //Validate the input parameter: if (v < 0.0) { throw std::out_of_range("Can't do square roots of negatives"); } ...
En el Explorador de pruebas, elige Ejecutar todas para probar el método corregido y asegúrate de que no se haya introducido una regresión.
Ahora todas las pruebas se superan.
Refactorizar el código sin cambiar pruebas
Simplifica el cálculo central en la función SquareRoot:
// old code //result = result - (result*result - v)/(2*result); // new code result = (result + v/result) / 2.0;
Elige Ejecutar todas para probar el método refactorizado y asegúrate de que no hayas introducido una regresión.
Sugerencia
Un conjunto estable de pruebas unitarias correctas proporciona la confianza de que no se han introducido errores al cambiar el código.
Mantén la refactorización independiente de otros cambios.