CMI-Testdaten-Generator
Repo: cmi-metatool
Projekt: CMI.Workspaces.Cmiaxioma.DataGenerator
Der Generator dient zur Erstellung von Testdaten und deren Abhängigkeiten anhand von Generationen, die mittels Json konfiguriert werden.
Inhaltsverzeichnis
- Übersicht der Testdaten-Generatorarten
- Feldgeneratoren
- Objektgeneratoren
- JSON Struktur
- Abhängigkeiten zwischen Feldgeneratoren
- Generator-Strategien
- Test Json und Generator
- Fehlercodes
- Beispiel Code
- Basis Klassen
- Implementierung
Übersicht der Testdaten-Generatorarten
Es gibt zwei Unterschiedliche Arten von Testdaten Generatoren. Es gibt einmal die Feld-Generatoren und die Objekt-Generatoren. Beide Generatoren haben unterschiedliche Aufgaben. Jeder Generator hat eine Priorität, der Generator mit der höchsten Priorität wird ausgeführt. Generatoren haben Konfigurationsobjekte. Diese werden beim Generieren der Daten mitgegeben und können Pro Generator Generisch mitgegeben werden. Damit ist es möglich jeden Generator komplett frei im JSON zu konfigurieren.
Feldgeneratoren
Die Aufgabe eines Feldgenerators ist es, Anhand eines Seeds zufällige Feldwerte für bestimmte Feldtypen zu generieren. So kann es einen Feld-Generator geben für das Generieren von Datumsfeldern. Will man aber etwas wie Vornamen Generieren muss das ebenfalls möglich sein. In einem Feldgenerator können andere Feldgeneratoren verwendet werden.
Objektgeneratoren
Ein Objektgenerator wird, nachdem alle Felder befüllt wurden, ausgeführt um Geschäftslogik Relevante dinge zu setzten. So kann beispielsweisse sichergestellt werden, dass der Beginn auf dem Geschäft vor dem Ende ist.
JSON Struktur
Jede JSON Konfiguration besteht aus einem Objekt mit einem Feld Seed und einem Array von Objektgeneratoren die immer mindestens einen Namen, einen Generator, eine Strategy und einen Amount beinhalten. Zusätzlich kann jeder Objektgenerator noch mit Fields ausgestattet werden.
Eigenschaften | Beschreibung |
Name* | Der Name ist wie ein Variablen Namen zu verstehen. Damit muss er eindeutig sein und sollte den gleichen Konventionen entsprechend wie ein C# Variablen Name. |
Generator* | Mit Generator ist der im Code hinterlegte Name eines Testdaten-Generators gemeint. Auch dieser Name muss eindeutig sein |
Strategy* | Hiermit wird die Strategie definiert, unter welchen Bedingungen Objekte neu erstellt werden. Siehe dazu Generator-Strategien |
Amount* | Amount ist eine string expression anhand dessen eine Fixe oder eine zufällige Anzahl an Objekten generiert wird. So werden mit dem Wert <100> 100 Objekte generiert. Alternativ kann auch <10 - 1000> eingetragen werden, da werden dann Anhand des Seeds zwischen 10 und 1000 Objekte erstellt. |
TypeDefinition | Die TypeDefinition ist kein Pflichtfeld. Jeder Generator braucht eine Typdefinition, in der Regel ist diese aber im Generator selbst definiert. Ist diese das nicht, kann sie über dieses Feld mitgegeben werden. |
Fields |
Fields können optional angegeben werden. Felder werden immer durchgegangen und generiert durch das Framework.
Jeder Objektgenerator kann die Felder, die so gesetzt werden, überschreiben. Es gibt zwei Möglichkeiten Felder anzugeben.
Die erste Möglichkeit ist, einfach den Namen der Felder zu schreiben. Damit wird der Standardfeldgenerator für diesen Feldtypen gesucht. Falls ein Feldgenerator für dieses spezifische Feld hinterlegt ist, wird dieser verwendet. Alternativ kann im Fields Array ein Json Objekt angegeben werden, mit dem für normale Felder einfache Generator Namen angegeben werden können:
Alternativ kann im Fields Array ein Json Objekt angegeben werden, mit dem für normale
Felder einfache Generator Namen angegeben werden können:
Will man Assoziationsfelder angeben kann man das ebenfalls über ein Objekt machen, dort
gibt man eine Referenz auf einen anderen Objektgenerator an. Es wird bei Generator also
nicht der Name des Generators angegeben, sondern der Name, der einem Generator in der
Json Datei weiter oben deklariert wurde.
|
*Obligatorisch Zusätzlich zu diesen Feldern kann bei jedem Generator eine Subklasse der Generator Klasse mitgegeben werden. Somit ist es möglich, bei jedem Generator noch zusätzliche Generator-Spezifische Felder hinzuzufügen. Das gilt auch für Field Konfigurationen, wenn es sich nicht um ein Assoc Feld handelt.
Abhängigkeiten zwischen Feldgeneratoren
Es ist sehr gut möglich das Feldgeneratoren und Objektgeneratoren voneinander abhängig sind. Das ist in der aktuellen Struktur bedacht. Es gibt keine Möglichkeit explizit zu sagen, welcher Generator von einem anderen abhängt, stattdessen wird anhand der effektiven Reihenfolge in der JSON Datei ausgeführt. Das gleiche gilt für Einträge in Feldern. Auch dort gilt das was zuerst im Array ist, wird als erstes ausgeführt.
Beispiel
{
"seed": "someSeed",
"generators": [
{
"name": "adressGenerator1",
"generator": "AdresseGenerator",
"strategy": "on_reference_new",
"amount": "1",
"fields": [
{
"name": "Strasse",
"generator": "StrasseGenerator"
},
{
"name": "Ort",
"generator": "OrtGenerator"
},
{
"name": "PLZ",
"generator": "PlzGenerator"
},
{
"name": "GueltigVonBis",
"generator": "GueltigVonBisGenerator"
}
]
},
{
"name": "benutzerGenerator",
"generator": "Kontakt",
"strategy": "new",
"amount": "1",
"fields": [
{
"name": "Geburtsdatum",
"generator": "GeburtsdatumGenerator"
},
{
"name": "Adressen",
"generator": "adressGenerator1"
}
]
}
]
}
Generator-Strategien
Das Ziel der Generator-Strategie ist es, den Grad der wieder Verwendbarkeit der erstellen Objekte zu bestimmen. So will man Beispielsweisse das Adressen nur dann erstellt werden, wenn diese für einen Kontakt gebraucht werden. Andersrum kann es sein, dass man will, das Kontakte, die erstellt wurden, nur einmal neu erstellt werden, und jedes Mal, wenn diese Referenziert werden, werden die gleichen Kontakte genommen. Das Ganze soll mit dem Strategy-Pattern umgesetzt werden, so dass es einfach ist, neue Strategien hinzuzufügen. Dabei ist aber zu beachten, dass in der JSON Datei bestimmt welche Strategie verwendet werden soll. Die Strategien werden wie die Generatoren in allen Workspace Projekten gesucht und automatisch registriert. Somit muss man nur eine neue Strategie Implementieren, und kann diese danach direkt im JSON verwenden.
Strategy | Beschreibung |
---|---|
new | Sobald der Generator auf diese Strategie stosst wird diese automatisch die gewünschten Objekte erzeugen. Wird irgendwo auf einen Generator mit dieser Strategie referenziert, werden die Objekte erneut erstellt und verbunden. |
on_reference_new | Diese Strategie macht nichts, wenn sie initial gefunden wird. Sobald in einem anderen Generator der Generator mit dieser Strategie aufgerufen wird, werden die Objekte neu erstellt, und das jedes Mal, wenn dieser Referenziert wird. |
Test Json und Generator
Im Rich Client im Debug Modus gibt es die Möglichkeit das Json wie auch den Generator zu testen. Hierzu gibt es ein Menue.
Dort kann ein Json Datei geladen werden und durch den Button generieren wird das Json validiert der Generator ausgeführt. Im Fehlerfall wird ein Fehlercode angezeigt.
Fehlercodes
Fehlercode | Beschreibung |
---|---|
TDG700 | Es wurde bei einem Generator Objekt kein Generator angegeben. |
TDG001 | Der angegebene Generator existiert nicht. |
TDG720 | Die in der JSON Datei angegebene TypeDefinition existiert nicht. |
TDG710 | Es wurde keine Typdefinition beim Generator und in der JSON Datei hinterlegt. |
TDG086 | Es wurde ein String angegeben für ein Assoc Feld. |
TDG016 | Objekt im Felder Array hat keinen Namen. |
TDG015 | Objekt im Felder Array hat keinen Generator. |
TDG005 | Es wurde ein Generator referenziert, bei dem der JSON Resolver noch nicht angekommen ist. |
TDG006 | Im Feld Array wurde ein nicht Objekt und nicht string angegeben. |
TDG004 | Es wurde ein Feld referenziert was im Model nicht gefunden wurde. |
TDG002 | Es wurde eine Strategie angegeben, die nicht existiert. |
TDG456 | Der Name des Generators wird doppelt verwendet. |
TDG058 | Es wurde dasselbe Feld mehrmals angegeben. |
TDG505 | Es wurde kein Amount angegeben. |
TDG504 | Es wurde eine negative Anzahl als Amount angegeben. |
TDG500 | Amount enthält illegale Zeichen. |
TDG501 | Es wurde eine invalide Anzahl an Bindestrichen im Amount angegeben. |
TDG502 | Ein Teil des Amount string konnte nicht konvertiert werden. |
TDG503 | Min Amount ist grösser als Max Amount. |
Beispiel Code
Als Orientierung um weitere Generatoren zu entwickeln, soll diese Codebeispiel helfen.
Welche Generatoren bereits vorhanden sind, muss im Repo im Projekt geprüft werden.
Basis Klassen
Die Basis Klassen dienen nur als Poco und enthalten keine Logik, um doppelten Code zu vermeiden.\ Grundlegende Daten der Generatoren (Field und Objekt) sind in der Basis Klasse DataGeneratorBase.\ Die jeweiligen Generator spezifischen Eigenschaften für Field- oder ObjektDataGenerator besitzen Ihre eigenen Basis Klassen.
DatageneratorBase
public abstract class DataGeneratorBase
{
public string Name { get; protected set; }
public Type ConfigType { get; protected set; }
public int Priority { get; protected set; } = 0;
public IEnumerable<string> Depends { get; protected set; } = [];
public bool IsDefault { get; protected set; } = false;
}
FieldDataGeneratorBase
public abstract class FieldDataGeneratorBase : DataGeneratorBase
{
public FieldDefinition FieldDefinition { get; set; }
public Type ValueHoldeType { get; protected set; }
protected readonly IRandomFactory randomFactory;
protected readonly IDataGeneratorHelper dataGeneratorHelper;
public FieldDataGeneratorBase(IRandomFactory randomFactory, IDataGeneratorHelper dataGeneratorHelper)
{
this.randomFactory = randomFactory;
this.dataGeneratorHelper = dataGeneratorHelper;
ConfigType = typeof(FieldDataGeneratorConfiguration);
}
}
ObjectDataGeneratorBase
public abstract class ObjectDataGeneratorBase : DataGeneratorBase
{
public TypeDefinition TypeDefinition { get; protected set; }
protected ObjectDataGeneratorBase()
{
ConfigType = typeof(ObjectDataGeneratorConfiguration);
}
}
Implementierung
Als Beispiel Implementierung nehmen wir ein Kontakt mit Vor-, Nachname sowie Email Adresse.
Grundsätzlich muss zunächst ein Objektgenerator angelegt werden, da die Felder Vor- und Nachname durch Fieldgeneratoren erzeugt werden sollen.\ Die Email besteht aus diesen Feldern.
Objektgenerator KontaktGenerator
public class KontaktGenerator : ObjectDataGeneratorBase,
IObjectDataGenerator
{
private readonly IRandomFactory randomFactory;
private readonly IDataGeneratorHelper dataGeneratorHelper;
public KontaktGenerator(MetaModel metaModel, IRandomFactory randomFactory, IDataGeneratorHelper dataGeneratorHelper)
{
Name = "KontaktMitMailAnhandDesNamens";
TypeDefinition = metaModel.TypeDefinitions.FindBySchluessel("Kontakt");
this.randomFactory = randomFactory;
this.dataGeneratorHelper = dataGeneratorHelper;
}
public void Generate(string seed, IBaseConfiguration config, WorkspaceObject workspaceObject)
{
var kontakt = dataGeneratorHelper.GetSpecificWorkspaceObject<Kontakt>(workspaceObject);
var domain = new ResourceLoader(randomFactory).GetRandom(seed, "Domains.txt");
kontakt.Email = kontakt.Vorname + "." + kontakt.Name + "@" + domain;
}
}
\ Kommen wir zu den Fieldgeneratoren Vor- und Nachname.\ Es wurden zwei separate Generatoren entwickelt, um anhand einer vorgegebenen Liste plausible Namen zu erzeugen.
Fieldgenerator VornamenGenerator
public class VornamenGenerator : FieldDataGeneratorBase,
IFieldDataGenerator
{
public VornamenGenerator(MetaModel metaModel, IRandomFactory randomFactory, IDataGeneratorHelper dataGeneratorHelper)
:base(randomFactory, dataGeneratorHelper)
{
Name = "Vornamen";
ValueHoldeType = typeof(TextValueHolder);
FieldDefinition = metaModel.TypeDefinitions.FindBySchluessel("Kontakt").Felder.FindByKey("Vorname");
}
public void Generate(string seed, IBaseConfiguration config, WorkspaceObject workspaceObject)
{
var fieldDefinition = dataGeneratorHelper.GetSpecificFieldDefinition<TextFieldDefinition>(FieldDefinition);
var valueHolder = workspaceObject.Universal.TextFields[fieldDefinition];
valueHolder.Value = new ResourceLoader(randomFactory).GetRandom(seed, "Vornamen.txt");
}
}
Fieldgenerator NamenGenerator
public class NamenGenerator : FieldDataGeneratorBase,
IFieldDataGenerator
{
public IRandomFactory RandomFactory => randomFactory;
public NamenGenerator(MetaModel metaModel, IRandomFactory randomFactory, IDataGeneratorHelper dataGeneratorHelper)
:base(randomFactory, dataGeneratorHelper)
{
Name = "Namen";
ValueHoldeType = typeof(TextValueHolder);
FieldDefinition = metaModel.TypeDefinitions.FindBySchluessel("Kontakt").Felder.FindByKey("Name");
}
public void Generate(string seed, IBaseConfiguration config, WorkspaceObject workspaceObject)
{
var fieldDefinition = dataGeneratorHelper.GetSpecificFieldDefinition<TextFieldDefinition>(FieldDefinition);
var valueHolder = workspaceObject.Universal.TextFields[fieldDefinition];
valueHolder.Value = new ResourceLoader(randomFactory).GetRandom(seed, "Namen.txt");
}
}
Json
Im Jason können wir nun definieren was mit welchen Generatoren und mit welchen Bedingungen generiert werden soll.
{
"seed":"someSeedtoTest",
"generators":[
{
"name":"benutzerGenerator",
"generator":"KontaktMitMailAnhandDesNamens",
"strategy":"new",
"amount":"1",
"fields":[
{
"name":"Name",
"generator":"Namen"
},
{
"name":"Vorname",
"generator":"Vornamen"
}
]
}
]
}