0

私はVS2010を使用しており、MSTestで単体テストを作成しています。私のプロジェクトでは、WPF、MVVM、およびPRISMフレームワークを使用しています。また、Moqを使用してインターフェースをモックしています。

コマンドとリスト内の選択されたアイテムの間の相互作用をテストしています。インタラクションは、MVVMパターンに従ってViewModelにカプセル化されます。基本的に、SelectedDatabaseが設定されている場合、コマンドでCanExecuteを発生させたいと思います。私はこの振る舞いのためにこのテストを書きました:

public void Test()
{
    var databaseService = new Mock<IDatabaseService>();
    var databaseFunctionsController = new Mock<IDatabaseFunctionsController>();

    // Create the view model
    OpenDatabaseViewModel viewModel
         = new OpenDatabaseViewModel(databaseService.Object, databaseFunctionsController.Object);

    // Mock up the database and its view model
    var database = TestHelpers.HelpGetMockIDatabase();
    var databaseViewModel = new DatabaseViewModel(database.Object);

    // Hook up the can execute changed event
    var resetEvent = new AutoResetEvent(false);
    bool canExecuteChanged = false;
    viewModel.OpenDatabaseCommand.CanExecuteChanged += (s, e) =>
        {
             resetEvent.Set();
             canExecuteChanged = true;
        };

    // Set the selected database
    viewModel.SelectedDatabase = databaseViewModel;

    // Allow the event to happen
    resetEvent.WaitOne(250);

    // Check that it worked
    Assert.IsTrue(canExecuteChanged,
        "OpenDatabaseCommand.CanExecuteChanged should be raised when SelectedDatabase is set");
}

のプロパティは次OpenDatabaseViewModelSelectDatabaseとおりです。

    public DatabaseViewModel SelectedDatabase
    {
        get { return _selectedDatabase; }
        set
        {
            _selectedDatabase = value;
            RaisePropertyChanged("SelectedDatabase");
            // Update the can execute flag based on the save
            ((DelegateCommand)OpenDatabaseCommand).RaiseCanExecuteChanged();
        }
    }

また、ビューモデルについて:

    bool OpenDatabaseCanExecute()
    {
        return _selectedDatabase != null;
    }

TestHelpers.HelpGetMockIDatabase()IDatabaseいくつかのプロパティが設定されたモックを取得するだけです。

このテストは、VS2010からテストを実行すると合格しますが、サーバーで自動ビルドの一部として実行すると失敗します。AutoResetEvent問題を解決するためにを入れましたが、効果がありませんでした。

自動テストでnoisolationMSTestコマンドラインのフラグが使用されていることがわかったので、それを削除しました。ただし、これにより「合格」が1回発生し、次は「不合格」になります。

私はこれらすべてにおいて重要な何かを見逃していると思いますが、それが何であるかを理解することはできません。誰かが私が間違っていることを教えてくれるのを手伝ってもらえますか?

4

2 に答える 2

1

更新しました

SelectedDatabaseコードが失敗する可能性のある他の唯一の場所は、プロパティのスニペットのこれらの2行です。

        RaisePropertyChanged("SelectedDatabase");
        // Update the can execute flag based on the save
        ((DelegateCommand)OpenDatabaseCommand).RaiseCanExecuteChanged();

いくつかの問題を抱えている人もいますがRaisePropertyChanged()、それはマジックストリングの使用です。しかし、これはおそらくあなたの当面の問題ではありません。それでも、マジックストリングの依存関係を削除する方法をたどりたい場合は、これらのリンクを確認できます。

WPF、MVVM、およびRaisePropertyChanged @WilberBeastMVVM
-RaisePropertyChangedコードを混乱に変える

このRaiseCanExecuteChanged()メソッドはもう1つの疑わしいものであり、PRISMでドキュメントを検索すると、このメソッドがUIスレッドでイベントをディスパッチすることを想定していることがわかります。mstestから、UIスレッドがテストのディスパッチに使用されているという保証はありません。

DelegateCommandBase.RaiseCanExecuteChanged @ MSDN

RaiseCanExecuteChanged()その周りにtry/catchブロックを追加し、が呼び出されたときに例外がスローされるかどうかを確認することをお勧めします。次に進む方法を決定できるように、スローされた例外に注意してください。このイベントディスパッチをどうしてもテストする必要がある場合は、実際のテストを実行して終了する小さなWPF対応アプリ(またはおそらくSTAThreadコンソールアプリ)を作成し、テストでそのアプリを起動して結果を確認することを検討してください。これにより、mstestまたはビルドサーバーによって引き起こされる可能性のあるスレッドの問題からテストが分離されます。

オリジナル

このコードスニペットは疑わしいようです。イベントが別のスレッドから発生した場合、元のスレッドが割り当ての前に最初に待機を終了し、フラグが古い値で読み取られる可能性があります。

viewModel.OpenDatabaseCommand.CanExecuteChanged += (s, e) =>
    {
         resetEvent.Set();
         canExecuteChanged = true;
    };

ブロック内の行を次のように並べ替えることを検討してください。

viewModel.OpenDatabaseCommand.CanExecuteChanged += (s, e) =>
    {
         canExecuteChanged = true;
         resetEvent.Set();
    };

もう1つの問題は、待機が満たされているかどうかを確認しないことです。信号なしで250msが経過した場合、フラグはfalseになります。

WaitHandle.WaitOneを参照して、受け取る戻り値を確認し、シグナルのない出口の場合を処理するようにコードのこのセクションを更新してください。

// Allow the event to happen
resetEvent.WaitOne(250);

// Check that it worked
Assert.IsTrue(canExecuteChanged,
    "OpenDatabaseCommand.CanExecuteChanged should be raised when SelectedDatabase is set");
于 2012-10-16T18:14:47.873 に答える
1

この単体テストで何が起こっているのかを説明する答えを見つけました。当時、私が気付いていなかった複雑な要因が他にもありました。これらの詳細は、関連があるとは思わなかったため、元の質問には含めませんでした。

コードの問題で説明されているビュー モデルは、WinForms との統合を使用しているプロジェクトの一部です。ElementHostの子として PRISM シェルをホストしています。Stackoverflow How to use Prism within an ElementHostに関する質問への回答に続いて、これを追加して適切な を作成しますApplication.Current

public class MyApp : System.Windows.Application
{
}

if (System.Windows.Application.Current == null)
{
    // create the Application object
    new MyApp();
}

上記のコードは、問題の単体テストでは実行されません。ただし、事前に実行されていた他の単体テストで実行されており、すべて/noisolationMSTest.exe でフラグを使用して一緒に実行されていました。

なぜこれが重要なのですか?さて、次の結果として呼び出される PRISM コードに埋もれています。

((DelegateCommand)OpenDatabaseCommand).RaiseCanExecuteChanged();

内部クラスMicrosoft.Practices.Prism.Commands.WeakEventHandlerには次のメソッドがあります。

public static DispatcherProxy CreateDispatcher()
{
    DispatcherProxy proxy = null;
#if SILVERLIGHT
    if (Deployment.Current == null)
        return null;

    proxy = new DispatcherProxy(Deployment.Current.Dispatcher);
#else
    if (Application.Current == null)
        return null;

    proxy = new DispatcherProxy(Application.Current.Dispatcher);
#endif
    return proxy;

}

次に、ディスパッチャーを使用して、問題のイベント ハンドラーを呼び出します。

private static void CallHandler(object sender, EventHandler eventHandler)
{
    DispatcherProxy dispatcher = DispatcherProxy.CreateDispatcher();

    if (eventHandler != null)
    {
        if (dispatcher != null && !dispatcher.CheckAccess())
        {
            dispatcher.BeginInvoke((Action<object, EventHandler>)CallHandler, sender, eventHandler);
        }
        else
        {
            eventHandler(sender, EventArgs.Empty);
        }
    }
}

そのため、現在のアプリケーションの UI スレッドがあれば、そのイベントをディスパッチしようとします。それ以外の場合は、eventHandler を呼び出すだけです。問題の単体テストでは、これによりイベントが失われました。

さまざまなことを試した後、私が落ち着いた解決策は、単体テストをさまざまなバッチに分割することでした。そのため、上記の単体テストは で実行されApplication.Current == nullます。

于 2012-10-19T20:12:17.873 に答える