Suponiendo que tengo la siguiente estructura de clase (sobre simplificada):
class Base
{
public:
Base(int valueForFoo) : foo(valueForFoo) { };
virtual ~Base() = 0;
int doThings() { return foo; };
int doOtherThings() { return 42; };
protected:
int foo;
}
class BarDerived : public Base
{
public:
BarDerived() : Base(12) { };
~BarDerived() { };
int doBarThings() { return foo + 1; };
}
class BazDerived : public Base
{
public:
BazDerived() : Base(25) { };
~BazDerived() { };
int doBazThings() { return 2 * foo; };
}
Como puede ver, la doThings
función en la clase Base devuelve resultados diferentes en cada clase Derivada debido a los diferentes valores de foo
, mientras que la doOtherThings
función se comporta de manera idéntica en todas las clases .
Cuando deseo de implementar las pruebas unitarias para estas clases, el manejo de doThings
, doBarThings
/ doBazThings
es claro para mí - que necesitan ser cubiertos por cada clase derivada. Pero, ¿cómo se debe doOtherThings
manejar? ¿Es una buena práctica duplicar esencialmente el caso de prueba en ambas clases derivadas? El problema empeora si hay media docena de funciones doOtherThings
y más clases derivadas .
c++
unit-testing
testing
CharonX
fuente
fuente
virtual
?BarDerived
yBase
puede haber sido una vez la misma clase. Cuando se iba a agregar una funcionalidad similar, la parte común se movía a la clase Base, con diferentes especializaciones implementadas en cada clase Derivada.Respuestas:
En sus pruebas
BarDerived
, quiere demostrar que todos los métodos (públicos)BarDerived
funcionan correctamente (para las situaciones que ha probado). Del mismo modo paraBazDerived
.El hecho de que algunos de los métodos se implementen en una clase base no cambia este objetivo de prueba para
BarDerived
yBazDerived
. Eso lleva a la conclusión de queBase::doOtherThings
debe probarse tanto en el contexto deBarDerived
yBazDerived
como para obtener pruebas muy similares para esa función.La ventaja de las pruebas
doOtherThings
para cada clase derivada es que si los requisitos para elBarDerived
cambioBarDerived::doOtherThings
deben devolver 24, la falla de la prueba en el caso deBazDerived
prueba le indica que podría estar incumpliendo los requisitos de otra clase.fuente
Normalmente esperaría que Base tuviera su propia especificación, que puede verificar para cualquier implementación conforme, incluidas las clases derivadas.
fuente
Tienes un conflicto aquí.
Desea probar el valor de retorno de
doThings()
, que se basa en un literal (valor constante).Cualquier prueba que escriba para esto se reducirá inherentemente a la prueba de un valor constante , lo cual no tiene sentido.
Para mostrarle un ejemplo más sensible (soy más rápido con C #, pero el principio es el mismo)
Esta clase se puede probar de manera significativa:
Esto tiene más sentido para probar. Su salida se basa en la entrada que eligió darle. En tal caso, puede dar a una clase una entrada elegida arbitrariamente, observar su salida y probar si coincide con sus expectativas.
Algunos ejemplos de este principio de prueba. Tenga en cuenta que mis ejemplos siempre tienen control directo sobre cuál es la entrada del método comprobable.
Todas estas pruebas pueden ingresar cualquier valor que deseen , siempre que sus expectativas estén en línea con lo que están ingresando.
Pero si su salida se decide por un valor constante, entonces tendrá que crear una expectativa constante y luego probar si el primero coincide con el segundo. Lo cual es una tontería, esto siempre o nunca va a pasar; ninguno de los cuales es un resultado significativo.
Algunos ejemplos de este principio de prueba. Observe que estos ejemplos no tienen control sobre al menos uno de los valores que se están comparando.
Estas pruebas para un valor particular. Si cambia este valor, sus pruebas fallarán. En esencia, no está probando si su lógica funciona para cualquier entrada válida, simplemente está probando si alguien puede predecir con precisión un resultado sobre el cual no tiene control.
Eso es esencialmente probar el conocimiento del escritor de prueba de la lógica empresarial. No está probando el código, sino el propio escritor.
Volviendo a su ejemplo:
¿Por qué
BarDerived
siempre tiene unfoo
igual a12
? ¿Cuál es el significado de este?Y dado que ya has decidido esto, ¿qué estás tratando de ganar escribiendo una prueba que confirme que
BarDerived
siempre tiene unfoo
igual12
?Esto empeora aún más si comienzas a tener en cuenta que
doThings()
se puede anular en una clase derivada. Imagínese siAnotherDerived
anularadoThings()
para que siempre regresefoo * 2
. Ahora, vas a tener una clase que está codificada comoBase(12)
, cuyodoThings()
valor es 24. Si bien es técnicamente comprobable, carece de significado contextual. La prueba no es comprensible.Realmente no puedo pensar en una razón para usar este enfoque de valor codificado. Incluso si hay un caso de uso válido, no entiendo por qué está intentando escribir una prueba para confirmar este valor codificado . No hay nada que ganar al probar si un valor constante es igual al mismo valor constante.
Cualquier falla en la prueba demuestra inherentemente que la prueba es incorrecta . No hay resultado cuando una falla en la prueba demuestra que la lógica del negocio es incorrecta. De hecho, no puede confirmar qué pruebas se crean para confirmar en primer lugar.
El problema no tiene nada que ver con la herencia, en caso de que te lo estés preguntando. Que acaba de pasar a haber utilizado un valor const en el constructor de la clase base, pero que podría haber utilizado este valor const cualquier otro lugar y entonces no estaría relacionado con una clase heredada.
Editar
Hay casos en que los valores codificados no son un problema. (de nuevo, perdón por la sintaxis de C # pero el principio sigue siendo el mismo)
Si bien el valor constante (
2
/3
) todavía influye en el valor de salida deGetMultipliedValue()
, el consumidor de su clase todavía tiene control sobre él.En este ejemplo, aún se pueden escribir pruebas significativas:
base(value, 2)
coincide con la constanteinputValue * 2
.El primer punto no es relevante para la prueba. El segundo es!
fuente
<
y>
llaves que encapsulan las etiquetas HTML. Lamentablemente (debido a la locura) utiliza un navegador "especializado"![{
y}]!
, en su lugar, Intitech decide que debe admitir este navegador en su escritor HTML. Por ejemplo, tiene la funcióngetHeaderStart()
ygetHeaderEnd()
que, hasta ahora, devolvió<HEADER>
y<\HEADER>
.<
, la otra con![{
. Pero eso sería bastante malo. Entonces, cada función inserta el (los) carácter (s) establecido (s) en una variable en los lugares<
e>
iría, y crea clases derivadas que, dependiendo de si son de conformidad estándar o dementes, proporcionan la función apropiada<HEADER>
o![{HEADER}]!
ninguna degetHeaderStart()
las dos depende de la entrada y depende en el conjunto constante durante la construcción de la clase derivada. Aún así, me sentiría incómodo si megetHeaderStart()
tienen sentido o no?