Testen auf dem Zeitstrahl
- Autor
- Rolf Wenger
- Thema
- Softwareentwicklung
- Lesezeit
- 5-7 Minuten
Abstrakt
Testen des selber geschriebenen Codes gehört zu den Pflichten eines jeden Softwareentwicklers oder Softwareentwicklerin. In den weitaus meisten Fällen ist das eine Fleissaufgabe, die Tests hervorbringt, die in wenigen Millisekunden oder Sekunden ausgeführt sind und aufzeigen, ob der Code korrekt arbeitet oder nicht. Was aber wenn die Software selber Minuten, Stunden oder Tage braucht um das zu tun, was getestet werden soll. Dieser Artikeln beschreibt für viele lange laufende Vorgänge einen Lösungsansatz: Die manipulierbare Zeitquelle.
Beschreibung der Herausforderung
Es gibt immer wieder softwaretechnische Anforderungen, die eine Vorgang, eine Bearbeitung in bestimmten Zeitabständen vornehmen muss oder bei denen ein Objektzustand aufgrund der Zeit in einen andern Status versetzt werden muss. Sobald dabei die Zeitabstände grösser als wenige Sekunden sind, ist das Testen eine Herausforderung, da man unter Umständen nicht so lange warten will oder muss, bis das Testresultat vorliegt. Als Beispiel stellen Sie sich eine Software vor, die in frei definierbaren Zeitabständen mit definierbaren Ausnahmen einen Vorgang auslösen muss. Wie können Sie sicherstellen, dass der 29. Februar in den einzelnen Jahren korrekten behandelt wird, oder ob der Vorgang wirklich jeden 2. Tag um 02:30 korrekt ausgeführt wird, insbesondere auch in der Nacht der Sommerzeit- und Winterzeitumstellung.
Lösungsansatz
Als erstes kommt man hier oft auf den Gedanken, dass man während des Tests die Realzeituhr des Testrechners während dem Testen verändert und so an der Zeitachse rumschraubt um der Software glaubhaft zu machen, dass die Zeit nun vorgerückt oder zurückgestellt ist. In der heutigen technischen Auslegung von Computern und Netzwerken ist das allerdings eine sehr schlechte Idee, weil das Betriebssystem selber auch die Realzeit für den Abgleich mit andern Rechnern benutzt. Zudem kommt die Gefahr, dass im Fall eines Absturzes des Tests, die Realzeit nicht mehr korrekt eingestellt werden kann, was schlussendlich dazu führt, dass der Computer instabil und die Verbindungen zur Aussenwelt gestört werden kann.
Aufgrund der Erkenntnis aus dem ersten Abschnitt dieses Kapitels, muss eine andere flexible Lösung gefunden werden. Der Lösungsansatz muss dabei folgende Anforderungen erfüllen:
- Die Lösung soll eine logische Softwarezeit bilden, die standardmässig an die Realzeit gekoppelt ist.
- Der programmiertechnische Zugriff auf diese Zeit, soll möglichst kompatibel zum Zugriff auf die Realzeit erfolgen.
- Die Genauigkeit der logischen Zeit muss der Realzeit des Computers entsprechen.
- Die logische Zeit muss mittels Software manipuliert werden können, ohne dass das die Realzeit des Computers beeinflusst wird.
- Das Verschieben der logischen Testzeit muss in die Vergangenheit und in die Zukunft möglich sein.
- Der Zugriff auf die logische Softwarezeit muss im gesamten Prozessraum aus beliebig vielen Threads möglich sein.
Abbildung
Die nachfolgende Abbildung zeigt das Prinzip. In der oberen Bildhälfte ist ein Test auf dem Zeitstrahl gezeichnet der direkt auf die Realzeit zugreift. Der Test ohne Manipulation der Zeit läuft in diesem Fall 6 Stunden. Die untere Hälfte der Abbildung zeigt die Verwendung einer logischen Uhr. Der Test greift nun auf diese Uhr zu und manipuliert sie wenn notwendig (rote Pfeile). In der Abbildung ist der Effekt aus darstellungstechnischen Gründen nicht sehr gross: In der Praxis konnten wir so Tests von einer theoretischen Laufzeit von mehreren Jahren auf wenige Sekunden reduzieren.

Umsetzung
Für die Umsetzung einer logischen Uhr im Prozessraum erstellen wir eine Klasse nach dem Muster des «Singleton». Damit erreichen wir, dass die logische Uhr aus dem ganzen Prozessraum von jeglichen Threads erreicht werden kann. Zwei einfache Eigenschaften die Klasse mit der Möglichkeit die aktuelle Zeit als Standardzeit und Lokalzeit abgefragt werden können. Die Implementierung der Eigenschaften greift dabei auf die Realzeit zu. Damit implementiert die Klasse das weitere Muster der «Fassade». Zur Lösung erster Näherung fügen wir nun noch eine Methode für die Definition einer Zeitdifferenz hinzu und ergänzen die beiden Eigenschaften so, dass die definierte Zeitdifferenz mit der Realzeit verrechnet wird. Selbstverständlich ist dabei die standard Zeitdifferenz 0.
Code
Der untenstehende Code zeigt eine einfachste Implementierung der logischen Zeitquelle mit der grundlegenden Funktionalität. Selbstverständlich kann diese Klasse fast beliebig mit weiteren Funktionalitäten wie Handhabung von relativen oder absoluten Offsets, Reset des Offsets und Möglichkeiten für das getaktete Verschieben mit einem Offset ergänzt werden.
// Implements the logical application time source.
public sealed class TimeSource
{
// Holds the singleton instance.
private static TimeSource Instance = new TimeSource();
// Holds the current time offset.
private TimeSpan timeOffset;
// Holds the synchronization root of this type.
private readonly object SyncRoot = new object();
// Gets the singleton instance.
public static TimeSource Current => Instance;
// Gets the currently set offset.
public TimeSpan Offset
{
get
{
lock (SyncRoot)
{
return timeOffset;
}
}
}
// Gets the current local time.
public DateTime Now
{
get
{
lock (SyncRoot)
{
return DateTime.Now + Offset;
}
}
}
// Gets the current universal time.
public DateTime UtcNow
{
get
{
lock (SyncRoot)
{
return DateTime.UtcNow + Offset;
}
}
}
// Implements the Singleton Pattern.
private TimeSource()
{
}
// Allows setting the time offset.
public void SetTimeOffset(TimeSpan offset)
{
lock (SyncRoot)
{
timeOffset = offset;
}
}
}
Das nächste Codebeispiel zeigt eine einfache Anwendung der logischen Zeit in dem mit einer Schleife die logische Zeit jeweils um einen Tag in die Zukunft verschoben wird. Dabei wird die gesamte Ausführungszeit gemessen und am Schloss ausgegeben.
// Test the time source.
static void Main(string[] args)
{
// Start stopwatch to measure time used for testing.
Stopwatch stopwatch = Stopwatch.StartNew();
// simulate 7 days.
for (int days = 1; days < 8; days++)
{
// Write the current local time.
Console.WriteLine(TimeSource.Current.Now);
// Set the offset of the time.
TimeSource.Current.SetTimeOffset(TimeSpan.FromDays(days));
}
// Write the time used for testing.
Console.WriteLine();
Console.WriteLine($"Test used {stopwatch.Elapsed}");
}
Ausgaben
Die beiden obenstehenden Codes erzeugen diese Ausgaben.
Beim Einsetzen einer logischen Uhr in der Software ist darauf zu achten, dass alle Zugriffe auf die Zeit in der nutzenden Software via der logischen Uhr vorgenommen werden. Wird dieses Prinzip verletzt, untergraben Sie die eigenen gewaltigen Möglichkeiten, die sie mit der logischen Uhr schaffen.
