Um den Komponententest durchzuführen, müssen umgebungsabhängige Elemente wie Geräte ersetzt / entfernt werden. Wenn diese Handhabungen nicht sorgfältig entworfen wurden, sind keine Unit-Tests möglich. In einer Reihe von Artikeln in diesem Titel werden Implementierungen zum Testen und Produzieren unter Verwendung von Entwurfsmustern betrachtet. In diesem Artikel werden wir uns mit dem Umgang mit im Speicher gespeicherten Daten befassen. Gleiches gilt jedoch für Geräte wie Netzwerke.
Letztendlich liegt der Fokus darauf, wie das Testmodul ausgetauscht werden kann. Diese Idee heißt Dependency Injection (DI). In DI wird der Kopplungsgrad zwischen Modulen über die Schnittstelle reduziert.
Das Beispielprogramm ist in C ++ geschrieben, aber auch C # und Python werden geschrieben.
26.04.2015 Python-Beispielcode hinzugefügt
Die Daten können alles sein, was das Verhalten der Anwendung bestimmt oder das Ergebnis des Verhaltens vermittelt. Sie können es lokal tun, aber es gibt Zeiten, in denen Sie es wirklich global verwenden möchten. Wie soll es aufbewahrt werden?
Im folgenden Beispiel wird eine Funktion angenommen, die ihr Verhalten abhängig von der Version des externen Moduls ändert.
//Vor der Einführung (nicht testbar oder schwierig)
void func() {
//Version herunterladen
int version = SomeModule::getMajorVersion();
if (version > 10) {
//Etwas mit der angegebenen Version zu tun
}
}
Hier wird angenommen, dass "SomeModule :: getMajorVersion ()" nur in einer bestimmten Umgebung verwendet werden kann oder es schwierig ist, den Status zu wechseln. Mit einem solchen Code ist es schwierig, die folgende if-Anweisung zu testen.
Erstellen Sie hier eine Dummy-Klasse, die "SomeModule" ersetzt, bereiten Sie eine Entität und einen Proxy vor, die auf den Dummy zugreifen, und machen Sie ihn testbar. Zunächst aus der Schnittstelle der Proxy-Klasse, die das Fenster ist.
class ModuleProxy {
virtual int getMajorVersion_() const = 0;
protected:
ModuleProxy() {}
public:
virtual ~ModuleProxy() {};
int getMajorVersion() const {
return this->getMajorVersion_();
}
};
Als nächstes kommt die reale Klasse und die Dummy-Klasse. Hier wird auch das ursprüngliche SomeModule ausgeblendet und die Verwendungsklasse erstellt.
//Klasse, die das externe Modul tatsächlich aufruft
class SomeModuleImpl : public ModuleProxy {
virtual int getMajorVersion_() const override {
return SomeModule::getMajorVersion();
}
};
//Dummy-Klasse zum Testen
class ModuleDummy : public ModuleProxy {
virtual int getMajorVersion_() const override {
return major_;
}
public:
ModuleDummy(int ver = 0)
: major_(ver)
{}
int major_;
};
Die Funktionsfunktion und der Testcode lauten wie folgt.
void func(ModuleProxy& module) {
//Version herunterladen
int version = module.getMajorVersion();
if (version > 10) {
}
}
void XXXTest::funcTest() {
ModuleDummy dummy(10);
{
func(dummy);
}
{
dummy.major_ = 11;
func(dummy);
}
}
Eine andere Möglichkeit, dies zu implementieren, ist die Verwendung des Monostate-Musters. Hier ist die Elementfunktion ebenfalls statisch, kann jedoch eine normale Funktion sein.
class Data {
static int majorVersion;
public:
static int getMajorVersion() const {
return majorVersion;
}
friend class XXXTest;
};
int Data::majorVersion = 1;
Die Anwendungsimplementierung, die dies verwendet, ist wie folgt.
void func() {
if (Data::getMajorVersion() > 10) {
}
}
Die Testseite sieht so aus.
void XXXTest::funcTest() {
{
Data::majorVersion = 10;
func();
}
{
Data::majorVersion = 11;
func();
}
}
Das Monostate-Muster ist einfach, aber Sie müssen über die Wertinitialisierung nachdenken. Wenn von anderen statischen Elementen verwiesen wird, muss in der Reihenfolge der Initialisierung vorsichtig vorgegangen werden.
Die Python-Version des Monostate-Musters ist unten dargestellt. Für die Methode wurde eine Klassenmethode angegeben.
class Data:
_majorVersion = 1
@classmethod
def getMajorVersion(cls) -> int:
return cls._majorVersion
Die Testseite sieht folgendermaßen aus:
import unittest
class Test_testSample(unittest.TestCase):
def test_version(self):
Monostate.Data._majorVersion = 10
self.assertFalse(Target.func())
Monostate.Data._majorVersion = 11
self.assertTrue(Target.func())
In diesem Artikel wurde DI mithilfe des Proxy-Musters realisiert. Wir haben auch gezeigt, wie man das Monostate-Muster einfacher verwendet.
Es ist wichtig, Verträge zu kennen, auch in Sprachen wie C ++ / C # / Python, die keine Vertragsfunktionalität unterstützen.
Da Monostate polymorph verwendet werden kann, ist es möglich, Proxy-Muster zu integrieren, um die Verwendung zu vereinfachen.
Es ist gut, friend im Komponententest zu verwenden, aber es fügt dem Header auf der Anwendungsseite einen Freund hinzu, was zu einer Neukompilierung führt. Überlegen Sie, wie Sie damit umgehen sollen.
Wenn Sie den Ressourcenzugriff mit shared_ptr implementieren, ist es möglicherweise besser, schwaches_ptr als IF des Proxys zu verwenden. Probieren Sie diese Verbesserungen aus, während Sie noch testbar sind.
Recommended Posts