nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Visual Studio and Development Technologies。なお掲載内容は個人の見解であり、所属する企業を代表するものではありません。

【解決済】引数渡しなし&マルチスレッドで、呼び出し元とオブジェクトを共有するいい方法

2017.02.19 追記:
本件は、以下のアドバイスで解決することができます。


AsyncLocalなんて追加されていたんですね。全く知りませんでした!
過去形じゃないのは、PrismのUnitTestプロジェクトがPCLでAsyncLocalが使えず、結局Prism依存の解決策を思いついたため、そちらを利用したためです。
直接の修正箇所は以下ですが、ちょっと分かりにくいですね。。。
https://github.com/PrismLibrary/Prism/pull/942/commits/d086e5db57685967591b0932108f5604eba5200d#diff-ee845c5f0d8ae25841a5335f0ca7b5a6

本件は、多くの方にアドバイス頂きました。
解決したこと以上に、その事が嬉しかったです。
みなさん、ありがとうございました!
追記終了

Prismの機能実装&不具合対応してるのですが、どうもイマイチうまくテストが書けないところがあって、私のC#力では限界に達しました。(まだ考えてはいるけど無理くさい。。。)
そんな訳で、どなたか良いアイディアがあったら支援欲しいなと思ってエントリー書いています。
端的に助けてほしいことを書くと
「該当のテストというコンテキスト内だけで、スレッド関係なく情報を共有する方法」
って無いかな?ってことです。
誰か僕とPrismを助けて〜!

おおもとのIssue

絶対パス遷移した時に、IDestructibleが呼ばれるように改修したい。

github.com

これ自体は実装できています。

実現したいこと

テストコード側で、IDestructible含めPrism側から発行されるイベントや、ViewやViewModelのコンストラクタの呼び出し順を確認したい。それに当たって

  • どのオブジェクトから発行されたか
  • 発行されたイベントは何か
  • 発行されて順序は?
  • テストケースをマルチスレッド実行したい

現状、最後が実現できていません。

現状の実装イメージ

テストコード側はこんな感じで

using (var recorder = PageNavigationEventRecoder.BeginRecord())
{
    await navigationService.NavigateAsync("/ContentPage");

    var record = recorder.TakeFirst();
    Assert.Equal(navigatedPage, record.Sender);
    Assert.Equal(PageNavigationEvent.OnNavigatingTo, record.Event);

    record = recorder.TakeFirst();
    Assert.Equal(navigatedPage.BindingContext, record.Sender);
    Assert.Equal(PageNavigationEvent.OnNavigatingTo, record.Event);

    // ・・・中略

    Assert.True(recorder.IsEmpty);

イベントの発生側はこんな感じでRecorderにイベントを記録

public class ViewModelBase : BindableBase, INavigationAware, IDestructible
{
    public void OnNavigatedFrom(NavigationParameters parameters)
    {
        PageNavigationEventRecoder.Record(this, PageNavigationEvent.OnNavigatedFrom);
    }

一応これでテストはできています。シングルスレッド限定なら

問題点

  • イベントをstatic変数で保持しているから

なのでマルチスレッドで実行すると、異なるテストケースのイベントが混ざって正しくテストできません。

試したこと

  1. ThreadLocalにRecorderを保持する
    • NavigateAsync内で別スレッドで処理される箇所があるため、別スレッドへRecorderが渡せない
  2. Recoderを一インスタンスだけしか生成できないよう制御し、イベントの発生するテストケースだけは同期的に実行されるように制御する
    • 根本問題であるマルチスレッドテストが限定的にしか実現できない
    • 今回と関係ないテストコードまで全部直す必要がある

悩み

  • テストをシングルスレッドに限定すると
    • テストの品質が低下する
    • テストの実行時間が伸びる
  • でもシングルスレッドでも十分高速に実行されるんだからシングルスレッドでもよくね?
  • そもそもPrismのコードってマルチスレッドで動作させるケースってそんなにあるのかな?

などなど、でもマルチスレッド化してほしいと言われちゃってるし

https://github.com/PrismLibrary/Prism/pull/942#issuecomment-280568542

そもそも、イベントが発生したかどうか?だけテストすれば良くて、発生順をテストする必要があるのか?というのも問われていますが、現時点でもDeepLinkの動作時のイベント発行順序に、私は違和感があります。

https://github.com/PrismLibrary/Prism/pull/942#issuecomment-280649615

可能ならイベント順もテストすべきだと私は思っています。

助けてほしいこと

つまるところ、タイトルは置いておいてやりたいことは以下の通りです。

  • PrismのNavigationAsyncのテストケースにて、発行されたイベント順を正しくテストしたい
    • INavigationAwareなどのイベントの発行順
    • View及びViewModelのコンストラクタの実行順
  • これらをマルチスレッドで適切に動かしたい

「該当のテストというコンテキスト内だけで、スレッド関係なく情報を共有する方法」 ってないですか?
両立は無理だろって明確な否定でもいいですので、誰か助けてくれませんか?
ここにブログへのコメントでおTwitterでも、なんならGithubの議論に参加してくれても嬉しいです。

github.com

ヘルプミー&Help Prism!