5

Visual Studio 2012と、同期コンテキストを必要とする非同期テストがあります。
ただし、MSTestのデフォルトの同期コンテキストはnullです。
同期コンテキストを持つWPF-またはWinForms-UIスレッドで実行されているものとしてテストしたいと思います。
同期コンテキストをテストスレッドに追加するための最良の方法は何ですか?

    [TestMethod]
    public async Task MyTest()
    {
        Assert.IsNotNull( SynchronizationContext.Current );
        await MyTestAsync();
        DoSomethingOnTheSameThread();
    }
4

4 に答える 4

9

SynchronizationContext私のAsyncExライブラリAsyncContextで:と呼ばれるシングルスレッドを使用できます。

[TestMethod]
public void MyTest()
{
  AsyncContext.Run(async () =>
  {
    Assert.IsNotNull( SynchronizationContext.Current );
    await MyTestAsync();
    DoSomethingOnTheSameThread();
  });
}

ただし、これは特定のUI環境を完全に偽造するわけではありません。たとえば、Dispatcher.CurrentDispatcher引き続きですnull。そのレベルの偽造が必要な場合はSynchronizationContext、元の非同期CTPの実装を使用する必要があります。テストに使用できる3つの実装が付属していSynchronizationContextます。1つは汎用(私のものと同様AsyncContext)、1つはWinForms用、もう1つはWPF用です。

于 2012-12-31T16:04:48.903 に答える
9

PanagiotisKanavosとStephenClearyからの情報を使用して、次のようなテストメソッドを記述できます。

    [TestMethod]
    public void MyTest()
    {
      Helper.RunInWpfSyncContext( async () =>
      {
        Assert.IsNotNull( SynchronizationContext.Current );
        await MyTestAsync();
        DoSomethingOnTheSameThread();
      });
    }

内部コードはWPF同期コンテキストで実行され、MSTestで使用されるすべての例外を処理します。HelperメソッドはStephenToubによるものです。

using System.Windows.Threading; // WPF Dispatcher from assembly 'WindowsBase'

public static void RunInWpfSyncContext( Func<Task> function )
{
  if (function == null) throw new ArgumentNullException("function");
  var prevCtx = SynchronizationContext.Current;
  try
  {
    var syncCtx = new DispatcherSynchronizationContext();
    SynchronizationContext.SetSynchronizationContext(syncCtx);

    var task = function();
    if (task == null) throw new InvalidOperationException();

    var frame = new DispatcherFrame();
    var t2 = task.ContinueWith(x=>{frame.Continue = false;}, TaskScheduler.Default);
    Dispatcher.PushFrame(frame);   // execute all tasks until frame.Continue == false

    task.GetAwaiter().GetResult(); // rethrow exception when task has failed 
  }
  finally
  { 
    SynchronizationContext.SetSynchronizationContext(prevCtx);
  }
}
于 2013-01-04T15:44:36.090 に答える
7

カスタムSynchronizationContextから派生したクラスを作成し、それを現在のコンテキストとしてSynchronizationContext.SetSynchronizationContextに登録できます。Stephen Toubの「Await、SynchronizationContext、およびConsole Apps」と「Await、SynchronizationContext、およびConsole Apps:Part2 」に関する投稿を読んでください。

カスタムSynchronizationContextは、非同期実行のコールバックを受け取るPostメソッドをオーバーライドするだけで済みます。それらをどのように実行するかはあなた次第です。

最初の投稿は、投稿されたすべてのアクションをキューに格納する同期コンテキストと、キューからアクションを取得して単一のスレッドで実行するブロッキングループを提供します。

于 2012-12-31T10:59:19.870 に答える
1

テスト実行でカスタムコードを挿入できる独自のテストメソッド属性を宣言することができます。これを使用して、[TestMethod]属性を独自の[SynchronizationContextTestMethod]に置き換えることができます。これにより、コンテキストセットを使用してテストが自動的に実行されます(VS2019でのみテストされます)。

public class SynchronizationContextTestMethodAttribute : TestMethodAttribute
{
    public override TestResult[] Execute(ITestMethod testMethod)
    {
        Func<Task> function = async () =>
        {
            var declaringType = testMethod.MethodInfo.DeclaringType;
            var instance = Activator.CreateInstance(declaringType);
            await InvokeMethodsWithAttribute<TestInitializeAttribute>(instance, declaringType);
            await (Task)testMethod.MethodInfo.Invoke(instance, null);
            await InvokeMethodsWithAttribute<TestCleanupAttribute>(instance, declaringType);
        };
        var result = new TestResult();
        result.Outcome = UnitTestOutcome.Passed;
        var stopwatch = Stopwatch.StartNew();
        try
        {
            RunInSyncContext(function);
        }
        catch (Exception ex)
        {
            result.Outcome = UnitTestOutcome.Failed;
            result.TestFailureException = ex;
        }
        result.Duration = stopwatch.Elapsed;
        return new[] { result };
    }

    private static async Task InvokeMethodsWithAttribute<A>(object instance, Type declaringType) where A : Attribute
    {
        if (declaringType.BaseType != typeof(object))
            await InvokeMethodsWithAttribute<A>(instance, declaringType.BaseType);

        var methods = declaringType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
        foreach (var methodInfo in methods)
            if (methodInfo.DeclaringType == declaringType && methodInfo.GetCustomAttribute<A>() != null)
            {
                if (methodInfo.ReturnType == typeof(Task))
                {
                    var task = (Task)methodInfo.Invoke(instance, null);
                    if (task != null)
                        await task;
                }
                else
                    methodInfo.Invoke(instance, null);
            }
    }

    public static void RunInSyncContext(Func<Task> function)
    {
        if (function == null)
            throw new ArgumentNullException(nameof(function));
        var prevContext = SynchronizationContext.Current;
        try
        {
            var syncContext = new DispatcherSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(syncContext);
            var task = function();
            if (task == null)
                throw new InvalidOperationException();

            var frame = new DispatcherFrame();
            var t2 = task.ContinueWith(x => { frame.Continue = false; }, TaskScheduler.Default);
            Dispatcher.PushFrame(frame);   // execute all tasks until frame.Continue == false

            task.GetAwaiter().GetResult(); // rethrow exception when task has failed
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(prevContext);
        }
    }
}
于 2020-07-10T12:25:28.620 に答える