resolucion - programacion orientada a objetos c++ ejemplos
Cómo hacer pruebas unitarias en miembros privados(y métodos) de clases C++ (8)
Bueno, las pruebas unitarias deberían probar unidades e, idealmente, cada clase es una unidad autónoma; esto se sigue directamente del principio de responsabilidad única.
Por lo tanto, no debería ser necesario probar a los miembros privados de una clase: la clase es una caja negra que puede incluirse en una prueba unitaria tal como está.
Por otro lado, esto no siempre es cierto, y a veces con buenas razones (por ejemplo, varios métodos de la clase podrían confiar en una función de utilidad privada que debería probarse). Una solución muy simple, muy segura pero al final exitosa es poner lo siguiente en su archivo de prueba de unidad, antes de incluir el encabezado que define su clase:
#define private public
Por supuesto, esto destruye la encapsulación y es malvado . Pero para las pruebas, sirve al propósito.
Esta pregunta ya tiene una respuesta aquí:
Soy muy nuevo en pruebas unitarias y estoy un poco confundido.
Estoy intentando hacer pruebas unitarias (usando el marco de prueba de la unidad Boost) en una clase de C ++ llamada VariableImpl
. Aquí están los detalles.
class Variable
{
public:
void UpdateStatistics (void) {
// compute mean based on m_val and update m_mean;
OtherClass::SendData (m_mean);
m_val.clear ();
}
virtual void RecordData (double) = 0;
protected:
std::vector<double> m_val;
private:
double m_mean;
};
class VariableImpl : public Variable
{
public:
virtual void RecordData (double d) {
// put data in m_val
}
};
Mi pregunta es ¿cómo puedo verificar que la media se calcule correctamente? Tenga en cuenta que 1) m_mean
está protegido y 2) UpdateStatistics
llama a un método de otra clase y luego borra el vector.
La única forma en que puedo ver sería agregar un getter (por ejemplo, GetMean
), pero no me gusta esta solución, ni creo que sea la más elegante.
¿Cómo debería hacer?
¿Y qué debería hacer si probara un método privado en lugar de una variable privada?
TIA,
Jir
En general, estoy de acuerdo con lo que otros han dicho aquí: solo la interfaz pública debe someterse a pruebas unitarias. Sin embargo, acabo de tener un caso en el que primero tuve que llamar a un método protegido para prepararme para un caso de prueba específico. Primero probé el enfoque #define protected public
mencionado anteriormente; esto funcionó con Linux / gcc, pero falló con Windows / VisualStudio. La razón fue que cambiar protected
to public
también cambió el nombre del símbolo mutilado y así me dio errores del enlazador: la biblioteca proporcionó un __declspec(dllexport) void Foo::bar()
, pero con el #define
en su lugar, mi programa de prueba esperaba un __declspec(dllimport) void Foo::bar()
público de __declspec(dllimport) void Foo::bar()
que me dio un error de símbolo no resuelto.
Por esta razón, cambié a una solución basada en friend
, haciendo lo siguiente en el encabezado de mi clase:
// This goes in Foo.h
namespace unit_test { // Name this anything you like
struct FooTester; // Forward declaration for befriending
}
// Class to be tested
class Foo
{
...
private:
bool somePrivateMethod(int bar);
// Unit test access
friend struct ::unit_test::FooTester;
};
Y en mi caso real de prueba, hice esto:
#include <Foo.h>
#include <boost/test/unit_test.hpp>
namespace unit_test {
// Static wrappers for private/protected methods
struct FooTester
{
static bool somePrivateMethod(Foo& foo, int bar)
{
return foo.somePrivateMethod(bar);
}
};
}
BOOST_AUTO_TEST_SUITE(FooTest);
BOOST_AUTO_TEST_CASE(TestSomePrivateMethod)
{
// Just a silly example
Foo foo;
BOOST_CHECK_EQUAL(unit_test::FooTester::somePrivateMethod(foo, 42), true);
}
BOOST_AUTO_TEST_SUITE_END();
Esto funciona con Linux / gcc y Windows / VisualStudio.
En general, sugiero probar la interfaz pública de sus clases, no las implementaciones privadas / protegidas. En este caso, si no se puede observar desde el mundo exterior por un método público, entonces la prueba unitaria puede no necesitar probarlo.
Si la funcionalidad requiere una clase hija, cualquiera de las unidades prueba la clase derivada real O crea su propia clase derivada de prueba que tiene una implementación apropiada.
Para un método / variable protegido, herede una clase de prueba de la clase y realice su prueba.
Para un privado, introduce una clase de amigos. No es la mejor de las soluciones, pero puede hacer el trabajo por usted.
O este truco
#define private public
Si bien, en mi opinión, la necesidad de probar miembros / métodos privados de una clase es un olor a código, creo que es técnicamente posible en C ++.
Como ejemplo, supongamos que tiene una clase Dog con miembros / métodos privados, excepto para el constructor público:
#include <iostream>
#include <string>
using namespace std;
class Dog {
public:
Dog(string name) { this->name = name; };
private:
string name;
string bark() { return name + ": Woof!"; };
static string Species;
static int Legs() { return 4; };
};
string Dog::Species = "Canis familiaris";
Ahora, por alguna razón, le gustaría probar los privados. Puedes usar privablic para lograr eso.
Incluya un encabezado llamado privablic.h junto con la implementación deseada así:
#include "privablic.h"
#include "dog.hpp"
luego mapear algunos stubs según los tipos de cualquier miembro de instancia
struct Dog_name { typedef string (Dog::*type); };
template class private_member<Dog_name, &Dog::name>;
... y método de instancia;
struct Dog_bark { typedef string (Dog::*type)(); };
template class private_method<Dog_bark, &Dog::bark>;
hacer lo mismo con todos los miembros de instancias estáticas
struct Dog_Species { typedef string *type; };
template class private_member<Dog_Species, &Dog::Species>;
... y métodos de instancia estáticos.
struct Dog_Legs { typedef int (*type)(); };
template class private_method<Dog_Legs, &Dog::Legs>;
Ahora puedes probarlos todos:
#include <assert.h>
int main()
{
string name = "Fido";
Dog fido = Dog(name);
string fido_name = fido.*member<Dog_name>::value;
assert (fido_name == name);
string fido_bark = (&fido->*func<Dog_bark>::ptr)();
string bark = "Fido: Woof!";
assert( fido_bark == bark);
string fido_species = *member<Dog_Species>::value;
string species = "Canis familiaris";
assert(fido_species == species);
int fido_legs = (*func<Dog_Legs>::ptr)();
int legs = 4;
assert(fido_legs == legs);
printf("all assertions passed/n");
};
Salida:
$ ./main
all assertions passed
Puede ver las fuentes de test_dog.cpp y dog.hpp .
DESCARGO DE RESPONSABILIDAD : Gracias a las percepciones de otras personas inteligentes , he reunido la "biblioteca" antes mencionada, capaz de acceder a miembros y métodos privados de una clase determinada de C ++ sin alterar su definición o comportamiento. Para que funcione, es (obviamente) necesario conocer e incluir la implementación de la clase.
NOTA : revisé el contenido de esta respuesta para seguir las instrucciones sugeridas por los revisores.
Un buen enfoque para probar los datos protegidos en c + + es la asignación de una clase de proxy amigo:
#define FRIEND_TEST(test_case_name, test_name)/
friend class test_case_name##_##test_name##_Test
class MyClass
{
private:
int MyMethod();
FRIEND_TEST(MyClassTest, MyMethod);
};
class MyClassTest : public testing::Test
{
public:
// ...
void Test1()
{
MyClass obj1;
ASSERT_TRUE(obj1.MyMethod() == 0);
}
void Test2()
{
ASSERT_TRUE(obj2.MyMethod() == 0);
}
MyClass obj2;
};
TEST_F(MyClassTest, PrivateTests)
{
Test1();
Test2();
}
ver más prueba de goolge (gtest): http://code.google.com/p/googletest-translations/
Unidad de prueba VariableImpl tal que si su comportamiento está asegurado, también lo es Variable.
Probar pruebas internas no es lo peor del mundo, pero el objetivo es que puedan ser cualquier cosa siempre que se garanticen los contratos de interfaz. Si eso significa crear un montón de implementaciones falsas extrañas para probar Variable, entonces eso es razonable.
Si eso parece mucho, considere que la herencia de implementación no crea una gran separación de preocupaciones. Si es difícil probar la unidad, entonces ese es un olor de código bastante obvio para mí.
Ejemplo del marco de prueba de google:
// foo.h
#include "gtest/gtest_prod.h"
class Foo {
...
private:
FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
int Bar(void* x);
};
// foo_test.cc
...
TEST(FooTest, BarReturnsZeroOnNull) {
Foo foo;
EXPECT_EQ(0, foo.Bar(NULL));
// Uses Foo''s private member Bar().
}
La idea principal es el uso de la palabra clave friend cpp. Puede extender este ejemplo de la siguiente manera:
// foo.h
#ifdef TEST_FOO
#include "gtest/gtest_prod.h"
#endif
class Foo {
...
private:
#ifdef TEST_FOO
FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
#endif
int Bar(void* x);
};
Puede definir el preprocesador TEST_FOO de dos maneras:
1) dentro de CMakeLists.txt
option(TEST "Run test ?" ON)
if (TEST)
add_definitions(-DTEST_FOO)
endif()
2) como argumentos para tu compilador
g++ -D TEST $your_args