c++ - unitarias - Parametrizar una prueba usando CppUnit
testing unitario software (7)
Mi organización está usando CppUnit y estoy intentando ejecutar la misma prueba usando diferentes parámetros. Ejecutar un ciclo dentro de la prueba no es una buena opción ya que cualquier falla abortará la prueba. Miré TestDecorator
y TestCaller
pero ninguno parece encajar realmente. Las muestras de código serían útiles.
No soy un programador de C ++, pero puedo ayudar con el concepto de prueba de unidad:
Los casos de prueba se ejecutan de forma aislada y sin dependencia de los parámetros externos. Además, debe mantener el número de casos de prueba al mínimo que cubre la mayor parte de su código. Sin embargo, hay casos (y ya he tratado con algunos), donde algunas pruebas tienen el mismo aspecto, que difieren solo en algunos parámetros menores. La mejor apuesta es escribir un dispositivo que tome el parámetro del que está hablando, y luego tener un caso de prueba para cada uno de los parámetros, llamando al dispositivo con él. Un ejemplo genérico a continuación:
class MyTestCase
# this is your fixture
def check_special_condition(param)
some
complex
tests
end
# these are your test-cases
def test_1
check_special_condition("value_1")
end
def test_2
check_special_condition("value_2")
end
end
De lo contrario, no estás escribiendo casos de prueba verdaderos, porque se supone que son reproducibles sin mucho conocimiento de quien los está ejecutando. Me imagino que hay un puñado de parámetros que son todos importantes como entrada para las pruebas. Entonces, ¿por qué no hacer que cada uno sea explícito dentro de su propio caso de prueba? Esa es también la mejor manera de documentar en ese momento, en lugar de escribir un documento separado para guiar al programador que leerá el código años después.
No parece posible en CppUnit parametrizar un caso de prueba directamente (ver aquí y aquí ). Sin embargo, tienes algunas opciones:
Usa un RepeatedTest
Es posible que pueda hacer un uso inteligente del decorador RepeatedTest
incorporado. Esto permite que un caso de prueba se ejecute varias veces (aunque sin parametrización).
Debo admitir que nunca lo he usado, pero quizás podría hacer que el RepeatedTest
manejara alguna función de gatekeeper, lo que (usando una variable estática de clase, ¿no?) Escoge una entrada diferente en cada ejecución. A su vez, llamaría a la verdadera función que le gustaría probar con ese valor como entrada.
Use una subclase de TestCase
Una persona en la página SourceForge de TestCase
afirma haber escrito una subclase de TestCase
que ejecutará una prueba en particular un número arbitrario de veces, aunque de una manera ligeramente diferente a la que ofrece la clase RepeatedTest
. Lamentablemente, el cartel simplemente describió la motivación para crear la clase, pero no proporcionó el código fuente. Hubo, sin embargo, una oferta para contactar al individuo para más detalles.
Use una función auxiliar simple
La manera más directa (pero menos automatizada) de hacer esto es crear una función auxiliar que tome el parámetro que desea transferir a su función "real" y luego tener muchos casos de prueba individuales. Cada caso de prueba llamaría a su función auxiliar con un valor diferente.
Si eliges cualquiera de las dos primeras opciones enumeradas anteriormente, me interesaría saber de tu experiencia.
class members : public CppUnit::TestFixture
{
int i;
float f;
};
class some_values : public members
{
void setUp()
{
// initialization here
}
};
class different_values : public members
{
void setUp()
{
// different initialization here
}
};
tempalte<class F>
class my_test : public F
{
CPPUNIT_TEST_SUITE(my_test<F>);
CPPUNIT_TEST(foo);
CPPUNIT_TEST_SUITE_END();
foo() {}
};
CPPUNIT_TEST_SUITE_REGISTRATION(my_test<some_values>);
CPPUNIT_TEST_SUITE_REGISTRATION(my_test<different_values>);
No sé si eso se considera kosher según la "forma preferida de hacer las cosas" de CppUnit, pero ese es el enfoque que estoy tomando ahora.
Esta es una pregunta muy antigua, pero solo necesitaba hacer algo similar y se me ocurrió la siguiente solución. No estoy 100% contento con eso, pero parece hacer el trabajo bastante bien
Definir un conjunto de parámetros de entrada para un método de prueba. Por ejemplo, digamos que estas son cadenas, así que hagamos lo siguiente:
std::vector<std::string> testParameters = { "string1", "string2" }; size_t testCounter = 0;
Implemente una función de probador genérico, que con cada invocación tomará el siguiente parámetro de la matriz de prueba, por ejemplo:
void Test::genericTester() { const std::string ¶m = testParameters[testCounter++]; // do something with param }
En la prueba declaración de método addTestToSuite () (oculto por las macros de CPPUNIT) en lugar de (o junto a) la definición de métodos con las macros CPPUNIT_TEST, agregue un código similar al siguiente:
CPPUNIT_TEST_SUITE(StatementTest); testCounter = 0; for (size_t i = 0; i < testParameters.size(); i++) { CPPUNIT_TEST_SUITE_ADD_TEST( ( new CPPUNIT_NS::TestCaller<TestFixtureType>( // Here we use the parameter name as the unit test name. // Of course, you can make test parameters more complex, // with test names as explicit fields for example. context.getTestNameFor( testParamaters[i] ), // Here we point to the generic tester function. &TestFixtureType::genericTester, context.makeFixture() ) ) ); } CPPUNIT_TEST_SUITE_END();
De esta forma registramos genericTester () varias veces, una para cada parámetro, con un nombre especificado. Esto parece funcionar bastante bien para mí.
Espero que esto ayude a alguien.
Ante la sugerencia de Marcin, he implementado algunas macros que ayudan a definir pruebas de CppUnit parametrizadas.
Con esta solución solo necesita reemplazar las macros antiguas CPPUNIT_TEST_SUITE y CPPUNIT_TEST_SUITE_END dentro del archivo de encabezado de la clase:
CPPUNIT_PARAMETERIZED_TEST_SUITE(<TestSuiteClass>, <ParameterType>);
/*
* put plain old tests here.
*/
CPPUNIT_PARAMETERIZED_TEST_SUITE_END();
En el archivo de implementación, debe reemplazar la macro anterior CPPUNIT_TEST_SUITE_REGISTRATION con:
CPPUNIT_PARAMETERIZED_TEST_SUITE_REGISTRATION ( <TestSuiteClass>, <ParameterType> )
Estas macros requieren que implemente los métodos:
static std::vector parameters();
void testWithParameter(ParameterType& parameter);
- parameters (): proporciona un vector con los parámetros.
- testWithParameter (...): se llama para cada parámetro. Aquí es donde implementa su prueba parametrizada.
Una explicación detallada se puede encontrar aquí: http://brain-child.de/engineering/parameterizing-cppunit-tests
La versión alemana se puede encontrar aquí: http://brain-child.de/engineering/parametrierbare-tests-cppunit
Con base en la respuesta de consumerwhore, terminé con un enfoque muy agradable en el que puedo crear múltiples pruebas utilizando una macro de registro de una línea con tantos parámetros que quiero.
Solo defina una clase de parámetro:
class Param
{
public:
Param( int param1, std::string param2 ) :
m_param1( param1 ),
m_param2( param2 )
{
}
int m_param1;
std::string m_param2;
};
Haga que su accesorio de prueba lo use como "parámetro de plantilla sin tipo" (creo que así se llama):
template <Param& T>
class my_test : public CPPUNIT_NS::TestFixture
{
CPPUNIT_TEST_SUITE(my_test<T>);
CPPUNIT_TEST( doProcessingTest );
CPPUNIT_TEST_SUITE_END();
void doProcessingTest()
{
std::cout << "Testing with " << T.m_param1 << " and " << T.m_param2 << std::endl;
};
};
Haga que una macro pequeña cree un parámetro y registre un nuevo accesorio de prueba:
#define REGISTER_TEST_WITH_PARAMS( name, param1, param2 ) /
Param name( param1, param2 ); /
CPPUNIT_TEST_SUITE_REGISTRATION(my_test<name>);
Finalmente, agregue tantas pruebas como desee:
REGISTER_TEST_WITH_PARAMS( test1, 1, "foo" );
REGISTER_TEST_WITH_PARAMS( test2, 3, "bar" );
Ejecutar esta prueba te dará:
my_test<class Param test1>::doProcessingTestTesting with 1 and foo : OK
my_test<class Param test2>::doProcessingTestTesting with 3 and bar : OK
OK (2)
Test completed, after 0 second(s). Press enter to exit
El siguiente par de macro clase / ayudante funciona para mis casos de uso actuales. En su subclase TestFixture
, simplemente defina un método que acepte un parámetro y luego agregue la prueba con PARAMETERISED_TEST(method_name, argument_type, argument_value)
.
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/ui/text/TestRunner.h>
template <class FixtureT, class ArgT>
class ParameterisedTest : public CppUnit::TestCase {
public:
typedef void (FixtureT::*TestMethod)(ArgT);
ParameterisedTest(std::string name, FixtureT* fix, TestMethod f, ArgT a) :
CppUnit::TestCase(name), fixture(fix), func(f), arg(a) {
}
ParameterisedTest(const ParameterisedTest* other) = delete;
ParameterisedTest& operator=(const ParameterisedTest& other) = delete;
void runTest() {
(fixture->*func)(arg);
}
void setUp() {
fixture->setUp();
}
void tearDown() {
fixture->tearDown();
}
private:
FixtureT* fixture;
TestMethod func;
ArgT arg;
};
#define PARAMETERISED_TEST(Method, ParamT, Param) /
CPPUNIT_TEST_SUITE_ADD_TEST((new ParameterisedTest<TestFixtureType, ParamT>(context.getTestNameFor(#Method #Param), /
context.makeFixture(), /
&TestFixtureType::Method, /
Param)))
class FooTests : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(FooTests);
PARAMETERISED_TEST(ParamTest, int, 0);
PARAMETERISED_TEST(ParamTest, int, 1);
PARAMETERISED_TEST(ParamTest, int, 2);
CPPUNIT_TEST_SUITE_END();
public:
void ParamTest(int i) {
CPPUNIT_ASSERT(i > 0);
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(FooTests);
int main( int argc, char **argv)
{
CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest( registry.makeTest() );
bool wasSuccessful = runner.run( "", false );
return wasSuccessful;
}