非同期プログラミング

非同期コードの単体テスト: テストを容易にする 3 つの解決策

Sven Grand

コード サンプルのダウンロード

ここ 10 年、非同期プログラミングがますます重要になっています。CPU ベースの並列処理や I/O ベースの同時実行に非同期処理が使用され、ほぼすべてのリソースをすぐに利用できる状態にして、少ない労力で大きな成果を生み出しています。応答性の高いクライアント アプリケーションや拡張性の高いサーバー アプリケーションがすべて現実のものになっています。

ソフトウェア開発者は、同期機能を効果的に構築するために多くの設計パターンを習得していますが、非同期ソフトウェア設計のベスト プラクティスは比較的新しいテーマです。とは言え、並列プログラミングや同時実行プログラミングに対応するプログラミング言語やライブラリのサポートは、Microsoft .NET Framework 4 から 4.5 のリリースで大幅に強化されています。「非同期プログラミング」(msdn.microsoft.com/ja-jp/magazine/jj991977.aspx) や「Talk: Async Best Practices」(ディスカッション: 非同期のベスト プラクティス、英語、bit.ly/1DsFuMi) など、新しいテクニックに関する優れたアドバイスが既にかなりの数公開されていますが、async や await などの言語機能とタスク並列ライブラリ (TPL) を使用してアプリケーションやライブラリの内部 API や外部 API を設計するためのベスト プラクティスはあまり話題になっていません。

こうしたギャップは、開発者がビルドしているアプリケーションやライブラリのパフォーマンスと信頼性に影響するだけでなく、そのソリューションのテストの容易さにも影響します。非同期プログラミングの設計を堅牢にするベスト プラクティスが増えていけば、それにつれて単体テストも容易になっていきます。

今回は、このようなベスト プラクティスを念頭において、テストを容易にするコードの設計とリファクタリングの方法を紹介し、テストへの影響度を説明します。ここで紹介する解決策は、async と await を利用するコードにも、以前のフレームワークやライブラリが提供する下位レベルのマルチスレッド メカニズムに基づくコードにも当てはめることができます。今回の解決策は、テストのためだけにリファクタリングするものではありません。このプロセスで開発したコードは、ユーザーが簡単かつ効果的に使用できるようになります。

私が働くチームは、医療用 X 線装置のソフトウェアを開発しています。この分野では、単体テストのカバレッジが重要で、常に高いレベルが求められます。最近、開発者から質問を受けました。「コードをすべてカバーする単体テストを作成するようにといつも言われますが、コードから別のスレッドを開始する場合や、タイマーを使って後からスレッドを開始して何回も実行する場合などは、合理的な単体テストをどのように作成すればよいでしょう」

これはよい質問です。以下のコードをテストするとしましょう。

public void StartAsynchronousOperation()
{
  Message = "Init";
  Task.Run(() =>
    {
      Thread.Sleep(1900);
      Message += " Work";
    });
}
public string Message { get; private set; }

このコードのテストを次のように記述してみましたが、あまりうまくいきませんでした。

[Test]
public void FragileAndSlowTest()
{
  var sut = new SystemUnderTest();
  // Operation to be tested will run in another thread.
  sut. StartAsynchronousOperation();
  // Bad idea: Hoping that the other thread finishes execution after 2 seconds.
  Thread.Sleep(2000);
  // Assert outcome of the thread.
  Assert.AreEqual("Init Work", sut.Message);
}

単体テストがすばやく実行され、想定どおりの結果が得られると思いましたが、このテストは不安定で、時間がかかりました。ここでの想定は、StartAsynchronousOperation メソッドを使ってテスト対象の操作を別のスレッドで開始し、その操作の結果をテストでチェックすることです。新しいスレッドの開始、スレッド プールの既存のスレッドの初期化、操作の実行のそれぞれにかかる時間は予測不能で、テスト コンピューターで他にどのようなプロセスが実行されているかによって異なります。スリープ状態の時間が短かすぎたり、非同期操作がまだ完了していなければ、テストは失敗します。どちらにしても正しい結果は得られません。テストが不安定になるリスクを受け入れて待機時間をできる限り短くするか、テストの速度がさらに遅くなってもスリープ時間を長くしてテストの堅牢性を高めるしかありません。

タイマーを使用するコードのテストでも同じ問題が生じます。

private System.Threading.Timer timer;
private readonly Object lockObject = new Object();
public void StartRecurring()
{
  Message = "Init";
  // Set up timer with 1 sec delay and 1 sec interval.
  timer = new Timer(o => { lock(lockObject){ Message += " Poll";} }, null,
    new TimeSpan(0, 0, 0, 1), new TimeSpan(0, 0, 0, 1));
}
public string Message { get; private set; }

次のようにしても、おそらく同じ問題が生じます。

[Test]
public void FragileAndSlowTestWithTimer()
{
  var sut = new SystemUnderTest();
  // Execute code to set up timer with 1 sec delay and interval.
  sut.StartRecurring();
  // Bad idea: Wait for timer to trigger three times.
  Thread.Sleep(3100);
  // Assert outcome.
  Assert.AreEqual("Init Poll Poll Poll", sut.Message);
}

複数の異なるコード分岐を備えた複雑な操作をテストするときは、個々のテストの数が膨大になります。新しいテストが増えるたびに、テスト スイートの速度が低下していきます。突発的なエラーの調査には時間がかかるので、テスト スイートのメンテナンス コストも増加します。速度の遅いテスト スイートは、実行される機会が少なくなり、メリットが低下していきます。おそらく、どこかの時点で、速度が遅く、断続的に失敗するテストはまったく使用されなくなります。

上記の 2 種類のテストは、操作で例外がスローされるタイミングを検出することもできません。操作が別のスレッドで実行されるため、例外はテストを実行しているスレッドに伝達されません。したがって、テストでできることは、テスト対象のコードのエラーの動作が正しいことを確認することに限定されます。

今回は、テスト対象のコードの設計を改善することで、速度が遅く、不安定な単体テストにならないようにする汎用の解決策を 3 つ提案します。また、単体テストで例外を確認する方法についても説明します。どの解決策にもそれぞれメリットがありますが、デメリットや制限事項もあります。コラムの最後に、状況に合わせてどの解決策を選択するかについて、いくつか推奨事項を示します。

ここで取り上げるのは、単体テストについてです。一般に、単体テストとは、システムの他の部分から独立しているコードをテストすることです。ここで言うシステムの他の部分の 1 つは、OS のマルチスレッド機能です。標準ライブラリのクラスやメソッドを使用して非同期作業のスケジュールを設定しますが、この中でマルチスレッドに関する部分は単体テストから除外して、非同期に実行される機能のテストに集中します。

非同期コードの単体テストは、コードに 1 つのスレッドで実行される機能のブロックが含まれている場合に意味があり、単体テストでは、そのブロックがが想定どおりに動作していることを確認します。単体テストで正しく機能していることがわかったら、別のテスト手法を使用して同時実行の問題を明らかにします。マルチスレッド コードのテストや分析を行って同時実行の問題を検出する手法はいくつかあります (例については、「同時実行の問題を特定するためのツールと手法」(msdn.microsoft.com/ja-jp/magazine/cc546569.aspx) を参照してください)。たとえば、ストレス テストでは、システム全体またはシステムの大きな部分に負荷をかけます。こうした手法は、単体テストの補完には重要ですが、今回は取り上げません。ここで紹介する解決策では、マルチスレッドに関連する部分を除外し、単体テストを使って独立した機能をテストする方法を説明します。

解決策 1: 機能をマルチスレッド処理から切り離す

非同期操作に関係する機能の単体テストを行うのに最も簡単な解決策は、テストする機能をマルチスレッド処理から切り離すことです。Gerard Mezaros は著書『xUnit Test Patterns』(Addison-Wesley、2007 年) の中で、この方法を Humble Object パターンとして説明しています。このパターンでは、テスト対象の機能を独立した新しいクラスに取り出し、マルチスレッド処理に関連する部分を Humble Object 内に残して、そこからこの新しいクラスを呼び出します (図 1 参照)。

Humble Object パターン
図 1 Humble Object パターン

以下のコードは、リファクタリングを行って取り出した機能を示します。これは、純粋な同期コードになります。

public class Functionality
{
  public void Init()
  {
    Message = "Init";
  }
  public void Do()
  {
    Message += " Work";
  }
  public string Message { get; private set; }
}

リファクタリングを行う前は、この機能と非同期コードが混在していましたが、ここではこの機能を Functionality クラスに移動しています。このクラスにはマルチスレッド処理が含まれていないため、簡単な単体テストを使ってテストできます。このようなリファクタリングは、単体テストにとってだけではなく、全般的に重要です。コンポーネントは、本質的な同期操作の非同期ラッパーを公開すべきではありません。代わりに、その操作の呼び出しをオフロードするかどうかの判断を呼び出し元に委ねます。単体テストの場合、オフロードしないようにしますが、その機能を利用するアプリケーションでは、応答性や並列実行を理由にオフロードする場合もあります。詳細については、Stephen Toub のブログ記事「Should I Expose Asynchronous Wrappers for Synchronous Methods?」(同期メソッドの非同期ラッパーを公開をすべきか、英語、bit.ly/1shQPfn) を参照してください。

このパターンを少し変えて、SystemUnderTest クラスの特定のプライベート メソッドをパブリックにして、そのメソッドをテストから直接呼び出せるようにしてもかまいません。その場合、マルチスレッド処理に関係しない機能をテストするために追加のクラスを作成する必要がなくなります。

Humble Object パターンによって機能を切り離すのは簡単な作業で、非同期作業を一度だけすぐにスケジュールするコードだけでなく、タイマーを使用するコードでも可能です。その場合、タイマーの処理を Humble Object に残し、繰り返しの操作を Functionality クラスまたはパブリック メソッドに移します。この解決策のメリットは、テスト対象のコードからスローされる例外をテストで直接確認できることです。Humble Object パターンは、非同期作業のスケジュール設定に使用する手法に関係なく、当てはめることができます。この解決策のデメリットは、Humble Object 自体のコードがテストの対象外になることと、テスト対象のコードを変更する必要があることです。

解決策 2: テストを同期する

非同期に実行されるテスト対象の操作の完了をテストで検出できれば、問題になっている不安定さも処理の遅さもなくなります。テストはマルチスレッド処理コードを実行しますが、テスト対象のコードによってスケジュールが設定された操作とテストを同期すると、テストの信頼性が高まり、速度が向上します。テストで機能に集中しながら、非同期実行による悪影響を最小限に抑えることができます。

テスト対象のメソッドが、操作の完了時に通知される型のインスタンスを返すことができればベストです。.NET Framework 4 から導入された Task 型がこのニーズを適切に満たし、.NET Framework 4.5 から導入された async/await 機能により、Task の構成が容易になります。

public async Task DoWorkAsync()
{
  Message = "Init";
  await Task.Run( async() =>
  {
    await Task.Delay(1900);
    Message += " Work";
  });
}
public string Message { get; private set; }

このリファクタリングは、一般的なベスト プラクティスを表し、単体テストにも、公開された非同期機能の一般的な利用にも有効です。非同期操作を表す Task を返すことで、コードの利用者は、非同期操作が完了したタイミングや、非同期操作が例外によって失敗したかどうか、および非同期操作から結果が返されたかどうかを簡単に確認できます。

これにより、同期メソッドの単体テストも非同期メソッドの単体テストも同様に容易になります。その結果、対象のメソッドを呼び出し、返された Task が完了するのを待つだけで、テストとテスト対象のコードとを簡単に同期できるようになります。完了の待機は、Task Wait メソッドから (呼び出し元のスレッドをブロックして) 同期をとって実行することも、非同期操作の結果を確認する前に await キーワードで (呼び出し元のスレッドがブロックされないように継続操作を使用して) 非同期に実行することもできます (図 2 参照)。

Async と Await を使用した同期
図 2 Async と Await を使用した同期

単体テスト メソッドで await を使用するには、テスト自体をそのシグネチャ内で async を使用して宣言する必要があります。sleep ステートメントは必要なくなります。

[Test]
public async Task SynchronizeTestWithCodeViaAwait()
{
  var sut = new SystemUnderTest();
  // Schedule operation to run asynchronously and wait until it is finished.
  await sut.StartAsync();
  // Assert outcome of the operation.
  Assert.AreEqual("Init Work", sut.Message);
}

さいわいなことに、主要単体テスト フレームワーク (MSTest、xUnit.net、NUnit) の最新バージョンは、async と await を使ったテストをサポートします (Stephen Cleary のブログ (bit.ly/1x18mta、英語) を参照)。どのフレームワークのテスト ランナーも、async Task テストに対処して、スレッドが完了するのを待ってから、assert ステートメントの評価を開始できます。単体テスト フレームワークのテスト ランナーが async Task テスト メソッド シグネチャに対処できない場合、テストでは、少なくとも、テスト対象のシステムから返された Task の Wait メソッドを呼び出すことができます。

また、タイマー ベースの機能は、TaskCompletionSource クラスを使用して拡張できます (詳細についてはコード ダウンロードを参照)。その後、テストは、繰り返し行う特定の操作が完了するまで待機します。

[Test]
public async Task SynchronizeTestWithRecurringOperationViaAwait()
{
  var sut = new SystemUnderTest();
  // Execute code to set up timer with 1 sec delay and interval.
  var firstNotification = sut.StartRecurring();
  // Wait that operation has finished two times.
  var secondNotification = await firstNotification.GetNext();
  await secondNotification.GetNext();
  // Assert outcome.
  Assert.AreEqual("Init Poll Poll", sut.Message);
}

残念ながら、既にリリースしたコードをテストする場合や、重要な変更を行うためテスト対象のメソッド シグネチャを変更できない場合など、テスト対象のコードで async と await を使用できないこともあります。このような場合、他のテクニックを使って同期を実装しなければなりません。同期を実現できるのは、テスト対象のクラスがイベントを呼び出したり、操作の完了時に依存関係のあるオブジェクトを呼び出したりする場合です。次の例では、依存関係のあるオブジェクトが呼び出される場合にテストを実装する方法を示しています。

private readonly ISomeInterface dependent;
public void StartAsynchronousOperation()
{
  Task.Run(()=>
  {
    Message += " Work";
    // The asynchronous operation is finished.
    dependent.DoMore()
  });
}

イベント ベースの同期に関する他の例も、コード ダウンロードに含めています。

テスト中は依存関係のあるオブジェクトをスタブに置き換えれば、テストと非同期操作を同期できるようになります (図 3 参照)。

依存関係のあるオブジェクトのスタブによる同期
図 3 依存関係のあるオブジェクトのスタブによる同期

テストでは、スタブにスレッドセーフな通知メカニズムを用意する必要があります。これは、スタブ コードが別のスレッドで実行されるためです。以下のテスト コード例では、ManualResetEventSlim を使用し、RhinoMocks モック作成フレームワークを使用してスタブを生成しています。

// Setup
var finishedEvent = new ManualResetEventSlim();
var dependentStub = MockRepository.GenerateStub<ISomeInterface>();
dependentStub.Stub(x => x.DoMore()).
  WhenCalled(x => finishedEvent.Set());
var sut = new SystemUnderTest(dependentStub);

これで、テストでは、非同期操作を実行し、通知を待機できるようになります。

// Schedule operation to run asynchronously.
sut.StartAsynchronousOperation();
// Wait for operation to be finished.
finishedEvent.Wait();
// Assert outcome of operation.
Assert.AreEqual("Init Work", sut.Message);

テストとテスト対象のスレッドとを同期する解決策は、ある特性を持つコードに当てはめることができます。つまり、async と await、またはプレーンなイベントなどの通知メカニズムがあるコードや、依存関係のあるオブジェクトを呼び出すコードです。

async と await による同期の大きなメリットは、どのような種類の結果でも呼び出し元のクライアントに伝達できることです。例外もその特殊な種類の結果の 1 つです。そのため、テストでは、例外を明示的に処理できます。他の同期メカニズムでは、問題のある結果から間接的にエラーを認識することしかできません。

タイマー ベースの機能を含むコードでは、async/await、イベント、または依存関係のあるオブジェクトの呼び出しのいずれかを利用して、テストとタイマー操作とが同期するようにします。繰り返し操作が完了するたびにテストが通知を受け取り、結果を確認できます (コード ダウンロードに含めた例を参照)。

残念ながら、通知を使用する場合でも、タイマーによって単体テストの速度は低下します。通常、テスト対象の繰り返し操作は、わずかに遅れて開始されます。テストの速度が低下し、少なくともこの遅れの分の時間がかかります。この遅れと、通知が必要になることがデメリットです。

それでは、ここまでの 2 つの解決策の制限事項の一部を回避するもう 1 つの解決策を見ていきましょう。

解決策 3: 単一スレッドでのテスト

この解決策では、テストからテスト自体と同じスレッド内で操作の実行を直接トリガーできるように、テスト対象のコードを準備します。これは、jMock チームの Java の手法を流用したものです (「マルチスレッド コードのテスト」(jmock.org/threads.html、英語) を参照)。

以下に例を示すテスト対象のシステムは、タスク スケジューラー オブジェクトを挿入して、非同期作業のスケジュールを設定します。3 番目の解決策の機能を説明するために、最初の操作の完了時に開始する 2 番目の操作を追加します。

private readonly TaskScheduler taskScheduler;
public void StartAsynchronousOperation()
{
  Message = "Init";
  Task task1 = Task.Factory.StartNew(()=>{Message += " Work1";},
                                     CancellationToken.None,
                                     TaskCreationOptions.None,
                                     taskScheduler);
  task1.ContinueWith(((t)=>{Message += " Work2";}, taskScheduler);
}

テスト対象のシステムを変更して、別の TaskScheduler を使用するようにします。テスト中は、"通常" の TaskScheduler を DeterministicTaskScheduler に置き換えます。置き換えたスケジューラーでは、非同期操作を同期をとって開始できるようにします (図 4 参照)。

SystemUnderTest での別の TaskScheduler の使用
図 4 SystemUnderTest での別の TaskScheduler の使用

以下のテストは、テスト自体と同じスレッド内で、スケジュールを設定した操作を実行します。テストは、DeterministicTaskScheduler をテスト対象のコードに挿入します。DeterministicTaskScheduler は、すぐに新しいスレッドを開始するのではなく、スケジュールを設定したタスクをキューに登録するだけです。その次のステートメントでは、RunTasksUntilIdle メソッドが 2 つの操作を同期をとって実行します。

[Test]
public void TestCodeSynchronously()
{
  var dts = new DeterministicTaskScheduler();
  var sut = new SystemUnderTest(dts);
  // Execute code to schedule first operation and return immediately.
  sut.StartAsynchronousOperation();
  // Execute all operations on the current thread.
  dts.RunTasksUntilIdle();
  // Assert outcome of the two operations.
  Assert.AreEqual("Init Work1 Work2", sut.Message);
}

DeterministicTaskScheduler は、スケジュール設定機能を提供するために TaskScheduler メソッドをオーバーライドし、テスト用に特別に RunTasksUntilIdle メソッドを追加します (DeterministicTaskScheduler の実装の詳細については、コード ダウンロードを参照)。同期単体テストと同様、スタブを使用して、一度に 1 つの機能だけに集中しテストします。

タイマーを使用するコードで問題になるのは、テストの不安定さと速度の遅さだけではありません。ワーカー スレッドで実行されていないタイマーをコードで使用すると、単体テストがさらに複雑になります。.NET Framework クラス ライブラリには、Windows フォーム アプリケーション用の System.Windows.Forms.Timer や Windows Presentation Foundation (WPF) アプリケーション用の System.Windows.Threading.DispatcherTimer など、UI アプリケーションで使用するために特別に設計されたタイマーがあります (「.NET Framework クラス ライブラリに含まれるタイマー クラスの比較」(bit.ly/1r0SVic、英語) を参照)。このようなタイマーは UI メッセージ キューを使用しますが、単体テストではこれを直接使用できません。コラムの冒頭で示したテストは、このようなタイマーには機能しません。テストは、WPF DispatcherFrame を使用するなどして、メッセージ ポンプをすばやく動作させる必要があります (コード ダウンロードに含まれる例を参照)。UI ベースのタイマーを利用しているときに単体テストをシンプルかつ明確にするには、テスト中にこれらのタイマーを置き換える必要があります。ここでは、"実際の" タイマーをテスト専用の実装に置き換えられるように、タイマーにインターフェイスを導入しています。あらゆる場面で単体テストを強化できるため、System.Timers.Timer や System.Threading.Timer などの "スレッド" ベースのタイマーの場合も同様の対処が可能です。テスト対象のシステムは、この ITimer インターフェイスを使用するように変更する必要があります。

private readonly ITimer timer;
private readonly Object lockObject = new Object();
public void StartRecurring()
{
  Message = "Init";
  // Set up timer with 1 sec delay and 1 sec interval.
  timer.StartTimer(() => { lock(lockObject){Message += 
    " Poll";} }, new TimeSpan(0,0,0,1));
}

ITimer インターフェイスの導入により、図 5 に示すように、テスト中にタイマーの動作を置き換えることができます。

SystemUnderTest での ITimer の使用
図 5 SystemUnderTest での ITimer の使用

ITimer インターフェイスを定義するという作業は増えますが、初期化と繰り返し操作の結果を確認する単体テストを、数ミリ秒以内に、迅速かつ確実に実行できるようになります。

[Test]
public void VeryFastAndReliableTestWithTimer()
{
  var dt = new DeterministicTimer();
  var sut = new SystemUnderTest(dt);
  // Execute code that sets up a timer 1 sec delay and 1 sec interval.
  sut.StartRecurring();
  // Tell timer that some time has elapsed.
  dt.ElapseSeconds(3);
  // Assert that outcome of three executions of the recurring operation is OK.
  Assert.AreEqual("Init Poll Poll Poll", sut.Message);
}

DeterministicTimer は、テストのために特別に作成されています。DeterministicTimer により、テストは、待機することなく、タイマーの動作が実行されるタイミングを制御できるようになります。この動作は、テスト自体と同じスレッドで実行されます (DeterministicTimer の実装の詳細については、コード ダウンロードを参照)。"テスト以外" のコンテキストでテスト コードを実行する場合は、既存のタイマーの ITimer アダプターを実装する必要があります。コード ダウンロードには、フレームワーク クラス ライブラリのいくつかのタイマー用にアダプターのサンプルを用意しています。ITimer インターフェイスは、具体的な状況のニーズに応じて調整できます。また、特定のタイマーの全機能の一部だけを含めることもできます。

DeterministicTaskScheduler または DeterministicTimer を使用した非同期コードのテストにより、テスト中は簡単にマルチスレッド処理を無効にすることができます。機能は、テスト自体と同じスレッド上で実行されます。初期化コードと非同期コードの相互の操作は維持されるため、テストが可能になります。たとえば、この種のテストでは、タイマーの初期化に使用する適切な時間値を確認できます。例外がテストに転送されるため、テストでコード エラーの動作を直接確認できます。

まとめ

非同期コードの効果的な単体テストには、主に 3 つのメリットがあります。1 つはテストのメンテナンス コストが削減される点、2 つ目はテストの速度が上がる点、3 つ目はテストを実行できないというリスクを最小限に抑えられる点です。今回紹介した解決策は、この目標の実現に役立ちます。

最初の解決策は、Humble Object を使用してプログラムの非同期の部分から機能を切り離すという最も汎用的な解決策です。これは、スレッドの開始方法に関係なく、あらゆる状況に当てはまります。非常に複雑な非同期のシナリオ、複雑な機能、またはその両方が混在する場合には、この解決策を使用することをお勧めします。これは、懸案事項の分離という設計概念を表現するのに適した例です (bit.ly/1lB8iHD (英語) を参照)。

2 番目の解決策は、テストとテスト スレッドの完了の同期をとります。この解決策は、テスト対象のコードが async や await などの非同期メカニズムを提供する場合に当てはめることができます。この解決策は、前提条件となる通知メカニズムを満たすことができる場合に有効です。可能であれば、タイマー以外のスレッドが開始するときに、async と await を使用します。これは、例外をテストに伝達するためです。タイマーを使用するコードのテストでは、await、イベント、または依存関係のあるオブジェクトの呼び出しを使用します。このようなテストは、タイマーの遅れや時間間隔が長い場合に速度が低下するおそれがあります。

3 番目の解決策は、DeterministicTaskScheduler と DeterministicTimer を使用して、他の解決策の制限事項やデメリットの大半を回避します。この解決策は、テスト対象のコードの準備に余分な作業が必要ですが、コード カバレッジの広い単体テストを実現できます。タイマーを使用したコードのテストは、遅れや時間間隔の待機を必要としないで、非常に速く実行できます。例外をテストに伝達することも可能です。そのため、この解決策は、広いコード カバレッジを兼ね備えた、堅牢、高速、かつ洗練された単体テスト スイートを実現します。

これら 3 つの解決策により、ソフトウェア開発者は、非同期コードの単体テストの問題点を解消できます。これらの解決策を使用すると、高速かつ堅牢な単体テスト スイートを作成し、幅広い並列プログラミング手法をカバーできます。


Sven Grandは、Philips Healthcare の診断用 X 線装置を扱う部署の品質担当エンジニアリング ソフトウェア アーキテクトです。彼は、遠い昔、2001 年のマイクロソフト主催のソフトウェア カンファレンスでテスト駆動開発について初めて耳にしたときに、"テストに感化" されました。Sven の連絡先は sven.grand@philips.com (英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Stephen Cleary、James McCaffrey、Henning Pohl、および Stephen Toub に心より感謝いたします。