UnitTests (GoogleTests) and ASSERT
-
Guten Morgen,
ich möchte googleTest verwenden um 100% codecoverage zu erreichen:
Da ich noch in der Phase bin zu entscheiden wie ich meine Funktionen (signatur) implementiere, damit ich diese auch gut testen bin , habe ich z.b. aktuell folgender fall:
void DoSomeThing(struct stFoo_t * foo) { ASSERT(foo); .... }
nun das kann ich ja nicht wirklich abfangen, könnte nun sowas machen
BOOL DoSomeThing(struct stFoo_t * foo) { if(!foo) return FALSE; .... return TRUE; }
oder sowas
BOOL DoSomeThing(struct stFoo_t * foo, BOOL* sucess) { if(!foo) { *sucess=FALSE; return; } .... * sucess= TRUE; }
oder eine FehlerCode zurückgeben etc.
Die eigentlich Frage ist, ob es das ein Funktion Signatur Styleguide gibt, für unit-test- Entwicklung.
Was würdet ihr empfehlen, dass ich aus der BLACKBOX assert beim testen feingranularer testen könnte?
-
-
@5cript sagte in UnitTests (GoogleTests) and ASSERT:
https://google.github.io/googletest/advanced.html#death-tests
super danke für den hinweis.. jedoch könnte meine assert auch so aussehe
ASSERT(a && b && c)
und um den exakten Grund nach außen zu schubsten müsste ich sagen
if(!a) return 1; if(!b) return 2; if(!c) return 3; return 0;
also grundsätzlich wäre eine detaillierte Fehler Analyse besser, um detaillierter zu testen oder nicht?
-
@SoIntMan Irgendwie verstehe ich dein Problem nicht richtig.
Asserts sind Überprüfungen, bei denen im Fehlerfall das Programm beendet wird. Um diese Fälle zu überprüfen sind death tests da.
Bei asserts muss du aber beachten, dass die in der Regel in Release abgeschaltet sind. D.h. du hast unterschiedliche Tests für Release und Debug.
Deine Frage klingt irgendwie mehr nach: Wann sollte man asserts verwenden und wann Exceptions bzw eine andere Fehler Rückgabe, die das Programm nicht beendet.
-
@Schlangenmensch sagte in UnitTests (GoogleTests) and ASSERT:
@SoIntMan Irgendwie verstehe ich dein Problem nicht richtig.
ja ich verstehe mein Problem auch noch nicht so richtig:)
Ich versuche gerade herauszufinden wie ich meine funktionen sauber "testbar" implemtieren kann. Statt all eingabe Parameter in eine funktion mit einem ASSERT zu prüfen und ggf. abzubrechen , oder ich gebe bei jeder "konstellation" von falschen parametern einen entsprechenden Fehlercodes zurück... aber das liegt ja in meinen ermessen ob ich da brauche.
ASSERT würde ich dann nur verwenden wenn bswp. der "Self, this, Ptr" für eine methode null ist!?
Die essentielle Frage ist ehr: wie schreibt man gut testebaren code bzw. funktionen:)
-
@SoIntMan Ok, erstmal: Ich nutze Asserts um damit Programmierfehler abzufangen. D.h. immer wenn er ein Fehlerzustand ist, der zur Laufzeit auftreten kann, aufgrund fehlerhafter Eingaben an das Programm nutze ich keine Asserts sondern Exceptions oder andere Mechanismen um Fehler anzuzeigen.
Über die Frage, wie man gut testbaren Code schreibt gibt es wahrscheinlich ganze Bücher
Aber mal ein paar Punkte die dabei helfen:
- Testdriven development
- Single responsibility principle (Die anderen SOLID Prinzipien helfen sichlich auch)
- Dependency Injection (In Verbindung mit dem D aus SOLID
)
-
Zusätzlich zu den wichtigen Themen, die @Schlangenmensch schon angesprochen hat:
Versuche möglichst modular zu arbeiten, die Klassen möglichst klein zu halten, die Klassen möglichst autark zu halten ( wenige Abhängigkeiten zu anderen Klassen ).
Death-Tests mache ich eigentlich gar nicht. Assertions verwende ich ebenfalls nicht. Ich versuche möglichst wenig mit Pointern zu arbeiten und stattdessen smartpointer, oder im Idealfall gar objekte direkt mittels std::move zu transferieren. D.h. Pointergültigkeit ist bei mir daher kein großes Thema. An Stellen, die kritisch sind, verwende ich stattdessen exceptions. Die lassen sich mit GTEST auch gut abchecken.
Bedenke, dass du solche Fehlerfälle in deinen Unittests gezielt provozierst und sicherstellst, dass die Exception auch wirklich fliegt.Und hänge dich nicht zu sehr an der CodeCoverage auf. 100% ist nice, aber muss nicht in allen Projekten unbedingt sein. Meine APIs, die ich firmenintern als Bibliothek anbiete liegen meist bei so 90-95%. Andere komplette Backends liegen bei wesentlich weniger. 100% Codecoverage garantiert nicht, dass keine Fehler im Code sind.
-
@SoIntMan sagte in UnitTests (GoogleTests) and ASSERT:
ASSERT würde ich dann nur verwenden wenn bswp. der "Self, this, Ptr" für eine methode null ist!?
Achte bitte auf die unterschiedlichen Definitionen von Assertions.
Das C++
assert()
nutze ich zur Überprüfung meiner Annahmen. Ein Beispiel:assert(std::isfinite(v));
Per Definition schlägt die Assertion nur im Debug Modus an. Etwas ähnliches gilt auch für_ASSERT()
.In meinen Testreihen nutze ich
Microsoft::VisualStudio::CppUnitTestFramework
und hierbei nutze ich unter anderemAssert::IsTrue
. Und das schlägt sowohl im Debug und Release Modus an.
-
@SoIntMan
Noch etwas allgemeines:Testreihen haben einen einschränkenden Charakter. Tritt ein Fehler auf, so nehme ich diesen immer in die Testreihe auf, s.d. in den nächsten Versionen dieser nicht mehr auftritt. Dadurch sammelt sich im Laufe der Zeit einige Fehler in den Testreihen, aber auch ein paar kuriose Fälle.
Ferner spielt auch der Programmierstil eine Rolle. Gewisse Stile sind fehleranfällig. Wie @It0101 versuche ich z.B. auch möglichst ohne Pointern zu arbeiten. Erst neulich flog mir Code der folgenden Form um die Ohren:
#include <string> #include <cstdio> class Coord { public: double x, y; Coord(double mx, double my) : x(mx), y(my) { } }; class Coord2 : public Coord { public: std::string ID; Coord2(double mx, double my, const std::string& s) : Coord(mx, my), ID(s) { } }; void Out(const Coord* L, int Count) { for (int i = 0; i < Count; i++) { printf("%i: %.2f / %.2f\n", i, L[i].x, L[i].y); } printf("\n"); } //void Out2(const Coord2* L, int Count) //{ // for (int i = 0; i < Count; i++) // { // printf("%i: %.2f / %.2f\n", i, L[i].x, L[i].y); // } //} int main() { const Coord L[] = { {1, 2}, {3, 4} }; const Coord2 L2[] = { {1, 2, "P1"}, {3, 4, "P2"}}; Out(L, 2); Out(L2, 2); // Autsch! //Out2(L2, 2); return 0; }
Wo ist der Fehler?
-
Oh, ist ja im C Subforum... damit entfallen natürlich Exceptions.
@Quiche-Lorraine Der ist schön
-
@Quiche-Lorraine sagte in UnitTests (GoogleTests) and ASSERT:
Testreihen haben einen einschränkenden Charakter. Tritt ein Fehler auf, so nehme ich diesen immer in die Testreihe auf, s.d. in den nächsten Versionen dieser nicht mehr auftritt.
Das nennt sich "Regressionstest". Du findest einen Fehler, baust zuerst einen Unitttest, der diesen Fehler provoziert ( und somit fehlschlägt ), und danach behebst du den Fehler, wodurch der Unittest dann sauber durchläuft. Ein sehr wichtige Technik, wie ich finde.