wake-up-neo.com

Komponententest c ++. Wie teste ich private Mitglieder?

Ich möchte Unit-Tests für meine C++ - Anwendung durchführen.

Was ist das richtige Formular, um private Mitglieder einer Klasse zu testen? Eine Freundesklasse erstellen, in der die privaten Mitglieder getestet werden, eine abgeleitete Klasse oder einen anderen Trick verwenden?

Welche Technik verwenden die Test-APIs?

36
Daniel Saad

Normalerweise testet man nur die öffentliche Schnittstelle, wie in den Kommentaren der Frage beschrieben. 

Es gibt jedoch Zeiten, in denen es hilfreich ist, private oder geschützte Methoden zu testen. Beispielsweise kann die Implementierung einige nicht triviale Komplexitäten aufweisen, die für Benutzer verborgen sind und die mit Zugriff für nicht öffentliche Mitglieder genauer getestet werden können. Oft ist es besser, einen Weg zu finden, um diese Komplexität zu beseitigen oder herauszufinden, wie die relevanten Teile öffentlich zugänglich gemacht werden können, jedoch nicht immer.

Eine Möglichkeit, den Zugriff von Komponententests für nicht öffentliche Mitglieder zuzulassen, ist über das Konstrukt friend .

36
Mr Fooz

Die Beantwortung dieser Frage berührt viele andere Themen. Neben jeglicher Religiosität in CleanCode, TDD und anderen:

Es gibt verschiedene Möglichkeiten, auf private Mitglieder zuzugreifen. In jedem Fall müssen Sie den getesteten Code überschreiben! Dies ist auf beiden Ebenen der Analyse von C++ (Präprozessor und Sprache selbst) möglich:

Definiere alles für die Öffentlichkeit

Mit dem Präprozessor können Sie die Kapselung unterbrechen.

#define private public
#define protected public
#define class struct

Der Nachteil ist, dass die Klasse des gelieferten Codes nicht dieselbe ist wie im Test ! Der C++ - Standard in Kapitel 9.2.13 sagt:

Die Reihenfolge der Zuordnung nicht statischer Datenelemente mit unterschiedlichen Zugriffskontrolle ist nicht angegeben.

Das bedeutet, dass der Compiler das Recht hat, die Member-Variablen und virtuellen Funktionen für den Test neu zu ordnen. Sie können sich bemühen, dass dies Ihren Klassen keinen Schaden zufügt, wenn kein Pufferüberlauf auftritt. Dies bedeutet jedoch, dass Sie nicht denselben Code testen, den Sie liefern. Das bedeutet, wenn Sie auf Mitglieder eines Objekts zugreifen, das durch Code initialisiert wurde, der mit private kompiliert wurde und nicht als public definiert ist, kann der Versatz Ihres Elements unterschiedlich sein!

Freunde

Diese Methode muss die getestete Klasse ändern, um sie mit der Testklasse oder der Testfunktion anzufreunden. Einige Testframeworks wie gtest (FRIEND_TEST(..);) verfügen über spezielle Funktionen, um diese Art des Zugriffs auf private Dinge zu unterstützen.

class X
{
private:
    friend class Test_X;
};

Es öffnet die Klasse nur für den Test und öffnet die Welt nicht, aber Sie müssen den Code ändern, der geliefert wird. Meiner Meinung nach ist dies eine schlechte Sache, da ein Test den getesteten Code niemals ändern sollte. Ein weiterer Nachteil besteht darin, dass andere Klassen des gelieferten Codes die Möglichkeit haben, Ihre Klasse durch Benennung wie eine Testklasse zu beeinflussen (dies würde auch die ODR-Regel des C++ - Standards beschädigen).

Die privaten Dinge für geschützt erklären und von der Klasse für Tests ableiten

Kein sehr eleganter Weg, sehr aufdringlich, funktioniert aber auch:

class X
{
protected:
    int myPrivate;
};

class Test_X: public X
{
    // Now you can access the myPrivate member.
};

Jeder andere Weg mit Makros

Funktioniert, hat jedoch die gleichen Nachteile bei der Standardkonformität wie der erste Weg. z.B.:

class X
{
#ifndef UNITTEST
private:
#endif
};

Ich denke, dass die letzten beiden Möglichkeiten keine Alternative zu den ersten beiden Möglichkeiten sind, da sie keine Vorteile gegenüber den ersten haben, aber den getesteten Code aufdringlicher machen. Der erste Weg ist sehr riskant, so dass Sie den befreundeten Ansatz verwenden können.


Einige Worte zur Nie-Test-Privatdinge-Diskussion. Der Vorteil von Unit-Tests ist, dass Sie sehr früh den Punkt erreichen, an dem Sie das Design Ihres Codes verbessern müssen. Dies ist manchmal auch einer der Nachteile von Unit-Tests. Dadurch wird die Objektorientierung manchmal komplizierter, als es sein muss. Vor allem, wenn Sie der Regel folgen, um Klassen auf dieselbe Weise wie die Objekte der realen Welt zu entwerfen.

Dann müssen Sie den Code manchmal in etwas Hässliches umwandeln, da Sie durch den Unit-Test-Ansatz dazu gezwungen werden. Ein Beispiel ist die Arbeit an komplexen Frameworks, mit denen physische Prozesse gesteuert werden. Dort möchten Sie den Code auf den physischen Prozess abbilden, da Teile des Prozesses oft bereits sehr komplex sind. Die Abhängigkeitsliste dieser Prozesse wird manchmal sehr lang. Dies ist ein möglicher Moment, in dem das Testen privater Mitglieder Nizza bekommt. Sie müssen sich mit den Vor- und Nachteilen jedes Ansatzes auseinandersetzen.

Der Unterricht wird manchmal komplexer! Dann müssen Sie sich entscheiden, sie zu teilen oder sie so zu nehmen, wie sie sind. Manchmal macht die zweite Entscheidung mehr Sinn. Letztendlich geht es immer darum, welche Ziele Sie erreichen möchten (z. B. perfektes Design, kurze Einarbeitungszeiten, niedrige Entwicklungskosten ...).


Meine Meinung

Mein Entscheidungsprozess für den Zugriff auf private Mitglieder sieht folgendermaßen aus:

  1. Müssen Sie private Mitglieder selbst testen? (Oft reduziert dies die Gesamtzahl der erforderlichen Tests)
  2. Wenn ja, sehen Sie einen Designvorteil, um die Klasse umzugestalten?
  3. Falls nicht, sollten Sie sich mit dem Test in Ihrer Klasse anfreunden (verwenden Sie dies aufgrund fehlender Alternativen).

Ich mag den befreundeten Ansatz nicht, weil er den getesteten Code ändert, aber das Risiko, etwas zu testen, das möglicherweise nicht das gleiche ist (wie beim ersten Ansatz möglich), rechtfertigt den Cleaner-Code nicht.

Übrigens: Das Testen nur der öffentlichen Schnittstelle ist auch eine fließende Angelegenheit, da sich diese Erfahrung meiner Erfahrung nach so oft ändert wie die private Implementierung. Sie haben also keinen Vorteil, den Test auf öffentliche Mitglieder zu reduzieren.

22
Stefan Weiser

Ich habe selbst keine goldene Lösung gefunden, aber Sie können friend verwenden, um private Mitglieder zu testen, wenn Sie wissen, wie das Testframework die Methoden benennt. Ich verwende Folgendes, um private Mitglieder mit Google-Test zu testen. Das funktioniert zwar ganz gut, aber es ist ein Hack, und ich verwende es nicht im Produktionscode.

In der Kopfzeile des Codes, den ich testen möchte (stylesheet.h), habe ich:

#ifndef TEST_FRIENDS
#define TEST_FRIENDS
#endif

class Stylesheet {
TEST_FRIENDS;
public:
    // ...
private:
    // ...
};

und im Test habe ich:

#include <gtest/gtest.h>

#define TEST_FRIENDS \
    friend class StylesheetTest_ParseSingleClause_Test; \
    friend class StylesheetTest_ParseMultipleClauses_Test;

#include "stylesheet.h"

TEST(StylesheetTest, ParseSingleClause) {
    // can use private members of class Stylesheet here.
}

Sie fügen immer eine neue Zeile zu TEST_FRIENDS hinzu, wenn Sie einen neuen Test hinzufügen, der auf private Mitglieder zugreift. Die Vorteile dieser Technik sind, dass sie im getesteten Code ziemlich unauffällig sind, da Sie nur einige #defines hinzufügen, die keine Auswirkungen haben, wenn Sie nicht testen. Der Nachteil ist, dass es in den Tests etwas ausführlich ist.

Nun noch ein Wort, warum Sie dies tun möchten. Im Idealfall haben Sie natürlich kleine Klassen mit klar definierten Verantwortlichkeiten, und die Klassen verfügen über leicht überprüfbare Schnittstellen. In der Praxis ist das jedoch nicht immer einfach. Wenn Sie eine Bibliothek schreiben, wird private und public von dem bestimmt, was der Benutzer der Bibliothek verwenden soll (Ihre öffentliche API). Sie können Invarianten haben, die sich sehr unwahrscheinlich ändern und getestet werden müssen, aber für den Benutzer Ihrer API nicht von Interesse sind. Dann reicht das Black-Box-Testen der API nicht aus. Auch wenn Sie auf Fehler stoßen und zusätzliche Tests schreiben, um Regressionen zu verhindern, kann es notwendig sein, private zu testen.

19
jdm

Der Wunsch, private Mitglieder zu testen, ist ein Design-Geruch, der im Allgemeinen darauf hinweist, dass in Ihrer Klasse eine Klasse gefangen ist, die nur schwer herauszukommen versucht. Die gesamte Funktionalität einer Klasse sollte durch ihre öffentlichen Methoden ausübbar sein. Funktionen, auf die nicht öffentlich zugegriffen werden kann, gibt es eigentlich nicht.

Es gibt einige Ansätze, um zu erkennen, dass Sie testen müssen, ob Ihre privaten Methoden das tun, was sie auf der Dose sagen. Freundenklassen sind die schlechtesten davon; Sie stellen den Test auf eine Art und Weise dar, die auf den ersten Blick fragil ist. Etwas besser ist die Abhängigkeitsinjektion: Die Klassenattribute der privaten Methoden werden als Klassenattribute festgelegt, aus denen der Test Mock-Up-Versionen liefern kann, damit private Methoden über die öffentliche Schnittstelle getestet werden können. Am besten extrahieren Sie eine Klasse, die das Verhalten Ihrer privaten Methoden als öffentliche Schnittstelle enthält, und testen Sie dann die neue Klasse wie gewohnt.

Weitere Informationen finden Sie unter Clean Code .

4
darch

Ungeachtet der Kommentare zur Angemessenheit des Testens privater Methoden, nehmen Sie an, Sie müssen dies wirklich tun ... Dies ist häufig der Fall, wenn Sie beispielsweise mit älterem Code arbeiten, bevor Sie ihn in etwas angemesseneres umwandeln. Hier ist das Muster, das ich verwendet habe:

// In testable.hpp:
#if defined UNIT_TESTING
#   define ACCESSIBLE_FROM_TESTS : public
#   define CONCRETE virtual
#else
#   define ACCESSIBLE_FROM_TESTS
#   define CONCRETE
#endif

Dann innerhalb des Codes:

#include "testable.hpp"

class MyClass {
...
private ACCESSIBLE_FROM_TESTS:
    int someTestablePrivateMethod(int param);

private:
    // Stuff we don't want the unit tests to see...
    int someNonTestablePrivateMethod();

    class Impl;
    boost::scoped_ptr<Impl> _impl;
}

Ist es besser als Testfreunde zu definieren? Es scheint weniger ausführlich als die Alternative zu sein, und es ist innerhalb der Kopfzeile klar, was passiert. Beide Lösungen haben nichts mit Sicherheit zu tun: Wenn Sie sich wirklich Gedanken über die Methoden oder Member machen, müssen diese in einer undurchsichtigen Implementierung versteckt sein, möglicherweise mit einem anderen Schutz.

3
mbells

Manchmal ist es erforderlich, private Methoden zu testen. Das Testen kann durch Hinzufügen von FRIEND_TEST zur Klasse durchgeführt werden.

// Production code
// prod.h

#include "gtest/gtest_prod.h"
...   

class ProdCode 
    {
     private:
      FRIEND_TEST(ProdTest, IsFooReturnZero);
      int Foo(void* x);
    };

//Test.cpp
// TestCode
...
TEST(ProdTest, IsFooReturnZero) 
{
  ProdCode ProdObj;
  EXPECT_EQ(0, ProdObj.Foo(NULL)); //Testing private member function Foo()

}
1
Syam Sanal

Es gibt eine einfache Lösung in C++, die #define verwendet. Wickeln Sie das Include Ihres "ClassUnderTest" einfach so ein: 

#define protected public
 #define private   public
    #include <ClassUnderTest.hpp>
 #undef protected
#undef private

[Credit geht an diesen Artikel und RonFox] [1]

0
Langley