Los espacios de nombres anónimos hacen que el código sea inestable

13

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 matchque 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 Fooes pesada y complicada, dicha prueba no aislará al examinado de otros factores no relacionados.

Así que tengo que moverme matchy 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?

Abyx
fuente
3
@ BЈовић vuelve a leer el código: ¡el espacio de nombres anónimo está dentro foo.cpp, no el encabezado! OP parece entender bastante bien que no debe colocar espacios de nombres anon en un encabezado.
amon
Alguna idea en Unit Testing C ++ Code en un espacio de nombres sin nombre ... pero el "punto" es que la encapsulación es buena. Después de todo, es el mismo problema que tiene con las funciones de miembros privados: no se pueden probar de manera no intrusiva, pero no desea renunciar a la ocultación de información para las pruebas unitarias (por ejemplo, stackoverflow.com/a/3676680/3235496 ).
manlio
1
Conceptualmente, su caso no es muy diferente del caso de probar (o no probar) métodos privados. Entonces, cuando busque "prueba de unidad privada" aquí en Programadores, obtendrá muchas respuestas que puede aplicar directamente a su caso.
Doc Brown
@DocBrown No estoy preguntando cómo probar funciones en anon. espacios de nombres Estoy preguntando "¿por qué poner el código en anon. Espacios de nombres?" (Ver el texto al final de la pregunta). Culpe a Ixrec por cambiar el título a algo diferente.
Abyx
1
@Abyx: en esas otras respuestas que mencioné anteriormente, encontrará un gran consenso de muchos expertos aquí de que es una muy mala idea probar métodos privados, y que friendno 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.
Doc Brown

Respuestas:

7

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 #includemecanismo, 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.

Deduplicador
fuente
6

La función en su ejemplo parece bastante compleja, y puede ser mejor moverla al encabezado, con el propósito de realizar pruebas unitarias.

¿Cuál es el punto de poner funciones y constantes en el espacio de nombres anónimos, si los hace inutilizables en las pruebas?

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.

BЈовић
fuente
5

Sería perfectamente lógico escribir una prueba unitaria separada para la coincidencia, porque no es nada trivial.

El código que mostraste matches un 1-liner bastante trivial sin ningún caso de borde complicado, ¿o es un ejemplo simplificado? De todos modos, supondré que está simplificado ...

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?

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 #includeengañ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 pimplsen C ++, lo que requeriría tanto friendship y #includetrucos para la caja blanca, intercambiando la facilidad de prueba de los detalles de implementación para tiempos de compilación mejorados p.ej

Desde un punto de vista pragmático, puede sonar asqueroso, pero matchpuede que no se implemente correctamente con algunos casos extremos que hacen que se dispare. Sin embargo, si la única clase externa Foo, a la que tiene acceso match, no puede usarla de una manera que encuentre esos casos límite, entonces es bastante irrelevante para la corrección de Fooque matchtiene estos casos extremos que nunca se encontrarán a menos Fooque haya cambios, en ese punto las pruebas de Foofallará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
Desearía poder votar esto diez veces. Como una tangente relacionada con el software de misión crítica y alta seguridad, está mucho más preocupado por la exactitud de los detalles. Este estilo idiomático para C ++ no es adecuado para eso. No usaría características del lenguaje como espacios de nombres anónimos o patrones de proxy. Controlaría mi límite de manera grosera con los encabezados de espacio de usuario [compatibles] y los encabezados de espacio de desarrollador [no compatibles].
David