Der Abschnitt Test
bietet explizite Unterstützung für das Testen der Implementierung. Siehe jedoch auch die Features zu Debug, Trace und Logging.
Einzelne Methoden am Baustein können attributiert werden, um anzugeben, dass diese Methode einen Unit-Test darstellt. Eine Unit-Test Methode darf keinen Rückkehrwert haben, sondern ist immer vom Typ void
. Zum Beispiel:
[ModuleUnitTest] private void CheckGenerics() { // check something }
Mit der Methode RunUnitTests
werden sämtliche derart attributierten Methoden ausgeführt.
protected override void OperationDurchfuehren() { // ...snip... #if DEBUG this.Test.RunUnitTests(); #endif }
Der Unit-Test gilt als erfolgreich, wenn keine Condition oder Exception
aufgeworfen wurde. Die Ausführungszeit wird bei einem InfoLevel > 1 im Monitor gezeigt:
User 33640 /5300 : Performance: AfBeispielDotnetDemo.CheckGenerics succeeded - 774ms
Außerdem wird die Ausführung in der Log-Datei mit Ursprung der Sourcecode-Datei und -Zeile festgehalten:
<LogEntry Date="2020-04-02 12:33:17" Severity="Performance" Source="D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs" Line="26" ThreadId="3"> <Message>AfBeispielDotnetDemo.CheckGenerics succeeded - 774ms</Message> </LogEntry>
Eine Methode kann mehrfach mit dem ModuleUnitTest
attributiert werden. Jedes Attribut steht für einen Testlauf, möglicherweise mit unterschiedlichen Bedingungen.
Wenn eine Methode mit einem oder mehreren ModuleUnitTest
-Attributen versehen ist, kann es eine Methode mit dem gleichen Namen wie dem der Testmethode und dem Suffix _prepare
geben, die vor der Ausführung jedes einzelnen Testlaufs der Testmethode ausgeführt wird. Ebenfalls kann es eine zur Testmethode passende Methode mit dem Suffix _cleanup
geben, die nach jedem jeweiligen Testlauf ausgeführt wird. Diese beiden Methoden werden nicht in der Zeitmessung und nicht in dem Task aufgenommen. Diese beiden Methoden können, müssen aber nicht vorhanden sein. Sie müssen jedoch die identische Signatur besitzen, also ebenfalls vom Typ void
sein und die gleichen Argumente bekommen. Beispiel:
[ModuleUnitTest(task: "NewList")] private void TNA_ObjList00_NewList() { const int count = 1000; for (var i = 1; i <= count; ++i) { var list = this.Data.NewList(DzugStrukturSnawa.Type); this.AssertIsNotNull(list); } } private void TNA_ObjList00_NewList_prepare() { // ensure datatype is prepared and not measured var list = this.Data.NewList(DzugStrukturSnawa.Type); this.AssertIsNotNull(list); }
Falls ein Unit-Test eine Condition oder Exception
aufwirft, oder der Arbeitsgang auf IsFault
gesetzt wurde, gilt der Test als nicht bestanden. Das wird sowohl im Monitor als auch in der Log-Datei verbucht.
OopsEntr 33640 /5300 : D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs(26): Die angegebene Umwandlung ist ungültig. bei AL.BeispielDotnetDemo.AfBeispielDotnetDemo.AfBeispielDotnetDemo.CheckGenerics() in D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs:Zeile 31. (AF-BEISPIEL-DOTNET-DEMO.DURCHFUEHREN) (TEST.001) [0x0596008c (TEST.AF-BEISPIEL-DOTNET-DEMO)/{BAA3FC7F-2C6F-434C-8096-56C08A95CB35}/33640//V9.9.0.76] User 33640 /5300 : Performance: AfBeispielDotnetDemo.CheckGenerics failed - 1.982ms
<LogEntry Date="2020-04-02 14:53:07" Severity="Exception" Source="D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs" Line="26" ThreadId="3"> <Exception Type="System.InvalidCastException" Source="AL.BeispielDotnetDemo.AfBeispielDotnetDemo.AfBeispielDotnetDemo.CheckGenerics"> <Message>Die angegebene Umwandlung ist ungültig.</Message> <StackTrace> bei AL.BeispielDotnetDemo.AfBeispielDotnetDemo.AfBeispielDotnetDemo.CheckGenerics() in D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs:Zeile 31.</StackTrace> </Exception> </LogEntry> <LogEntry Date="2020-04-02 14:53:07" Severity="Performance" Source="D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs" Line="26" ThreadId="3"> <Message>AfBeispielDotnetDemo.CheckGenerics failed - 1.982ms</Message> </LogEntry>
Wenn der Test fehlschlägt, wird eine Ausnahme UnitTestException
aufgeworfen, die ggf. eine InnerException
mit der tatsächlich aufgeworfene Ausnahme oder Condition beinhaltet.
try { this.Test.RunUnitTests(); } catch (UnitTestException exception) { // ...snip... }
Wenn ein Testlauf fehlschlägt, werden weitere Testläufe trotzdem ausgeführt. Erst am Ende aller Testläufe wird im Fehlerfall eine UnitTestException
aufgeworfen. Wenn Testläufe mit einer aufgeworfenen Ausnahme fehlschlagen, ist die erste aufgeworfene Ausnahme als InnerException
an der UnitTestException
gespeichert. Wenn weitere Testläufe mit einer Ausnahme fehlschlagen, können diese über die Eigenschaft AdditionalExceptions
abgefragt werden.
Das ModuleUnitTest
Attribut kennt einige optionale Argumente, mit dem der Unit Test zusätzlich gesteuert werden kann.
Mit dem Argument Caption
kann der Testlauf mit einem zusätzlichem Namen versehen werden.
[ModuleUnitTest(caption: "GenericSample")] private void CheckGenerics() { // check something }
Im Monitor wirkt sich das wie folgt aus:
User 37684 /38588 : Performance: AfBeispielDotnetDemo.CheckGenerics.GenericSample succeeded - 1.750ms
Und in der Log-Datei wie folgt:
<LogEntry Date="2020-04-02 15:05:01" Severity="Performance" Source="D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs" Line="26" ThreadId="3"> <Message>AfBeispielDotnetDemo.CheckGenerics.GenericSample succeeded - 1.750ms</Message> </LogEntry>
Da das ModuleUnitTest
Attribut bei einer Methode mehrfach angegeben werden kann, können so die einzelnen Testläufe unterschiedlich beschriftet werden.
Falls der Testlauf ein Bestandteil einer Sammlung von Tests ist, kann ein Bezug zu dieser Sammlung (Task) festgelegt werden. Die Angabe wird in den Output Daten übernommen und kann auch bei der Messung mit VTune
sinnvoll genutzt werden.
[ModuleUnitTest(caption: "Bulk", task: "DataOps", data: new object[] { 1000, "B_", 512 })] [ModuleUnitTest(caption: "Small", task: "DataOps", data: new object[] { 10, "S_", 16 })] private void BuildData(int instancesToCreate, string id, int lengthToUse) { // snip }
Ergibt im Monitor:
User 9092 /32448 : Performance: DataOps (AfBeispielDotnetDemo.BuildData.Bulk) succeeded - 119ms User 9092 /32448 : Performance: DataOps (AfBeispielDotnetDemo.BuildData.Small) succeeded - 9ms
Wenn Testläufe mit Task
-Markierung definiert sind, kann auch beim Aufruf von RunUnitTests
optional mitgegeben werden, welche Tasks ausgeführt werden sollen:
public void RunUnitTests(params string[] tasks)
try { this.Test.RunUnitTests("NewList", "DataOps"); } catch (UnitTestException exception) { // ...snip... }
Mit der Angabe Repeat
kann gesteuert werden, wie oft die zugrundeliegende Methode für den Testlauf aufgerufen werden soll. Diese Angabe macht besonders bei Unit-Tests, die für Performance-Messungen gebaut wurden, Sinn.
[ModuleUnitTest(repeat: 1000, caption: "Bulk")] [ModuleUnitTest(repeat: 1, caption: "Singular")] private void CheckGenerics() { // check something }
Mit der Angabe MaxDuration
kann gesteuert werden, wie lange die zugrundeliegende Methode für den Testlauf dauern darf. Diese Angabe macht besonders bei Unit-Tests, die für Performance-Messungen gebaut wurden, Sinn.
[ModuleUnitTest(repeat: 1000, caption: "Bulk", maxDuration: 20)] [ModuleUnitTest(repeat: 1, caption: "Singular", maxDuration: 5)] private void CheckGenerics() { // check something }
Überschreitet die Methode diese Angabe wird eine entsprechende Exception aufgeworfen.
<LogEntry Date="2020-09-30 14:08:57" Severity="Exception" Source="D:\Test\TBTestUnitTest\TbEfunTestmoduleUnitTest.cs" Line="32" ThreadId="3"> <Exception Type="System.Exception" Source="D:\Test\TBTestUnitTest\TbEfunTestmoduleUnitTest.cs"> <Message>Die maximale Laufzeit von 5ms wurde überschritten.</Message> <StackTrace /> </Exception> </LogEntry>
Mit dem Argument Description
kann der Testlauf mit einem Beschreibung versehen werden.1)
[ModuleUnitTest(description: "BeschreibungsText")] private void CheckGenerics() { // check something }
Die Unit-Test Methoden sollten grundsätzlich vom Typ void
sein. Sie können allerdings Argumente beinhalten, die pro Testlauf unterschiedlich bestückt werden können. Auch hier macht der Einsatz von der Caption
-Angabe Sinn:
[ModuleUnitTest(caption: "Bulk", data: new object[] { 1000, "B_", 512 })] [ModuleUnitTest(caption: "Small", data: new object[] { 10, "S_", 16 })] private void BuildData(int instancesToCreate, string id, int lengthToUse) { // ...snip... }
Monitor Ausgabe für dieses Beispiel:
User 33324 /4184 : Performance: AfBeispielDotnetDemo.BuildData.Bulk succeeded - 696ms User 33324 /4184 : Performance: AfBeispielDotnetDemo.BuildData.Small succeeded - 11ms
Und in der Log-Datei findet sich:
<LogEntry Date="2020-04-02 15:20:17" Severity="Performance" Source="D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs" Line="26" ThreadId="3"> <Message>AfBeispielDotnetDemo.BuildData.Bulk succeeded - 696ms</Message> </LogEntry> <LogEntry Date="2020-04-02 15:20:17" Severity="Performance" Source="D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs" Line="27" ThreadId="3"> <Message>AfBeispielDotnetDemo.BuildData.Small succeeded - 11ms</Message> </LogEntry>
Die Argumentwerte müssen gemäß .NET-Standards konstante Ausdrücke sein. Bei der Zuweisung wird versucht, die in den Attributen festgelegten Argumentwerte auf den Datentyp des jeweiligen Parameters zu konvertieren. Fehler bei der Konvertierung werden mit einer Oopsmeldung quittiert, führen jedoch nicht zum Abbruch des Testlaufs. Stattdessen erfolgt die Argumentzuweisung für den jeweiligen Parameter mit einem Defaultwert.
[ModuleUnitTest(task: "Timestamp", repeat: 10000, data: "undefined")] private void TNA_TimestampVarTest(DateTime tsVar) { // ...snip... }
OopsEntr 31740 /33280 : D:\utils\Test\Module\DM01\AfBeispielDotnetDemo\AfBeispielDotnetDemo.cs(63): TNA_TimestampVarTest - Timestamp (AfBeispielDotnetDemo.TNA_TimestampVarTest): Die Zeichenfolge wurde nicht als gültige DateTime erkannt. Ein unbekanntes Wort beginnt bei Index 0. bei System.DateTimeParse.Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles) bei System.Convert.ToDateTime(String value, IFormatProvider provider) bei System.String.System.IConvertible.ToDateTime(IFormatProvider provider) bei System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) bei TeamWiSE.Runtime.Common.TestSupport.InvokeWithArgs(MethodInfo methodInfo, ModuleUnitTestAttribute methodRun) in D:\WORK\dll32\NativeSupport\Common\TestSupport.cs:Zeile 294. (AF-BEISPIEL-DOTNET-DEMO.DURCHFUEHREN) (TEST.001) [0x092ecaec (TEST.AF-BEISPIEL-DOTNET-DEMO)/{C13243B3-CEC4-4B7E-8C7E-5FEFE1424DFF}/31740//V9.9.0.76]
Beim Ablauf bzw. Ausführung von Unit-Tests in NativeSupport
wird dem Entwickler über Ereignisse die Möglichkeit geboten, Einfluss auf den konkreten Testverlauf nehmen.2) Dafür stehen folgende Events
zur Verfügung:
using TeamWiSE.Runtime.ModuleUnitTest; public event EventHandler<ExecutingArgs> Executing; public void OnExecuting(Object sender, ExecutingArgs e);
Dieses Ereignis wird für jeden betroffenen Unit-Test aufgerufen, bevor es ausgeführt wird.
Die Klasse ExecutingArgs
stellt dabei die Ereignisdaten bereit.3)
Stellt Ereignisdaten für das Ereignis Executing
bereit.4)
Zur Verfügung stehen folgende Eigenschaften:
bool Cancel {get; set; }
Hiermit kann ggf. die Ausführung des jeweiligen Unit-Tests verhindert werden. Der Cancel
-Auftrag betrifft nur den im aktuellen Ereignis betroffenen Test. Der Wert wird mit false
vorbelegt.
String Caption { get; set; }
Caption
ist ein zusätzlicher Name am Testlauf und wird aus Feld Caption des ModuleUnitTest
Attributes vorbelegt.
Die Eigenschaft wird beim Protokollieren, Logs, Outputs und im Monitor angezeigt.
Eine Anpassung der Caption
bewirkt, dass die veränderte Angabe anstelle des Originaltextes im weiteren Verlauf verwendet bzw. angezeigt wird.
UInt32 Repeat { get; set; }
Stammt aus der Angabe Repeat im zugehörigen ModuleUnitTest
Attribut und gibt an, wie oft die Methode, zu der das ModuleUnitTest
Attribut definiert wurde, ausgeführt werden soll. Durch eine Veränderung wird die Häufigkeit der Ausführung der Methode entsprechend angepasst.
String MethodName { get; }
Ist der Name der Methode, zu der das ModuleUnitTest
Attribut gesetzt wurde. Der Name kann nicht verändert werden. Für weitere Informationen siehe Methoden fuer UnitTests.
Int64 MaxDuration { get; set; }
MaxDuration
steuert, wie lange die zugrundeliegende Methode maximal für den Testlauf dauern darf. Die Dauer wird in Millisekunden festgelegt. Der Wert wird vorbestückt aus dem Feld Max Duration des ModuleUnitTest
Attributs. Eine Anpassung dieser Angabe wird bei der nachfolgenden Ausführung berücksichtigt. Das Überschreiten führt zu einer TestSupportException
.
String TaskName { get; set; }
Die einzelnen Module-Unit-Tests können Bestandteil einer Sammlung von Tests sein, von sogenannten Tasks
. Die Angabe wird bei Logs und Outputs und im Monitor angezeigt, für mehr Information siehe Task.
String Description { get; }
Der Wert wird vorbestückt aus dem Feld Description des zugehörigen ModuleUnitTest
Attributs. Sie kann z.B. erklärende Informationen zu dem jeweiligen Test enthalten.
public Int32 Line { get; }
Ist die Zeilenangabe, auf welcher das ModuleUnitTest
Attribut definiert wurde.
public String Source { get; }
Source enthält den Name der Datei in welcher das ModuleUnitTest
Attribut definiert wurde.
using TeamWiSE.Runtime.ModuleUnitTest; public event EventHandler<ExecutedArgs> Executed; public void OnExecuted(object sender, ExecutedArgs e);
Dieses Ereignis wird für jeden betroffenen Unit-Test aufgerufen, nachdem es ausführt wurde. Die Klasse ExecutedArgs
stellt dabei die Ereignisdaten bereit.5)
Stellt Ereignisdaten für das Ereignis Executed
bereit.6) Zur Verfügung stehen die folgende Eigenschaften:
String Caption { get; }
Gibt an mit welcher Caption
der Testlauf durchgeführt wurde, und dementsprechend beim Protokollieren, Logs, Outputs und im Monitor verwendet wurde.
UInt32 Repeat { get; }
Gibt an, wie oft die der Unit-Test ausgeführt wurde.
String MethodName { get; }
Ist der Name der ausgeführten Methode. Für weitere Informationen siehe Methoden fuer UnitTests.
Int64 MaxDuration { get; }
MaxDuration
war die maximale Anzahl der Millisekunde, die für die Überwachung des Unit-Tests verwendet wurde.
String TaskName { get; }
Die TaskName
Eigenschaft benennt die Sammlung von Tests, den sogenannten Task, die als Gruppierung für den ausgeführten Test verwendet wurde.
String Description { get; }
Die Angabe Description
wurde bei der Ausführung des Tests mitgeführt. Sie kann z.B. erklärende Informationen zu dem jeweiligen Test enthalten.
public Int32 Line { get; }
Ist die Zeilenangabe, auf welcher das ModuleUnitTest
Attribut definiert wurde.
public String Source { get; }
Source enthält den Name der Datei in welcher das ModuleUnitTest
Attribut definiert wurde.
bool Success { get; }
Success
gibt an, ob die Ausführung des Tests erfolgreich gewesen ist.
TimeSpan Elapsed { get;}
Die Eigenschaft Elapsed
gibt an, wie viel Zeit für die Ausführung des Tests benötigt wurde. Die Dauer wird in Logs, Outputs und Monitor in Millisekunden angezeigt.
Module Module { get; }
Die Eigenschaft Module
verweist auf den TAA-Baustein, in dessen Kontext der Test ausgeführt wurde. Über Module
können bspw. auch Informationen über den Gevo, Arbeitsgang und aufgetretene Conditions besorgt werden.
UnitTestException Exception { get; }
Die Exception wird mit einer Sammlung von TestSupportException
bestückt, welche bei der Ausführung aufgetreten sind.
Falls mit dem Werkzeug Intel® VTune™ Profiler Messungen vorgenommen werden, tauchen ausgeführte Unit-Tests dort als Tasks auf.
In den Abschnitten Summary
, Bottom-Up
und Platform
kann mit der Task gearbeitet werden, oder auch darauf gefiltert werden. Hier einige Screenshots: