Hier ist ein typischer C ++ - Code:
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;
...
Es sieht aus wie ein anständiger idiomatischer C ++ - Code - eine Klasse, eine Hilfsfunktion, match
die von den Methoden Foo
einiger Konstanten für diese Hilfsfunktion verwendet wird.
Und dann möchte ich Tests schreiben.
Es wäre völlig logisch, einen separaten Komponententest zu schreiben match
, da dies nicht trivial ist.
Es befindet sich jedoch in einem anonymen Namespace.
Natürlich kann ich einen Test schreiben, der anrufen würde Foo::f()
. Allerdings ist es kein guter Test, wenn er Foo
schwer und kompliziert ist. Ein solcher Test kann den Testteilnehmer jedoch nicht von anderen Faktoren isolieren, die nichts damit zu tun haben.
Also muss ich umziehen match
und alles andere aus dem anonymen Namensraum raus.
Frage: Wozu sollten Funktionen und Konstanten in den anonymen Namespace eingefügt werden, wenn sie dadurch in Tests unbrauchbar werden?
quelle
foo.cpp
, nicht der Header! OP scheint recht gut zu verstehen, dass Sie in einem Header keine Namespaces setzen sollten.friend
Schlüsselworts für diesen Zweck nicht empfohlen wird. Kombinieren Sie dies mit Ihrer Annahme Wenn eine Einschränkung für eine Methode zu einer Situation führt, in der Sie sie nicht mehr direkt testen können, bedeutet dies, dass private Methoden nicht nützlich waren.Antworten:
Wenn Sie die Details der privaten Implementierung einem Komponententest unterziehen möchten, weichen Sie für unbenannte Namespaces auf die gleiche Weise aus wie für private (oder geschützte) Klassenmitglieder:
Einbruch und Party.
Während für Klassen, die Sie missbrauchen
friend
, für unbenannte Namespaces der#include
-Mechanismus missbraucht wird , werden Sie nicht einmal gezwungen, den Code zu ändern.Jetzt, da sich Ihr Testcode (oder besser gesagt, nur etwas, um alles freizulegen) in derselben TU befindet, gibt es kein Problem mehr.
Ein Wort zur Vorsicht: Wenn Sie die Implementierungsdetails testen, wird Ihr Test abgebrochen, wenn sich diese ändern. Stellen Sie wirklich sicher, dass Sie nur die Implementierungsdetails testen, die ohnehin lecken, oder akzeptieren Sie, dass Ihr Test ungewöhnlich kurzlebig ist.
quelle
Die Funktion in Ihrem Beispiel sieht ziemlich komplex aus, und es ist möglicherweise besser, sie zum Zweck des Komponententests in die Kopfzeile zu verschieben.
Um sie vom Rest der Welt zu isolieren. Und Sie können nicht nur Funktionen und Konstanten in den anonymen Namespace eingeben, sondern auch Typen.
Wenn es Ihre Einheitentests jedoch sehr komplex macht, machen Sie es falsch. In diesem Fall gehört die Funktion nicht dorthin. Dann ist es Zeit für ein kleines Refactoring, um das Testen zu vereinfachen.
Daher sollten in anonymen Namespaces nur sehr einfache Funktionen verwendet werden, manchmal Konstanten und Typen (einschließlich typedefs), die in dieser Übersetzungseinheit verwendet werden.
quelle
Der Code, für den Sie gezeigt haben,
match
ist ein ziemlich trivialer 1-Liner ohne schwierige Randfälle, oder ist das wie ein vereinfachtes Beispiel? Wie auch immer, ich gehe davon aus, dass es vereinfacht ist ...Diese Frage wollte mich dazu bringen, hier einzuspringen, da Deduplicator bereits eine sehr gute Möglichkeit zum Einbruch und Durchstieg zeigte
#include
Tricks einzudringen . Der Wortlaut hier klingt jedoch so, als würde man jedes einzelne interne Implementierungsdetail von allem testen. Dies ist eine Art universelles Endziel, wenn es weit davon entfernt ist.Das Ziel selbst von Unit-Tests besteht nicht immer darin, jede kleine granulare interne Mikro-Funktionseinheit zu testen. Dieselbe Frage gilt für statische Dateibereichsfunktionen in C. Sie können die Beantwortung der Frage sogar erschweren, indem Sie fragen, warum Entwickler
pimpls
in C ++ verwenden, was beidesfriendship
und#include
Tricks in der White Box erfordern würde. z.BAus einer Art pragmatischer Perspektive mag es grob klingen, aber
match
mit einigen Randfällen, die dazu führen, dass es auslöst, möglicherweise nicht richtig implementiert werden. Wenn jedoch die einzige äußere Klasse,Foo
die Zugriff darauf hat,match
es möglicherweise nicht in einer Weise verwenden kann, die auf diese Randfälle stößtFoo
,match
ist dies für die Richtigkeit dieser Randfälle, die niemals angetroffen werden, außer wennFoo
sich an welchem Punkt etwas ändert, irrelevant Die Tests vonFoo
werden fehlschlagen und wir werden es sofort wissen.Eine obsessivere Denkweise, die jedes einzelne interne Implementierungsdetail testen möchte (möglicherweise eine unternehmenskritische Software, z. B.), möchte vielleicht einbrechen und feiern, aber viele Leute denken nicht unbedingt, dass dies die beste Idee ist, da sie das schaffen würde spröde Tests vorstellbar. YMMV. Aber ich wollte mich nur mit dem Wortlaut dieser Frage befassen, der so klingt, als ob diese Art von überfeinkörniger Testbarkeit auf interner Detailebene ein Endziel sein sollte, wenn sich auch die strengste Einstellung zum Testen von Einheiten hier etwas entspannen könnte und vermeiden Sie es, die Einbauten jeder Klasse zu durchleuchten.
Warum definieren die Benutzer Funktionen in anonymen Namespaces in C ++ oder als statische Funktionen im Dateibereich mit interner Verknüpfung in C, die vor der Außenwelt verborgen sind? Und das war es vor allem: Sie vor der Außenwelt zu verstecken. Dies hat eine Reihe von Effekten, von der Verkürzung der Kompilierungszeiten bis zur Reduzierung der Komplexität (auf was anderswo nicht zugegriffen werden kann, kann an anderer Stelle keine Probleme verursachen) und so weiter. Wahrscheinlich ist die Überprüfbarkeit von privaten / internen Implementierungsdetails nicht das Wichtigste, wenn die Leute dies erledigen, um beispielsweise die Erstellungszeiten zu verkürzen und unnötige Komplexität vor der Außenwelt zu verbergen.
quelle