REPL y tests

Un REPL (Read–Eval–Print Loop) es un pequeño programa que lee un comando del usuario, lo interpreta, lo evalúa, e imprime el resultado.

Este tipo de interfaces pueden usarse para: 1) tener una forma simplificada de acceder y ejecutar funcionalidad de nuestro programa, y 2) probar la lógica de una forma directa.

Por ejemplo, supongamos que tenemos una función suma:

int suma(int x, int y) {
  return x + y;
}

Y queremos ver que realmente esté haciendo lo que debería estar haciendo. Una opción es hacer un programa de prueba con varios asserts:

#define _DEBUG
#include <cassert>

int main() {
  assert(suma(0, 0) == 0);
  assert(suma(2, 3) == 5);
  assert(suma(4, 8) == 12);
  assert(suma(2, -3) == -1);
}

Pero supongamos que tenemos cientos de funciones, y que cada una puede interactuar con las demás generando distintos resultados y combinaciones. (O incluso los resultados esperados pueden no entrar en un archivo de código fuente, ej: el resultado puede ser una imagen.) En estos casos puede ser beneficioso tener un “script” al que se le puedan agregar pruebas.

En C++, la forma más simple de implementar un REPL es utilizando la función std::getline:

#include <iostream>
#include <string>
using namespace std;

int main() {
  string line;
  while (getline(cin, line)) {
    if (line == "exit")
      break;
  }
}

Con este código leemos línea a línea la entrada estándar hasta que el usuario ingrese exit. Ahí termina todo. Lo interesante es que podríamos leer cada línea e intentar interpretar los comandos que se ingresan. Una línea representaría una prueba distinta. Por ejemplo, teniendo un archivo de texto pruebas.txt:

suma 0 0 0
suma 2 3 5
suma 4 8 12
suma 2 -3 -1
exit

Podemos crear un programa que lea estas líneas, ejecute las llamadas a suma, y muestre los resultados:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

bool eval(const string& line);

int main() {
  string line;
  while (getline(cin, line)) {
    if (!eval(line))
      break;
  }
}

bool eval(const string& line) {
  stringstream input(line);

  string cmd;
  input >> cmd;

  if (cmd == "exit") {
    return false;
  }
  else if (cmd == "suma") {
    int x, y, expected;
    input >> x >> y >> expected;

    int result = suma(x, y);

    cout << "suma(" << x << ", " << y << ") = "
         << result << " "
         << (result != expected ? "(FAILED) ": "(OK)")
         << endl;
  }

  return true;
}

Así podemos ejecutar el programa por medio de la línea de comando pasándole el archivo pruebas.txt como entrada estándar. Es como si cada línea del archivo fuera escrita por el usuario a mano, sólo que todo sucede de forma automática y obtenemos los resultados al instante:

suma(0, 0) = 0 (OK)
suma(2, 3) = 5 (OK)
suma(4, 8) = 12 (OK)
suma(2, -3) = -1 (OK)

Creamos un pequeño script tests.bat que sólo haga:

repl.exe < pruebas.txt

¿La ventaja? Podemos –nosotros y otra gente– agregar más pruebas sin recompilar el programa original, y con doble-click ver los resultados.


 29 April 2015 | cpp programacion