Testing Unitario. Laboratorio de Testing y Aseguramiento de la Calidad del Software

45  Download (0)

Full text

(1)

Testing Unitario

Laboratorio de Testing y Aseguramiento de la

Calidad del Software

(2)

Introducción

Testing “ad hoc” | Automatización de testing |

Testing unitario | Unidad y Suite de test |

GoogleTest | Fixture e independencia |

Test parametrizados por datos

(3)

• Todo programador está habituado al testing.

• En muchos casos, la forma en la que hacemos testing

es “ad hoc”, es decir, no sistemática.

Ej: supongamos que queremos testear la siguiente función: #include <stdio.h>

int main(int argc, char **argv) { int a,b,res;

if (argc != 3)

printf("Uso: max <int> <int>\n\n"); else { a = atoi(argv[1]); b = atoi(argv[2]); if (a < b) res = b; else res = a;

printf("Resultado: %i\n\n", res); }

}

(4)

En este caso, podemos testear la aplicación directamente

desde la línea de comandos:

Testing “ad hoc” (cont.)

$ gcc max.c -o max $ $ ./max 2 3 Resultado: 3 $ ./max 123546 94746294 Resultado: 94746294 $ ./max -454 -3 Resultado: -3 $ ./max 24 6 29

Uso: max <int> <int> $

(5)

Ej: En caso que lo que tengamos no sea una aplicación, sino una funcionalidad interna:

int max(int x, int y) { if (x < y)

return y; else

return x; }

el testing “ad hoc” se vuelve más difícil: tenemos que programar un arnés para la rutina (i.e., un “main” que la invoque).

(6)

• Es simple para testear aplicaciones,

• pero no almacena los tests para pruebas futuras.

• Requiere la construcción manual de “arneses” para la prueba de funcionalidades “internas” (no directamente invocables desde la interfaz de la aplicación).

• Requiere la inspección humana de la salida de los tests: • se decide manualmente si el test pasó o no.

(7)

Automatización de testing

Cuando el testing se vuelve

complejo y caro

como el

testing es “opcional”

, hay una

tendencia a

abandonarlo;

o se lo intenta simplificar a través de su

automatización

.

El costo

de realizar y mantener un testing automatizado

debe ser menor

que el costo ahorrado por la

(8)

Economía del Testing

Esfuerzo en desarrollo Esfuerzo en testing Esfuerzo inicial Incremento

del esfuerzo Esfuerzo

reducido

Esfuerzo ahorrado

(9)

Economía del Testing

Esfuerzo en desarrollo Esfuerzo en testing Esfuerzo inicial Incremento

del esfuerzo Esfuerzo

creciente

Esfuerzo ahorrado

(10)

Automatización de testing: objetivos

Aumentar la calidad:

utilizando tests como especificaciones;

previniendo la reintroducción de defectos (en lugar de repararlos);

localizando los defectos (ubicación y causa).

Comprender el SUT:

utilizando los tests como documentación.

Reducir (y no introducir) riesgos:

utilizando los tests como “red de seguridad”;

(11)

Automatización de testing: objetivos

Ser sencillos de ejecutar:

completamente automáticos. self-checking.

repetibles.

Ser sencillos de escribir y mantener:

simplicidad. expresividad.

separación de incumbencias.

Requerir mantenimiento mínimo:

(12)

Automatización de testing: problemas

Muchas herramientas ofrecen

automatizar

test que

interactuan con el SUT a través de su

interfaz

de

usuario

(metáfora

record

and

playback

)

Estos test acarrean los siguientes

problemas

Sensibilidad al

comportamiento

Sensibilidad a la

interfaz

Sensibilidad a los

datos

(13)

Pruebas unitarias

Testing unitario | Unidad y Suite de test |

GoogleTest | Fixture e independecia |

(14)

Testing unitario

• Es una metodología para testear unidades individuales de código (SUT: Software under test), preferentemente de

forma aislada de sus dependencias (DOC: Depend-on component).

Las unidades pueden ser de

distinta granularidad

:

porción de código, método, clase, librería, etc.

Normalmente

las pruebas son creadas por los mismos

programadores

durante el proceso de desarrollo.

Normalmente se utiliza uno o mas

frameworks

para su

(15)

• Normalmente las unidades son las partes mas pequeñas

de un sistema: funciones o procedimientos, clases, etc. • pero la granularidad es variable.

Testing unitario (cont.)

alp aplicación ejercita Test unitario 1 Test unitario 2 Test unitario 3 Test unitario 4 Test unitario 5 componente 2 unidad 2 unidad 1 ejercita ejercita ejercita ejercita usa usa

(16)

Una librería (framework) de apoyo al testing unitario ayuda a sistematizar y automatizar parte de las tareas manuales

asociadas al testing:

Define la estructura básica de un test:

• Inicialización | Ejercitación | Verificación | Demolición • Permite almacenar los tests como “scripts”.

• Permite definir la salida esperada como parte del script.

Testing unitario: sistematización y

(17)

Una librería (framework) de apoyo al testing unitario ayuda a sistematizar y automatizar parte de las tareas manuales

asociadas al testing:

Permite organizar tests en suites:

• Algunas librerías encuentran todos los test definidos y

construyen la suite automáticamente.

• Otras requieren una construcción explícita.

• Las suites suelen organizarse jerárquicamente.

Testing unitario: sistematización y

automatización

(18)

Una librería (framework) de apoyo al testing unitario ayuda a sistematizar y automatizar parte de las tareas manuales

asociadas al testing:

Ofrece entornos para la ejecución de tests y suites:

El test runner permite ejecutar automaticamente • todos los test o subconjunto de ellos,

• de manera independiente.

El test runner permite repetir un test con diferentes datos de entrada.

Testing unitario: sistematización y

automatización

(19)

Una librería (framework) de apoyo al testing unitario ayuda a sistematizar y automatizar parte de las tareas manuales

asociadas al testing:

Reporta información detallada sobre las pruebas:

El test runner genera un reporte estadístico de la ejecución de las suites,

• El resultado puede incluir datos de benchmarking y

cobertura.

Testing unitario: sistematización y

automatización

(20)

• Google Test Framework es una librería de apoyo al

testing unitario para C/C++ que incluye: • descubrimiento automático de tests,

• amplio conjunto de aserciones (y aserciones definidas por el usuario),

• test negativos,

• fallas fatales y no fatales, • test paramétricos, etc.

(21)

#include <limits.h> #include "staticRoutines.h" #include "gtest/gtest.h" TEST(MaxTest, Positive) { int a = 19; int b = 4;

int res = max(a,b);

EXPECT_TRUE(res == a); }

act: se ejecuta el programa con los datos construidos.

assert: se evalúa si los resultados obtenidos se corresponden con lo esperado.

Estructura de un test

arrange: se preparan los datos para alimentar al programa.

(22)

int main(int argc, char **argv) {

::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();

}

se inicializa el framework

se ejecutan todos los test

Ejecución de tests

• Google Test necesita un runner explícito,

• que detecta automáticamente todos los test, y

(23)

Demo

(24)

• ASSERT_TRUE(<expr>): verifica que <expr> evalúe a true, parando la ejecución en caso de falla.

• ASSERT_FALSE(<expr>): verifica que <expr> evalúe a false, parando la ejecución en caso de falla.

• EXPECT_TRUE(<expr>): verifica que <expr> evalúe a true. • EXPECT_FALSE(<expr>): verifica que <expr> evalúe a false. • ASSERT_EQ(<expected>, <actual>): verifica que <actual>

y <expected> evalúen al mismo valor.

• Sobre punteros verifica que sean la misma dirección y no que tengan el mismo valor!

NE, LT, LE, GT, GE, para verificar desigualdad, menor,

menor igual, mayor, y mayor igual respectivamente.

Algunas aserciones

(25)

• ASSERT_STREQ(<expected>, <actual>): verifica que los strings <actual> y <expected> tengan el mismo contenido.

STRNE: verifica que tengan diferente contenido.

• ASSERT_STRCASEEQ(<expected>, <actual>): verifica que los strings <actual> y <expected> tengan el mismo contenido, ignorando mayúsculas/minúsculas.

STRCASENE: verifica que tengan diferente contenido

ignorando mayúsculas/minúsculas.

ASSERT_PREDn(<pred>, <val1>,..., <valn>): verifica que la función <pred> (de n parámetros) aplicada a

<val1>,...,<valn> devuelva true.

(26)

• ASSERT_FLOAT_EQ(<expected>, <actual>): verifica los valores float <expected> y <actual> sean casi iguales.

DOUBLE: para doubles

• ASSERT_NEAR(<val1>, <val2>, <error>): verifica que la

diferencia entre <val1> y <val2> no supere el valor <error>. • ASSERT_THROW(<prog>, <exception>): verifica que el

programa <prog> lance la expeción <exception>.

• ASSERT_ANY_THROW(<prog>,): verifica que el programa

<prog> lance cualquier excepción.

• ASSERT_NO_THROW(<prog>,): verifica que el programa

<prog> no lance ninguna excepción.

También tienen sus versiones EXPECT

(27)

Test “negativos” en Google Test

• Los tests negativos son aquellos en los que probamos

que el SUT falla al ejecutar cierta porción de código bajo

ciertas entradas.

• Con Google Test, podemos capturar los tests negativos

con aserciones especiales:

TEST(DeathTest, SegmentationFault) {

  ASSERT_DEATH({ int *n = NULL; int x = *n;}, "Segmentation fault: .*"); }

TEST(DeathTest, NormalExit) {

  EXPECT_EXIT(NormalExit(), ::testing::ExitedWithCode(0), "Success"); }

TEST(DeathTest, SigKill) {

  EXPECT_EXIT(KillMyself(), ::testing::KilledBySignal(SIGKILL), "Sending ..."); }

(28)

Ejercicio

Utilizando la clase Point:

1.Implementá el método equals.

2.Construí una suite de test para la clase, incluyendo al

menos un test por método.

(29)

• Cuando el SUT está compuesto por varias clases, o

simplemente varios métodos de una clase, resulta claro que debemos organizar los tests.

• Los tests se organizan en test suites:

• una test suite es simplemente un conjunto de tests.

• Con Google Test, varios test lógicamente relacionados se puede agrupar en bajo un mismo nombre de Test Case.

• Una Suite (Fixture) es un conjunto de test bajo un mismo nombre de Test Case, definidos en una misma clase.

(30)

• En muchos casos, los tests de una suite comparten los datos que manipulan.

• En estos contextos, suele ser útil organizar los tests definiendo procesos comunes de inicialización (o

arrangement, o setUp) para todos los tests.

• Similarmente, se pueden definir procesos comunes de destrucción (o tearDown), que se ejecuten luego de

cada test.

(31)

• Consideremos un ejemplo de testing de una clase

Minefield, que representa el estado de un campo minado en el juego “Buscaminas”: class Mine { bool mined; bool marked; bool opened; public: Mine();

void setMined(bool isMined); bool isMined();

void setMarked(bool isMarked); bool isMarked();

void setOpened(bool isOpened); bool isOpened();

! };

• Para poder testear minedNeighbours, debemos crear el

minefield, y ubicar minas en lugares específicos.

Suites de test y datos compartidos:

un ejemplo

#include "Mine.h" class Minefield { Mine field[8][8]; ! public:

void open(int x, int y); void close(int x, int y); void mark(int x, int y); void unmark(int x, int y); void putMine(int x, int y); void removeMine(int x, int y);

int minedNeighbours(int x, int y);! };

(32)

• El proceso setUp en el siguiente ejemplo crea el escenario

adecuado para la ejecución de tests de minedNeighbours.

• Se ejecuta antes de cada test.

#include "Minefield.h" #include "gtest/gtest.h" class MinefieldTest : public ::testing::Test { ! protected: ! Minefield testingField; ! ! void SetUp() { ! ! testingField.putMine(0, 0); ! ! testingField.putMine(3, 4); ! ! testingField.putMine(4, 3); ! ! testingField.putMine(2, 2); ! ! testingField.putMine(0, 7); ! ! testingField.putMine(7, 7); ! ! testingField.putMine(5, 1); ! ! testingField.putMine(4, 7); ! } };

Suites de test y datos compartido:

un ejemplo

x x x

x

x x

x

x

TEST_F(MinefieldTest, NoOfMinedNeighbours01) {

int number = testingField.minedNeighbours(1, 0); ASSERT_EQ(1, number);

};

TEST_F(MinefieldTest, NoOfMinedNeighbours02) {

int number = testingField.minedNeighbours(7, 7); ASSERT_EQ(0, number);

(33)

• Es importante que no haya dependencias entre tests diferentes.

• No hay ninguna garantía sobre el orden en el que se ejecutan los tests

• GoogleTest construye un fixture fresco para cada test.

• Las rutinas SetUp y TearDown ayudan a setear el contexto

entre diferentes tests.

• El método SetUp se ejecuta antes de cada test de la suite.

• El método TearDown se ejecuta después de cada test de la suite.

Sobre setUp y tearDown

testingfield = new Minefield(); testingfield.SetUp();

NoOfMinedNeighbours01; testingfield.TearDown(); free(testingfield);

testingfield = new Minefield(); testingfield.SetUp();

NoOfMinedNeighbours02; testingfield.TearDown(); free(testingfield);

(34)

• GoogleTest asegura la independencia entre test (aunque se debe tener cuidado con datos persistentes).

Independencia entre Tests

class MineTest : public ::testing::Test { !

protected: Mine mine; };

TEST_F(MineTest, Marked) { mine.setMarked(true);

ASSERT_TRUE(mine.isMarked() && !mine.isOpened()); };

TEST_F(MineTest, Opened) { mine.setOpened(true);

ASSERT_TRUE(!mine.isMarked() && mine.isOpened()); };

class MineTest : public ::testing::Test { !

protected: Mine *mine; };

TEST_F(MineTest, Marked) { mine = new Mine();

mine->setMarked(true);

ASSERT_TRUE(mine->isMarked() && !mine->isOpened()); };

TEST_F(MineTest, Opened) { mine->setOpened(true);

ASSERT_TRUE(!mine->isMarked() && mine->isOpened()); };

class MineTest : public ::testing::Test { !

protected:

static void SetUpTestCase() { mine = new Mine();

}

static Mine* mine; };

Mine* MineTest::mine = NULL;

TEST_F(MineTest, Marked) { mine->setMarked(true);

ASSERT_TRUE(mine->isMarked() && !mine->isOpened()); };

TEST_F(MineTest, Opened) { mine->setOpened(true);

ASSERT_TRUE(!mine->isMarked() && mine->isOpened()); };

(35)

Demo

(36)

Ejercicio

Utilizando las clases Minefield y Minefield_Test:

1.Agregá a Minefield_Test más casos de prueba para

testear todos los métodos de Minefield.

2.Implementá el método minedNeighbours en la clase

(37)

• Ya hemos visto varios casos en los cuales varios tests

poseen exactamente la misma estructura, pero difieren en los datos que se utilizan para realizar el arrange del test.

• Para estos casos, Google Test ofrece una forma conveniente de organizar los tests, separando su estructura de los datos que manipulan.

• La clave es:

• definir un test como paramétrico.

• definir un generador de parámetros para la suite, que se usará para instanciar los datos para los tests.

Tests “parametrizados” por los datos

que manipulan

(38)

Consideremos nuevamente el método max. El siguiente test parametrizado lo prueba con varios datos diferentes:

using namespace std;

using ::testing::TestWithParam;

using ::testing::Values;

typedef ::tr1::tuple<int, int> myTuple;

class MaxTest : public TestWithParam< myTuple > { };

TEST_P(MaxTest, Test) { myTuple row;

int maxVal, minVal;

row = GetParam();

maxVal = ::tr1::get<0>(row); minVal = ::tr1::get<1>(row);

ASSERT_EQ(maxVal, max(minVal,maxVal)); }

INSTANTIATE_TEST_CASE_P(MaxOnLeft, MaxTest, Values( myTuple (2,1),

myTuple (45,-2), myTuple (-12,-90) ));

Indica que la suite está formada por tests parametrizados.

Test paramétrico (TEST_P)

Datos de los test

Test parametrizados: un ejemplo

(39)

Test parametrizados

• El runner genera una instancia de cada test por cada dato: • MaxOnLeft/MaxTest.Test/0 para (2,1)

• MaxOnLeft/MaxTest.Test/1 para (42,2)

• MaxOnLeft/MaxTest.Test/2 para (-12,-90)

• Puede además haber más de una generador de datos

(entonces se genera una instancia por cada test, por cada dato de cada generador).

• GoogleTest define una serie de funciones para definir

generadores de datos:

Generador Descripción

Range(begin, end, step) el conjunto de valores en el rango comenzando en begin, terminando en end (no incluido) de a step

incrementos.

Values(v1,v2,...,vn) el conjunto de valores {v1, v2, ..., vn}.

ValuesIn(container) el conjunto de valores del contenedor container.

Bool el conjunto de valores {true, false}.

(40)

Demo

• Max Parametrizado

(41)

Testing unitario: beneficios

El testing unitario

sistematizado:

Facilita los cambios

: los test permiten comprobar

que la unidad continúa funcionando correctamente a

pesar de cualquier tipo de cambio (refactoring o

nuevas funcionalidades); su sistematización facilita el

testing de regresión.

Simplifica la integración

: los test reducen la

incertidumbre sobre las componentes individuales,

facilitando la integración y su verificación, ya sea

(42)

Testing unitario: beneficios

El testing unitario

sistematizado:

Sirve como documentación

: los test especifican las

partes criticas del comportamiento de la unidad, ya

sea su uso apropiado o inapropiado; sirven como

ejemplo y descripción siempre actualizada de la API.

Contribuye al diseño

: los test sirven como ejemplo

de uso y dan feedback temprano; fuerzan a

enforcarse en el

qué y en el cómo, encontrando

defectos de diseño y aportando a su

replanteamiento.

(43)

Testing unitario: principios

Primero escribir los test.

Diseñar pensando en la “testeabilidad”.

Utilizar la puerta principal.

Documentar los test fixtures.

No modificar el SUT.

Mantener la independencia de los test.

Aislar el SUT.

Minimizar el solapamiento.

Minimizar el código inestable.

Mantener la lógica de testing fuera del código de producción.

Verificar sólo una condición por test.

Probar diferentes incumbencias

(44)

Ejercicio

Considerando los métodos definidos en staticRoutines:

1.Extendé el test de largest para incluir más casos.

2.Construí una suite paramétrica para testear

bubbleSort.

(45)

Referencias

Figure

Updating...

References