Pruebas unitarias en C++

Saludos, ha pasado un tiempo desde el último post en el que hemos tratado temas relacionados al lenguaje de programación C++, el día de hoy vamos a revisar como hacer pruebas unitarias en este poderoso lenguaje.

Las pruebas unitarias nos permiten comprobar el comportamiento de un pedazo de código, función o clase. Esto nos ayuda a ser menos propensos a futuros errores en nuestro código.

Hay muchos frameworks para el desarrollo de software, algunos nos ayudan a construir nuestro software de una manera mas organizada y rápida, mientras que otros nos ayudan a realizar las pruebas a nuestro código.

En C++ tenemos varios frameworks para realizar pruebas unitarias como:

  • Google Test
  • CppUnit
  • CppTest
  • Catch2

En esta página: Frameworks de Pruebas Unitarias para C++ podemos ver una lista de los frameworks existentes para realizar pruebas unitarias en C++, muchos de los cuales son Open Source.

Para nuestro ejemplo usaremos Catch2 puesto que es bastante sencillo de utilizar, dicho esto manos a la obra.

Puesto que esto no es un artículo de sobre TDD (Test Driven Development), vamos a escribir primero nuestra código y luego nuestra prueba.

Realizaremos un ejercicio simple que suma, resta, multiplica y divide dos números enteros y nos muestra el resultado. Lo llamaremos math-operations.cpp

/**
* Solo las funciones para poder probarlas después
**/

int sum(int firstNumber, int secondNumber) {
    return firstNumber + secondNumber;
}

int subtract(int firstNumber, int secondNumber) {
    return firstNumber - secondNumber;
}

int multiply(int firstNumber, int secondNumber) {
    return firstNumber * secondNumber;
}

int division(int firstNumber, int secondNumber) {
    return firstNumber / secondNumber;
}

A continuación escribiremos nuestro main.cpp

#include <iostream>
#include "math-operations.cpp" // Incluímos el archivo de las operaciones matemáticas

using namespace std;

int main(int argc, char *argv[]) {
    int firstNumber, secondNumber, result;

    firstNumber = 10;
    secondNumber = 5;

    result = sum(firstNumber, secondNumber);
    cout << "Result of sum is: " << result << endl;
    result = subtract(firstNumber, secondNumber);
    cout << "Result of subtract is: " << result << endl;
    result = multiply(firstNumber, secondNumber);
    cout << "Result of multiplication is: " << result << endl;
    result = division(firstNumber, secondNumber);
    cout << "Result of division is: " << result <<endl;

}

Al ejecutar nuestro código el resultado será el siguiente:

Result of sum is: 15                                                                                                                                                                              
Result of subtract is: 5                                                                                                                                                                          
Result of multiplication is: 50                                                                                                                                                                   
Result of division is: 2

Ahora vienen las pruebas, para ello vamos a utilizar Catch2 como lo mencioné anteriormente, este framework los podemos descargar del siguiente enlace https://github.com/catchorg/Catch2/archive/v2.9.1.tar.gz

Una vez descargado vamos a proceder con los siguientes pasos:

  • Dentro de nuestro proyecto creamos una carpeta llamada test
  • Vamos a donde descargamos el archivo Catch2
  • Extraemos el archivo
  • Dentro de la carpeta Catch2 debe haber la siguiente carpeta single_include
  • Dentro de single_include hay una carpeta llamada catch2 que tiene 4 archivos de cabecera
  • Copiamos toda la carpeta catch2 dentro de nuestra carpeta test
  • Creamos un archivo para las pruebas, en este caso math-operations-test.cpp

Dentro de math-operations-test.cpp vamos a escribir nuestras pruebas de la siguiente manera:

#define CATCH_CONFIG_MAIN // Esta linea pide a Catch2 proveer un 'main' para realizar la prueba
#include "catch2/catch.hpp" // Incluímos catch para realizar las pruebas
#include "../math-operations.cpp" // Incluímos el archivo que vamos a probar

/**
* Anatomía de las pruebas con Catch2
**/

// Lleva la palabra reservada TEST_CASE y adentro
// ponemos lo que creemos que debe hacer la prueba
TEST_CASE("Should Sum two numbers properly") {
    // Ponemos los valores necesarios para la prueba 
    // así como el resultado esperado
    int n1 = 3, n2 = 6, expectedResult = 9;
    // Nuestra prueba requiere que una condición se cumpla
    // en este caso debe cumplirse que nuestro codigo sume 3 + 6
    // y que el resultado esperado sea 9
    REQUIRE(sum(n1, n2) == expectedResult);
}

TEST_CASE("Should Subtract two numbers properly") {
    int n1 = 13, n2 = 2, expectedResult = 11;
    REQUIRE(subtract(n1, n2) == expectedResult);
}

TEST_CASE("Should Multiply two numbers properly") {
    int n1 = 5, n2 = 11, expectedResult = 55;
    REQUIRE(multiply(n1, n2) == expectedResult);
}

TEST_CASE("Should Divide two numbers properly") {
    int n1 = 49, n2 = 13, expectedResult = 3;
    REQUIRE(division(n1, n2) == expectedResult);
}

Compilamos la prueba con g++ -o math-operations-test math-operations-test.cpp de la misma manera que compilamos cualquier otro archivo C++, lo cual generará un archivo compilado con el nombre math-operations-test que al ejecutarlo nos mostrará el siguiente resultado:

$ ./math-operations-test
===============================================================================
All tests passed (4 assertions in 4 test cases)

Esto significa que nuestras pruebas fueron exitosas 🙂

Veamos que pasa si modificamos una prueba, por ejemplo la division

TEST_CASE("Should Divide two numbers properly") {
    int n1 = 49, n2 = 13, expectedResult = 4; // Cambiamos el resultado esperado
    REQUIRE(division(n1, n2) == expectedResult);
}

Compilamos y ejecutamos

$ ./math-operations-test
-------------------------------------------------------------------------------
math-operations-test is a Catch v2.9.1 host application.
Run with -? for options

-------------------------------------------------------------------------------
Should Divide two numbers properly
-------------------------------------------------------------------------------
math-operations-test.cpp:20
...............................................................................

math-operations-test.cpp:22: FAILED:
  REQUIRE( division(n1, n2) == expectedResult )
with expansion:
  3 == 4

===============================================================================
test cases: 4 | 3 passed | 1 failed
assertions: 4 | 3 passed | 1 failed

Ahora el resultado es un poquito diferente, hay un montón de cosas pero todas son útiles, vamos a entender que nos dice.

  • math-operations-test is a Catch v2.9.1 host application. nuestra prueba se esta ejecutando con Catch2
  • Should Divide two numbers properly El caso de prueba que está fallando
  • math-operations-test.cpp:20 En qué archivo y línea se encuentra dicho caso de prueba
  • El siguiente bloque de texto nos dice la línea específica y el código que esta fallando
  • También nos dice el error, en este caso el resultado es 3 pero el esperado es 4
  • Al final del todo nos muesta un bloque de texto con la siguiente información
    • Número de casos de prueba, cuantos pasaron y cuantos fallaron
    • Numero de aserciones (Afirmaciones), cuantas pasaron y cuantas fallaron

Eso ha sido todo por hoy, si les interesa como hacer pruebas mucho más avanzadas pueden consultar el tutorial que tienen ellos en su repositorio, el cual es muy completo https://github.com/catchorg/Catch2/blob/master/docs/tutorial.md/

El código de estos ejemplos lo pueden encontrar en https://github.com/nano-bytes/cpp-compilation/tree/master/unit-tests

Happy hacking!
Hasta otra!

You may also like...