Aquí hay un código típico de C ++:
foo.hpp
#pragma once
class Foo {
public:
void f();
void g();
...
};
foo.cpp
#include "foo.hpp"
namespace {
const int kUpperX = 111;
const int kAlternativeX = 222;
bool match(int x) {
return x < kUpperX || x == kAlternativeX;
}
} // namespace
void Foo::f() {
...
if (match(x)) return;
...
Parece un código de C ++ idiomático decente: una clase, una función auxiliar match
que se usa mediante los métodos de Foo
, algunas constantes para esa función auxiliar.
Y luego quiero escribir pruebas.
Sería perfectamente lógico escribir una prueba unitaria separada para match
, porque es bastante no trivial.
Pero reside en un espacio de nombres anónimo.
Por supuesto que puedo escribir una prueba que llamaría Foo::f()
. Sin embargo, no será una buena prueba si Foo
es pesada y complicada, dicha prueba no aislará al examinado de otros factores no relacionados.
Así que tengo que moverme match
y todo lo demás fuera del espacio de nombres anónimo.
Pregunta: ¿cuál es el punto de poner funciones y constantes en el espacio de nombres anónimo, si los hace inutilizables en las pruebas?
fuente
foo.cpp
, no el encabezado! OP parece entender bastante bien que no debe colocar espacios de nombres anon en un encabezado.friend
no se recomienda abusar de la palabra clave para ese propósito. Combine eso con su suposición que si una restricción para un método lleva a una situación en la que ya no puede probarlo directamente, eso implicaría que los métodos privados no serían útiles.Respuestas:
Si desea probar unitariamente los detalles privados de implementación, haga el mismo tipo de esquiva para espacios de nombres sin nombre que para miembros de clase privados (o protegidos):
Entra y festeja.
Mientras que para las clases que abusa
friend
, para los espacios de nombres sin nombre, abusa del#include
mecanismo, que ni siquiera lo obliga a cambiar el código.Ahora que su código de prueba (o mejor solo algo para exponerlo todo) está en la misma TU, no hay problema.
Una palabra de precaución: si prueba los detalles de implementación, su prueba se romperá si cambian. Asegúrese de probar solo los detalles de implementación que se filtrarán de todos modos, o acepte que su prueba es inusualmente efímera.
fuente
La función en su ejemplo parece bastante compleja, y puede ser mejor moverla al encabezado, con el propósito de realizar pruebas unitarias.
Para aislarlos del resto del mundo. Y no solo las funciones y las constantes se pueden colocar en el espacio de nombres anónimo, sino también los tipos.
Sin embargo, si hace que las pruebas de su unidad sean muy complejas, entonces lo está haciendo mal. En tal caso, la función no pertenece allí. Entonces es hora de un poco de refactorización para simplificar las pruebas.
Por lo tanto, en el espacio de nombres anónimo solo deben ir funciones muy simples, a veces constantes y tipos (incluidos typedefs) utilizados en esa unidad de traducción.
fuente
El código que mostraste
match
es un 1-liner bastante trivial sin ningún caso de borde complicado, ¿o es un ejemplo simplificado? De todos modos, supondré que está simplificado ...Esta pregunta es lo que quería hacerme saltar aquí, ya que Deduplicator ya mostró una forma perfectamente buena de entrar y obtener acceso a través del
#include
engaño. Pero la redacción aquí hace que parezca que probar cada detalle de implementación interna de todo es una especie de objetivo final universal, cuando está lejos de serlo.El objetivo de las pruebas unitarias uniformes no siempre es probar cada pequeña microunidad interna granular de funcionalidad. La misma pregunta se aplica a las funciones estáticas de alcance de archivo en C. Incluso puede hacer que la pregunta sea más difícil de responder preguntando por qué los desarrolladores usan
pimpls
en C ++, lo que requeriría tantofriendship
y#include
trucos para la caja blanca, intercambiando la facilidad de prueba de los detalles de implementación para tiempos de compilación mejorados p.ejDesde un punto de vista pragmático, puede sonar asqueroso, pero
match
puede que no se implemente correctamente con algunos casos extremos que hacen que se dispare. Sin embargo, si la única clase externaFoo
, a la que tiene accesomatch
, no puede usarla de una manera que encuentre esos casos límite, entonces es bastante irrelevante para la corrección deFoo
quematch
tiene estos casos extremos que nunca se encontrarán a menosFoo
que haya cambios, en ese punto las pruebas deFoo
fallarán y lo sabremos de inmediato.Una mentalidad más obsesiva y ansiosa por probar cada detalle de implementación interna (tal vez un software de misión crítica, por ejemplo) podría querer entrar y divertirse, pero mucha gente no necesariamente piensa que esa es la mejor idea, ya que crearía el La mayoría de las pruebas frágiles imaginables. YMMV. Pero solo quería abordar la redacción de esta pregunta que hace que parezca que este tipo de comprobabilidad de nivel de detalle interno súper fino debe ser un objetivo final, cuando incluso la mentalidad de prueba de unidad más rigurosa podría relajarse un poco aquí y evite radiografiar los elementos internos de cada clase.
Entonces, ¿por qué las personas definen funciones en espacios de nombres anónimos en C ++ o como funciones estáticas de alcance de archivo con enlaces internos en C, ocultos del mundo exterior? Y eso es todo: esconderlos del mundo exterior. Eso tiene una serie de efectos desde reducir los tiempos de compilación hasta reducir la complejidad (a lo que no se puede acceder en otro lugar no puede causar problemas en otros lugares) y así sucesivamente. Probablemente la capacidad de prueba de los detalles de implementación privados / internos no es lo más importante en la mente de las personas cuando lo hacen, por ejemplo, reduciendo los tiempos de construcción y ocultando la complejidad innecesaria del mundo exterior.
fuente