Ich habe eine Familie von Datenstrukturen, die mit boost :: serialization von einer Ebene zu einer anderen übergeben werden sollten. Zum Beispiel
%Vor%Ich möchte Unit Tests dazu schreiben, nur um sicherzugehen, dass ich einige Felder nicht übersehen habe (es gibt viele Strukturen und Felder).
Das Problem ist, wenn ich ein neues Feld in der Struktur hinzufüge (ich werde es auf jeden Fall tun) und vergessen, den Komponententest zu aktualisieren, wird dieses Feld nicht vom Komponententest abgedeckt.
Meine Frage ist: Wie erkennt man, dass diese Struktur (oder Klasse) geändert wurde? Meine Idee war, static_assert (sizeof (DataType1) == HARD_CODED_VALUE) zu verwenden, aber es leidet an unterschiedlichen Strukturgrößen in verschiedenen Compilern, Plattformen (x64, x86) und Konfigurationen (release, debug).
Irgendeine gute Idee, wie man damit umgeht?
Das Problem ist, wenn ich ein neues Feld in der Struktur hinzufüge (ich werde das auf jeden Fall tun) und vergessen, den Komponententest zu aktualisieren, wird dieses Feld nicht vom Komponententest abgedeckt.
Meine Frage ist: Wie erkennt man, dass diese Struktur (oder Klasse) geändert wurde?
Meine Idee war, static_assert (sizeof (DataType1) == HARD_CODED_VALUE) [...]
zu verwenden
Das ist keine tragbare Lösung (wie Sie selbst bemerkt haben).
Irgendeine gute Idee, wie man damit umgeht?
Ja: Können Sie mit der Aktualisierung des Tests beginnen?
Das heißt, nicht entscheiden, was in der Struktur gehen soll, dann fügen Sie es hinzu, dann aktualisieren Sie die Tests (wenn Sie nicht vergessen).
Aktualisieren Sie stattdessen die Tests, um nach den neuen serialisierten Daten zu suchen, stellen Sie dann sicher, dass die aktualisierten Tests fehlschlagen, und aktualisieren Sie erst dann den Code, damit die Tests bestanden werden.
Dieser Ansatz (Einheitstest zuerst schreiben / aktualisieren) wurde (teilweise) erstellt, um genau dieses Problem anzugehen.
Der Test-First-Ansatz hat noch weitere Vorteile:
es vermeidet geschickt YAGNI
minimiert die vorzeitige Optimierung
es entwickelt sich auf natürliche Weise zur Überwachung der Funktionsvollständigkeit Ihrer Anwendung / Implementierung.
Fügen Sie der Klassendefinition einen Kommentar hinzu, um Sie daran zu erinnern, dass Sie beim Hinzufügen von Mitgliedern den Serializer optimieren müssen. Es gibt Grenzen, was ein Computer für Sie tun könnte - deshalb ist Code Review wichtig. Lassen Sie alle Patches von einem anderen Programmierer überprüfen, haben Sie eine Reihe strenger Testfälle und hoffen Sie auf das Beste.
Ich bin sicher, du könntest z.B. schreibe ein clang-plugin, das sicherstellt, dass eine bestimmte Methode auf jedes Mitglied einer Struktur verweist, aber brauchst du das wirklich und kannst du deine Zeit darauf verwenden?
Das heißt, Sie haben Bonuspunkte, um so viel Arbeit wie möglich an den Computer auszulagern. Sogar der static_assert
Trick ist ein guter. Wenn Sie es mit einer Menge von #ifdef
s für eine bestimmte ABI und Architektur schützen, wo Sie oft genug bauen, könnte es eine gute Arbeit leisten.
Sie können Ihren Strukturen einfach die statische Variable "version" hinzufügen und sie erhöhen, wenn sich die Struktur ändert.
%Vor%Dann schreibe in deinen Tests einfach
%Vor%Aber Sie können immer noch vergessen, die Version zu aktualisieren, wenn Struktur ändern, oder vergessen, einige der neuen Mitglieder hinzuzufügen, wenn der Test aktualisiert wird.
Ich hatte ein ähnliches Problem und ich fand meine Lösung mit boost :: fusion. Hier können Sie über alle Mitglieder Ihrer Struktur iterieren. Man muss also nicht mehr manuell vorgehen. Außerdem bekommst du das nette Feature der Kompilier-Introspektion. So ist es einfach, den Inhalt der gesamten Struktur, z. in eine Protokolldatei mit einem liitle Vorlagencode.
Eine Craay-Methode besteht darin, Mitglieder nicht direkt zu verwenden.
Erstellen Sie eine aggregierte Variant-Vorlage. Erstellen Sie eine Datenelementvorlage.
Die Datenelementvorlage verwendet eine Tag-Struktur.
Überschreiben Sie data_member<tag,T>::operator^( tag )
, um einen Verweis auf T
zurückzugeben. Mqybe macht das gleiche für die freie operator^( data_member< tag, T >*, tag )
Jetzt können Sie das Mitglied über this^tag()
erhalten, was wie ein Mitgliederzugriff aussieht. Wenn Sie eine globale Instanz von tag
erstellen, können Sie sogar ()
entfernen.
Sie haben auch eine Reflektion der Kompilierungszeit für Ihre Datenmitglieder. Sie können also for_each_member
schreiben und Ihren gesamten Serialisierungscode einmal schreiben und für jedes struct
verwenden.
Zugriffskontrolle und andere Kategorien von data_member
können in der aggregate
Vorlage vorgenommen werden.
Tag-basierte Konstruktion von Daten kann mit einem komplexen und ausgefallenen aggregate
-Konstruktor gemacht werden.
Alternativ können Sie darauf warten, dass in C ++, wahrscheinlich innerhalb des Jahrzehnts, echte Reflektion auftaucht.
Alternativ können Sie Ihren struct
in einen tuple
-Wrapper umwandeln und etwas wie den obigen Override-Trick verwenden, um this^tag
für einen namenbasierten Zugriff zu verwenden.
Wenn wir struct foo
mit int x, y
und double d
haben, mit denen wir das tun möchten, könnten wir Folgendes tun:
Ein (schwerwiegendes) Problem besteht darin, dass zwei Membervariablen in zwei verschiedenen struct
s denselben "Namespace" und denselben Konflikt haben, wenn sie denselben Namen haben.
Tags und Links unit-testing c++