Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2014年06月28日]

続:Task 周りのテストで気をつけること

2014年06月29日 21:21更新 筆者:K.Ktouth

先日、NUnit と SynchronizationContext に関する注意事項を書いたんですが、また同じ問題が出たのでコードの追跡に追われてましたげー

結論だけを書くと、生成したタスクの中でさらにタスクを作っていたため、そのタスクの中で生成した SynchronizationContext は当然ながら別スレッドプールで実行されていたわけです。魔法の重ねがけ良くない(ぉぃ
んで、色々考えたんですが……一番シンプルな方法をとることに。つまり……

  • その都度 Sync....Context を呼ぶ

    → 呼ぶタイミングが別タスク=別スレッドプールの中であるため null を返す。(どのスレッドプールで呼び出されてるかは外からではわからない。)

  • タスク生成直前に Sync....Context を呼んで保持しておく

    → 単純なコードなら問題ないが、その生成タイミングが別タスクなら結局同じ。

  • いっそ静的プロパティに保存しておく

    → 静的プロパティ、正確には静的フィールドの初期化タイミングは型が参照される(静的コンストラクタが呼ばれる)タイミングなので、タスク生成よりずっと前に確保される。

前回の日記の時は2番目の方法を使ってました。で、タスクの中でタスクを呼ぶという状況に陥っていた模様。
そもそもメインスレッドは一つ、というか同期に使うコンテキストは1プロセスで同じでなければ意味が無いわけで。インスタンス単位で持つ必要は無かった(笑)

NUnit 上で単体テストしてみたところ、静的フィールドなどもテストフィクスチャ毎に初期化されているようです。NULL 参照例外に何度も遭遇しましたはず
以下のようなコードで対応できました。

public class FooBar
{
  internal static SynchronizationContext _Sync { get; private set; }
  static FooBar()
  {
    _Sync = SynchronizationContext.Current;
  }
}

[TestFixture]
class FooBarTest
{
  private static readonly SynchronizationContext BaseContext = new SynchronizationContext();
  private static SynchronizationContext _PrivateContextBuckUp;

  [TestFixtureSetUp]
  public static void StaticTestSetUp()
  {
    _PrivateContextBuckUp = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(BaseContext);
  }
  [TestFixtureTearDown]
  public static void StaticTearDown()
  {
    SynchronizationContext.SetSynchronizationContext(_PrivateContextBuckUp);
  }
}

最悪このコードなら、テストの静的セットアップの際にリフレクション使って直接値を代入すれば良いので対応しやすいかな、と(ぉぃ

NUnit ランナーには Sync....Current を適当な値に初期化しておいて欲しいもんですよまったく……

本日のリンク元
その他のリンク元
検索