Asynchrone Programmierung

Komponententests von asynchronem Code

Stephen Cleary

Laden Sie die Codebeispiele herunter

Komponententests stellen einen Eckpfeiler in der modernen Entwicklung dar. Die Vorzüge von Komponententests im Rahmen eines Projekts leuchten leicht ein: Komponententests verringern die Anzahl der Programmfehler, verkürzen die Zeit bis zur Marktreife und beugen einer Entwicklung zu übermäßig starker Integration vor. Das alles sind erwähnenswerte Vorzüge, es gibt aber noch weitere Vorteile, die für Entwickler in direkterer Weise relevant sind. Wenn ich Komponententests erstelle, kann ich viel mehr Vertrauen zum Code haben. Es ist einfacher, Features hinzuzufügen oder Programmfehler in getestetem Code zu beheben, da die Komponententest ein Sicherheitsnetz bereitstellen, während der Code verändert wird.

Das Erstellen von Komponententests für asynchronen Code birgt einige recht einmalige Herausforderungen. Darüber hinaus ist der aktuelle Stand der Unterstützung von asynchronem Code in Komponententests und Mocking-Frameworks uneinheitlich – sie bildet sich gerade erst heraus. In diesem Artikel werden MSTest, NUnit und xUnit beleuchtet, die allgemeinen Prinzipien gelten jedoch für jedes Framework für Komponententests. Die meisten Beispiele in diesem Artikel verwenden die Syntax von MSTest, ich weise jedoch fortschreitend auf die Unterschiede im Verhalten hin. Der Codedownload enthält Beispiele für alle drei Frameworks.

Bevor wir uns in Details verlieren, stelle ich kurz die Funktion der Schlüsselwörter "async" und "await" als konzeptionelles Modell vor.

"Async" und "Await" kurz zusammengefasst

Das Schlüsselwort "async" bewirkt zweierlei: Es aktiviert das Schlüsselwort "await" innerhalb der betreffenden Methode, und es transformiert die Methode in einen Zustandsautomaten (ähnlich der Weise, wie das Schlüsselwort "yield" Iteratorblöcke in Zustandsautomaten überführt). Async-Methoden sollten nach Möglichkeit "Task" oder "Task<T>" zurückgeben. Es ist zulässig, dass eine Async-Methode "void" zurückgibt, es empfiehlt sich jedoch nicht, da es sehr schwierig ist, eine Async-Methode mit dem Rückgabewert "void" zu nutzen (oder zu testen).

Die von einer Async-Methode zurückgegebene Taskinstanz wird vom Zustandsautomaten verwaltet. Der Zustandsautomat erstellt die zurückzugebende Taskinstanz und schließt später eben diesen Task ab.

Die Ausführung der asynchronen Methode beginnt synchron. Erst wenn die Async-Methode einen Operator "await" erreicht, kann die Methode möglicherweise asynchron werden. Der Operator "await" nimmt ein einzelnes Argument an, ein "Erwartbares", wie etwa eine Taskinstanz. Zuerst überprüft der Operator "await" das Erwartbare (awaitable), um festzustellen, ob es bereits abgeschlossen wurde; In diesem Fall wird die Methode fortgesetzt (synchron). Wenn das Erwartbare noch nicht abgeschlossen ist, "hält" der Operator "await" die Methode an und setzt ihre Ausführung fort, wenn das Erwartbare abgeschlossen wurde. Die zweite Aktion, die der Operator "await" ausführt, besteht darin, dass er alle Ergebnisse vom Erwartbaren abruft und Ausnahmen auslöst, wenn das Erwartbare mit einem Fehler abgeschlossen wird.

Der von der asynchronen Methode zurückgegebene "Task" oder "Task<T>" stellt konzeptionell die Ausführung dieser Methode dar. Der Task wird abgeschlossen, wenn die Methode abgeschlossen wird. Wenn die Methode einen Wert zurückgibt, wird der Task mit diesem Wert als Ergebnis abgeschlossen. Wenn die Methode eine Ausnahme ausgibt (und sie nicht abfängt), wird der Task mit dieser Ausnahme abgeschlossen.

Aus dieser kurzen Übersicht lassen sich unmittelbar zwei Lektionen ableiten. Erstens, beim Testen der Ergebnisse einer asynchronen Methode ist der wichtige Teil der zurückgegebene Task. Die asynchrone Methode verwendet ihren Task, um den Abschluss, Ergebnisse und Ausnahmen zu melden. Die zweite Lektion ist die, dass der Operator "await" ein besonderes Verhalten aufweist, wenn sein Erwartbares bereits abgeschlossen ist. Das erörtern wir später noch im Zusammenhang mit asynchronen Stubs.

Der falsch übergebende Komponententest

In der Ökonomie der freien Märkte sind Verluste genau so wichtig wie Gewinne, es sind die Fehlschläge von Unternehmen, die sie zwingen, das herzustellen, was Kunden kaufen möchten, und die optimale Ressourcenverteilung im System als Ganzem zu fördern. In ähnlicher Weise sind die Fehler von Komponententests ebenso wichtig wie erfolgreichen Ausführungen. Sie müssen sich sicher sein, dass der Komponententest fehlschlägt, wenn er es soll, andernfalls bedeutet sein erfolgreicher Abschluss gar nichts.

Ein Komponententest, der vermutlich fehlschlägt wird (fälschlicher Weise) erfolgreich abgeschlossen, wenn er das Falsche testet. Das ist der Grund, warum die testgesteuerte Entwicklung (TDD, test-driven development) intensiven Gebrauch von der Rot/Grün/Refaktorieren-Schleife macht: Der "rote" Teil der Schleife stellt sicher, dass der Komponententest fehlschlägt, wenn der Code unrichtig ist. Auf den ersten Blick erscheint das Testen von Code, von dem man weiß, dass er fehlerhaft ist, lachhaft, es stellt aber einen wichtigen Schritt dar, weil Sie sicher sein müssen, dass die Tests fehlschlagen, wenn sie das müssen. Der rote Teil der TDD-Schleife stellt tatsächlich einen Test der Tests dar.

Behalten Sie das im Hinterkopf, und betrachten Sie die folgende zu testende asynchrone Methode:

public sealed class SystemUnderTest
{
  public static async Task SimpleAsync()
  {
    await Task.Delay(10);
  }
}

Neulinge in asynchronen Komponententests machen als ersten Versuch häufig einen Test wie diesen:

// Warning: bad code!
[TestMethod]
public void IncorrectlyPassingTest()
{
  SystemUnderTest.SimpleAsync();
}

Leider testet dieser Komponententest die asynchrone Methode tatsächlich nicht ordnungsgemäß. Wenn ich den zu testenden Code so ändere, dass er fehlschlägt, wird der Komponententest dennoch bestanden:

public sealed class SystemUnderTest
{
  public static async Task SimpleAsync()
  {
    await Task.Delay(10);
    throw new Exception("Should fail.");
  }
}

Dies verdeutlicht die erste Lektion aus dem Async/Await-Konzeptmodell: Um das Verhalten einer asynchronen Methode zu testen, müssen Sie den Task beobachten, den sie zurückgibt. Die beste Möglichkeit, das zu tun, besteht darin, auf die Rückgabe des Tasks durch die zu testende Methode zu warten. Dieses Beispiel verdeutlicht außerdem den Nutzen des Rot/Grün/Refaktorieren-Testentwicklungszyklus – Sie müssen sicherstellen, dass die Tests fehlschlagen, wenn der zu testende Code fehlschlägt.

Die meisten modernen Frameworks für Komponententests unterstützen das Testen von asynchronen Komponenten mit Rückgabe von Tasks. Die Methode "IncorrectlyPassingTest" löst die Compilerwarnung CS4014 aus, die eine Verwendung von "await" zum Nutzen des von SimpleAsync zurückgegebenen Tasks empfiehlt. Wenn die Komponententestmethode so geändert wird, dass sie auf den Task wartet, ist der naheliegendste Ansatz, die Testmethode so zu ändern, dass sie zu einer asynchronen Task-Methode wird. Dies stellt sicher, dass die Testmethode (ordnungsgemäß) fehlschlägt:

[TestMethod]
public async Task CorrectlyFailingTest()
{
  await SystemUnderTest.FailAsync();
}

Vermeiden von asynchronen Komponententests mit Rückgabewert "Void"

Erfahrene Async-Benutzer wissen, wie sie asynchrone Methoden mit dem Rückgabewert "void" vermeiden. Ich habe die Probleme mit "void" bei asynchroner Programmierung in meinem Artikel aus März 2013 beschrieben, "Bewährte Verfahren bei der asynchronen Programmierung” (bit.ly/1ulDCiI). Methoden von asynchronen Komponententests mit dem Rückgabewert "void" stellen keine einfache Möglichkeit zum Abrufen des Ergebnisses des Tests bereit. Ungeachtet dieser Schwierigkeit unterstützen einige Frameworks für Komponententests Async-void-Komponententests, indem sie ihren eigenen SynchronizationContext bereitstellen, innerhalb dessen die Komponententests ausgeführt werden.

Das Bereitstellen eines SynchronizationContext wird kontrovers diskutiert, da es die Umgebung, in der die Tests ausgeführt werden, verändert. Insbesondere wenn eine asynchrone Methode auf einen Task wartet, setzt sie standardmäßig diese asynchrone Methode unter dem aktuellen SynchronizationContext fort. Das Vorhandensein oder Fehlen eines SynchronizationContext ändert also indirekt das Verhalten des zu testenden Systems. Wenn Sie jetzt mehr Details über SynchronizationContext erfahren möchten, finden Sie meinen Artikel im MSDN Magazine zu dem Thema unter bit.ly/1hIar1p.

MSTest stellt keinen SynchronizationContext zur Verfügung. Wenn MSBuild Tests in einem Projekt erkennt, die Async-void-Komponententests verwenden, wird vielmehr Warnung UTA007 ausgegeben, die die Benutzer benachrichtigt, dass die Komponententestmethode "Task" anstelle von "void" zurückgeben sollte. MSBuild führt asynchrone Komponententests mit dem Rückgabewert "void" nicht aus.

NUnit unterstützt asynchrone Komponententests mit dem Rückgabewert "void" ab Version 2.6.2. Das nächste größere Update von NUnit, Version 2.9.6, unterstützt Async-void-Komponententests, die Entwickler haben sich aber bereits entschieden, die Unterstützung in Version 2.9.7 zu entfernen. NUnit stellt einen SynchronizationContext nur für asynchrone Komponententests mit dem Rückgabewert "void" zur Verfügung.

Während ich dies schreibe, plant xUnit, Unterstützung für asynchrone Komponententests mit dem Rückgabewert "void" in Version 2.0.0 hinzuzufügen. Anders als NUnit stellt xUnit einen SynchronizationContext für alle seine Testmethoden zur Verfügung, sogar für die synchronen. Da MSTest Async-void-Komponententests nicht unterstützt und NUnit seine frühere Entscheidung revidiert und die Unterstützung zurückzieht, käme es wohl nicht überraschend, wenn sich auch xUnit dazu durchränge, die Unterstützung für asynchrone Komponententests mit dem Rückgabewert "void" noch vor Veröffentlichung von Version 2 aufzugeben.

Unterm Strich ergibt sich, dass asynchrone Komponententests mit dem Rückgabewert "void" in Frameworks schwierig zu unterstützen sind, Änderungen an der Testausführungsumgebung erfordern und gegen über asynchronen Komponententests mit dem Rückgabewert "Task" keinen Vorteil erbringen. Außerdem unterscheidet sich die Unterstützung für Async-void-Komponententests unter den verschiedenen Frameworks und sogar Frameworkversionen. Daher sollten asynchrone Komponententests mit dem Rückgabewert "void" möglichst vermieden werden.

Asynchrone Task-Komponententests

Asynchrone Komponententests, die "Task" zurückgeben, weisen keine der Probleme von asynchronen Komponententests auf, die "void" zurückgeben. Asynchrone Komponententests mit dem Rückgabewert "Task" genießen in nahezu allen Frameworks für Komponententests breite Unterstützung. MSTest hat die Unterstützung in Visual Studio 2012, NUnit in den Versionen 2.6.2 und 2.9.6 und xUnit in Version 1.9 hinzugefügt. Sofern ihr Komponententest-Framework also weniger als 3 Jahre als ist, sollten asynchrone Task-Komponententests schlicht funktionieren.

Leider können veraltete Frameworks für Komponententests mit asynchronen Komponententests mit Rückgabewert "Task" nichts anfangen. Während diese Zeilen entstehen, gibt es eine größere Plattform, die sie nicht unterstützt: Xamarin. Xamarin verwendet eine angepasste ältere Version von NUnitLite und unterstützt aktuell Async-Task-Komponententests nicht. Ich erwarte, dass Unterstützung in der nahen Zukunft hinzugefügt wird. In der Zwischenzeit verwende ich eine Umgehung, die ineffizient ist, aber funktioniert: Die asynchrone Testlogik wird auf einem Thread aus einem anderen Threadpool ausgeführt und dann die Komponententestmethode (synchron) blockiert, bis der eigentliche Test abgeschlossen ist. Der Umgehungscode verwendet "GetAwaiter().GetResult()" anstelle von "Wait", da Wait alle Ausnahmen innerhalb einer AggregateException kapselt:

[Test]
public void XamarinExampleTest()
{
  // This workaround is necessary on Xamarin,
  // which doesn't support async unit test methods.
  Task.Run(async () =>
  {
    // Actual test code here.
  }).GetAwaiter().GetResult();
}

Testen von Ausnahmen

Beim Testen ist es ganz natürlich, das erfolgreiche Szenario zu testen, z. B. ob ein Benutzer das eigene Profil aktualisieren kann. Das Testen von Ausnahmen ist aber ebenfalls sehr wichtig; beispielsweise sollte ein Benutzer nicht in der Lage sein, das Profil eines anderen Benutzers zu aktualisieren. Ausnahmen sind genauso Teil einer API-Oberfläche wie Methodenparameter. Daher ist es wichtig, über Komponententests für Code zu verfügen, wenn er erwartungsgemäß fehlschlägt.

Ursprünglich wurde das "ExpectedExceptionAttribute" auf einer Komponententestmethode gesetzt, um anzugeben, dass ein Fehlschlagen des Komponententests erwartet wurde. Es gab jedoch einige Probleme mit "ExpectedExceptionAttribute". Das erste war, dass es nur das Fehlschlagen eines Komponententests im Ganzen erwarten konnte; es gab keine Möglichkeit, anzugeben, dass das Fehlschlagen nur bei einem bestimmten Teil des Tests erwartet wurde. Bei sehr einfachen Tests stellt das kein Problem dar, es kann aber irreführende Ergebnisse mit sich bringen, wenn die Tests länger werden. Das zweite Problem mit "ExpectedExceptionAttribute" besteht darin, dass es auf das Überprüfen des Typs der Ausnahme beschränkt ist. Es gibt keine Möglichkeit, auf andere Attribute zu prüfen, wie etwa Fehlercodes oder Meldungen.

Aus diesen Gründen hat sich in den letzten Jahren die Tendenz herausgebildet, eher etwa in der Art von "Assert.ThrowsException" einzusetzen, das den wichtigen Teil des Codes als Stellvertreter übernimmt und die ausgelöste Ausnahme zurückgibt. Dies behebt die Mängel von "ExpectedExceptionAttribute". Das MSTest-Desktopframework unterstützt nur "ExpectedExceptionAttribute", während das neuere MSTest-Framework, das für Windows Store-Komponententestprojekte verwendet wird, nur "Assert.ThrowsException" unterstützt. xUnit unterstützt nur "Assert.Throws", und NUnit unterstützt beide Ansätze. Abbildung 1 ist ein Beispiel für beide Arten von Tests in der Syntax von MSTest.

Abbildung 1 Testen von Ausnahmen mit synchronen Testmethoden

// Old style; only works on desktop.
[TestMethod]
[ExpectedException(typeof(Exception))]
public void ExampleExpectedExceptionTest()
{
  SystemUnderTest.Fail();
}
// New style; only works on Windows Store.
[TestMethod]
public void ExampleThrowsExceptionTest()
{
  var ex = Assert.ThrowsException<Exception>(() 
    => { SystemUnderTest.Fail(); });
}

Aber wie geht's nun mit asynchronem Code? Asynchrone Komponententests mit dem Rückgabewert "Task" funktionieren einwandfrei mit "ExpectedExceptionAttribute" sowohl in MSTest als auch in NUnit (xUnit unterstützt "ExpectedExceptionAttribute" überhaupt nicht). Die Unterstützung eines für asynchronen Code geeigneten "ThrowsException" ist jedoch weniger einheitlich. MSTest unterstützt asynchrones "ThrowsException", jedoch nur für Windows Store-Komponententestprojekte. xUnit hat ein asynchrones "ThrowsAsync" in den Builds der Vorabversionen von xUnit 2.0.0 eingeführt.

NUnit ist komplexer. Während dieser Artikel geschrieben wird, unterstützt NUnit asynchronen Code in seinen Überprüfungsmethoden, wie etwa "Assert.Throws". Damit dieser jedoch funktioniert, stellt NUnit einen SynchronizationContext bereit, was die gleichen Probleme wie bei asynchronen Komponententests mit Rückgabewert "void" verursacht. Darüber hinaus ist die Syntax zurzeit spröde, wie das Beispiel in Abbildung 2 zeigt. NUnit plant bereits, die Unterstützung für Async-void-Komponententests aufzugeben, und ich wäre nicht erstaunt, wenn diese Unterstützung zugleich damit aufgegeben würde. Zusammenfassend: Ich empfehle Ihnen, diesen Ansatz nicht zu verwenden.

Abbildung 2 Sprödes Testen von Ausnahmen in NUnit

[Test]
public void FailureTest_AssertThrows()
{
  // This works, though it actually implements a nested loop,
  // synchronously blocking the Assert.Throws call until the asynchronous
  // FailAsync call completes.
  Assert.Throws<Exception>(async () => await SystemUnderTest.FailAsync());
}
// Does NOT pass.
[Test]
public void BadFailureTest_AssertThrows()
{
  Assert.Throws<Exception>(() => SystemUnderTest.FailAsync());
}

Die aktuelle Unterstützung für ein "ThrowsException"/"Throws", das für asynchronen Code bereit ist, ist also nicht toll. In meinem eigenen Komponententestcode verwendet ich einen Typ, der dem "AssertEx" in Abbildung 3 sehr ähnlich ist. Dieser Typ ist ziemlich einfach, insofern als er einfach nur nackte Exception-Objekte auslöst, statt Assertionen vorzunehmen, aber genau dieser Code funktioniert in alle größeren Komponententest-Frameworks.

Abbildung 3 Die Klasse "AssertEx" für asynchrones Testen von Ausnahmen

using System;
using System.Threading.Tasks;
public static class AssertEx
{
  public static async Task<TException> 
    ThrowsAsync<TException>(Func<Task> action,
    bool allowDerivedTypes = true) where TException : Exception
  {
    try
    {
      await action();
    }
    catch (Exception ex)
    {
      if (allowDerivedTypes && !(ex is TException))
        throw new Exception("Delegate threw exception of type " +
          ex.GetType().Name + ", but " + typeof(TException).Name +
          " or a derived type was expected.", ex);
      if (!allowDerivedTypes && ex.GetType() != typeof(TException))
        throw new Exception("Delegate threw exception of type " +
          ex.GetType().Name + ", but " + typeof(TException).Name +
          " was expected.", ex);
      return (TException)ex;
    }
    throw new Exception("Delegate did not throw expected exception " +
      typeof(TException).Name + ".");
  }
  public static Task<Exception> ThrowsAsync(Func<Task> action)
  {
    return ThrowsAsync<Exception>(action, true);
  }
}

Dies ermöglicht die Verwendung eines moderneren "ThrowsAsync" anstelle des "ExpectedExceptionAttribute" in asynchronen Komponententests mit Rückgabewert "Task", in dieser Weise:

[TestMethod]
public async Task FailureTest_AssertEx()
{
  var ex = await AssertEx.ThrowsAsync(() 
    => SystemUnderTest.FailAsync());
}

Asynchrone Stubs und Mocks

Meines Erachtens kann nur äußerst einfacher Code ohne den Einsatz von Stubs, Mocks, Fakes oder ähnlicher Vorrichtungen getestet werden. In diesem Einführungsartikel bezeichne ich alle diese Testhilfsmittel zusammenfassend als Mocks. Beim Verwenden von Mocks ist es nützlich, im Hinblick auf Schnittstellen statt auf Implementierungen zu programmieren. Asynchrone Methoden funktionieren mit Schnittstellen einwandfrei; der Code in Abbildung 4 zeigt, wie Code eine Schnittstelle mit einer asynchronen Methode nutzen kann.

Abbildung 4 Verwenden einer asynchronen Methode an einer Schnittstelle

public interface IMyService
{
  Task<int> GetAsync();
}
public sealed class SystemUnderTest
{
  private readonly IMyService _service;
  public SystemUnderTest(IMyService service)
  {
    _service = service;
  }
  public async Task<int> RetrieveValueAsync()
  {
    return 42 + await _service.GetAsync();
  }
}

Mit diesem Code ist es ziemlich einfach, eine Testimplementierung der Schnittstelle zu erstellen und sie dem zu testenden System zu übergeben. Abbildung 5 zeigt, wie die drei wichtigsten Stubfälle getestet werden: asynchroner Erfolg, asynchrones Fehlschlagen und synchroner Erfolg. Asynchroner Erfolg und asynchrones Fehlschlagen sind die beiden wichtigsten Szenarien beim Testen von asynchronem Code, es ist aber auch wichtig, den synchronen Fall zu testen. Das liegt daran, dass sich der Operator "await" anders verhält, wenn das Erwartbare bereits abgeschlossen ist. Der Code in Abbildung 5 verwendet das Mockingframework Moq, um die Stubimplementierungen zu erstellen.

Abbildung 5 Stubimplementierungen für asynchronen Code

[TestMethod]
public async Task RetrieveValue_SynchronousSuccess_Adds42()
{
  var service = new Mock<IMyService>();
  service.Setup(x => x.GetAsync()).Returns(() => Task.FromResult(5));
  // Or: service.Setup(x => x.GetAsync()).ReturnsAsync(5);
  var system = new SystemUnderTest(service.Object);
  var result = await system.RetrieveValueAsync();
  Assert.AreEqual(47, result);
}
[TestMethod]
public async Task RetrieveValue_AsynchronousSuccess_Adds42()
{
  var service = new Mock<IMyService>();
  service.Setup(x => x.GetAsync()).Returns(async () =>
  {
    await Task.Yield();
    return 5;
  });
  var system = new SystemUnderTest(service.Object);
  var result = await system.RetrieveValueAsync();
  Assert.AreEqual(47, result);
}
[TestMethod]
public async Task RetrieveValue_AsynchronousFailure_Throws()
{
  var service = new Mock<IMyService>();
  service.Setup(x => x.GetAsync()).Returns(async () =>
  {
    await Task.Yield();
    throw new Exception();
  });
  var system = new SystemUnderTest(service.Object);
  await AssertEx.ThrowsAsync(system.RetrieveValueAsync);
}

Da wir von Mockingframeworks sprechen, sie können asynchrones Komponententests ihrerseits ein Stück weit unterstützen. Überlegen Sie für einen Moment, was das Standardverhalten einer Methode sein soll, wenn kein Verhalten angegeben wird. Einige Mockingframeworks (wie etwa Microsoft Stubs) geben standardmäßig eine Ausnahme zurück, andere (wie etwa Moq) einen Standardwert. Wenn eine asynchrone Methode einen "Task<T>" zurückgibt, bestünde ein naives Standardverhalten darin, "default(Task<T>)" zurückzugeben, anders gesagt, einen Null-Task, der eine "NullReferenceException" verursacht.

Dieses Verhalten ist nicht wünschenswert. Ein sinnvolleres Standardverhalten für asynchrone Methoden würde darin bestehen, "Task.FromResult­(default(T))" zurückzugeben – also einen Task, der mit dem Standardwert "T" abgeschlossen wird. Das ermöglicht dem zu testenden System, den zurückgegebenen Task zu verwenden. Moq hat diese Art von Standardverhalten für asynchrone Methoden in Moq-Version 4.2 implementiert. Meines Wissens ist das gegenwärtig die einzige Mockingbibliothek, die für asynchronen Code geeignete Standardwerte wie diesen verwendet.

Zusammenfassung

"Async" und "await" waren seit der Einführung von Visual Studio 2012 verfügbar, lang genug, dass sich einige bewährte Verfahren ausprägen konnten. Frameworks und Hilfskomponenten für Komponententests, wie etwa Mockingbibliotheken, konvergieren zu einer einheitlichen Unterstützung von asynchronem Code. Asynchrone Komponententests stellen heute schon eine Realität dar, und diese Tendenz wird zukünftig noch zunehmen. Wenn Sie das noch nicht getan haben, sollten Sie jetzt daran denken, Ihre Frameworks und Mockingbibliotheken für Komponententests zu aktualisieren, um sicherzustellen, dass Sie über die beste Unterstützung für asynchronen Code verfügen.

Frameworks für Komponententests entwickeln sich von asynchronen Komponententests mit Rückgabewert "void" weg, hin zu asynchronen Komponententests mit Rückgabewert "Task". Wenn Sie asynchrone Komponententests mit Rückgabewert "void" einsetzen, empfehle ich Ihnen, noch heute auf asynchrone Komponententests mit Rückgabewert "Task" umzustellen.

Ich erwarte in den nächsten zwei Jahren eine deutlich verbesserte Unterstützung für das Testen von Fehlerfällen in asynchronen Komponententests. Bis Ihr Framework für Komponententests gute Unterstützung bietet, empfehle ich Ihnen die Verwendung des in diesem Artikel erwähnten Typs "AssertEx" oder eines ähnlichen Verfahrens, das besser an ihr bestimmtes Framework angepasst ist.

Ordnungsgemäße asynchrone Komponententests sind ein wichtiger Teil der Entwicklung von asynchronem Code, und ich sehe mit Spannung, wie diese Frameworks und Bibliotheken sich des Themas asynchroner Code annehmen. Einer meiner ersten Lightning Talks vor einigen Jahren drehte sich um asynchrone Komponententests, als "async" noch eine Community Technology Preview war, und heute ist es schon so viel leichter geworden!


Stephen Cleary lebt als Ehemann, Vater und Entwickler in den USA im Norden von Michigan. Seit 16 Jahren beschäftigt er sich mit Multithreading und asynchroner Programmierung und nutzt die Async-Unterstützung in Microsoft .NET Framework seit der ersten Community Technology Preview. Er ist der Autor von "Concurrency in C# Cookbook" (O’Reilly Media, 2014).  Seine Homepage, einschließlich seines Blogs, befindet sich unter stephencleary.com.

Unser Dank gilt dem folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: James McCaffrey