2017-01-11 1 views
1

Ich schreibe einen kleinen Wrapper (MyWrapper) für den Einsatz in Komponententests. Sein Zweck besteht darin, Testcode mit einem try-catch zu umhüllen, um eine bestimmte Ausnahme zu erfassen (MySpecialException) und dann den Test zu ignorieren.Wie kann verhindert werden, dass ein Aktionsparameter ein asynchrones Lambda ist?

Warum sollte ich das für diese Frage nicht relevant sein.

den Code unten gegeben, wie kann ich andere aus Leiten eines Action und mit Asynchron wie dies zu verhindern? Oder mit anderen Worten: Wie zwinge ich sie stattdessen, MyWrapper.ExecuteAsync(Func<Task>) zu verwenden?


using System; 
using System.Threading.Tasks; 
using NUnit.Framework; 

namespace PreventAsyncActionLambdaExample 
{ 
    [TestFixture] 
    public class Example 
    { 
     [Test] 
     public async Task ExampleTest() 
     { 
      // How do I prevent others from passing an action and using async like this? 
      // Or in other words: How do I force them to use MyWrapper.ExecuteAsync(Func<Task>) instead? 
      MyWrapper.Execute(async() => 
      { 
       var cut = new ClassUnderTest(); 

       await cut.DoSomethingAsync(); 

       Assert.Fail("Problem: This line will never be reached"); 
      }); 
     } 
    } 

    public static class MyWrapper 
    { 
     // This method SHOULD NOT, BUT WILL be used in this example 
     public static void Execute(Action action) 
     { 
      try 
      { 
       action(); 
      } 
      catch (MySpecialException) 
      { 
      Assert.Ignore("Ignored due to MySpecialException"); 
      } 
     } 

     // This method SHOULD BE USED in this example, BUT WILL NOT be used. 
     public static async Task ExecuteAsync(Func<Task> func) 
     { 
      try 
      { 
       await func(); 
      } 
      catch (MySpecialException) 
      { 
       Assert.Ignore("Ignored due to MySpecialException"); 
      } 
     } 
    } 

    public class MySpecialException : Exception 
    { 
     // This is another exception in reality which is not relevant for this example 
    } 

    public class ClassUnderTest 
    { 
     public Task DoSomethingAsync() 
     { 
      return Task.Delay(20); // Simulate some work 
     } 
    } 
} 
+1

Es gibt so etwas wie eine 'async' Methode nicht. Dieses Schlüsselwort ist nur syntaktischer Zucker, der es dem Compiler ermöglicht, eine Zustandsmaschine zu erzeugen. Das Problem hierbei ist, dass das Lambda eine 'async void' ist, dh nur eine' void' Methode, die nicht erwartet werden kann. Es ist * nur eine Aktion, was die Methodenauflösung betrifft. Wenn es sich um eine Async-Task handelt, können Sie 'Execute' nicht verwenden. –

+0

Um es anders auszudrücken: Async() => {}' wie jede Async-Lücke kann nicht erwartet werden, also ist es nicht Es ist nicht möglich, 'ExecuteAsync' zu verwenden. –

+0

@ PanagiotisKanavos Aber das ist, was ich in anderen Tests tue so: 'erwarten MyWrapper.Execute (async() => {...}' – ChrisM

Antwort

2

Ich habe Angst, dass Sie nicht wirklich diese zum Zeitpunkt der Kompilierung verhindern können, aber man könnte eine weitere Überlastung schreiben, die in diesem Fall abgeholt werden ihnen sagen, dass sie ExecuteAsync verwenden sollen statt:

public static Task Execute(Func<Task> action) 
{ 
    throw new Exception("Please use the ExecuteAsync(Func<Task> func) method instead if you will be passing async lambdas"); 
} 
+0

Diese Signatur ist eine 'async void', dh eine' void', dh eine Aktion. ' Execute (Func ) 'wird nicht aufgerufen, weil' Execute (Action) 'mit der Signatur übereinstimmt. Methoden mit 'async void' können trotzdem nicht erwartet werden. Semantisch ist nichts falsch mit dem Code - warum ExecuteAsync wenn Lambda kann nicht erwartet werden –

+0

Nein, ich kann das nicht tun, weil der Compiler beschwert, dass "eine Methode mit dieser Signatur bereits deklariert ist." – ChrisM

+0

@ChrisM der Compiler würde nur beschweren, wenn eine 'Execute (Func )' Methode bereits existiert. Ihr Code zeigt jedoch nur 'ExecuteAsync'. Hast du ein anderes 'Execute' definiert? Denken Sie daran, dass "async" nur syntaktischer Zucker ist, so dass es die Überladungsauflösung nicht beeinflusst. –

0

Wie in anderen Antworten erwähnt, ich glaube nicht, dass Sie es in der Kompilierung verhindern können. Sie können jedoch eine hacky Abhilfe schaffen und eine Ausnahme auslösen. Inspiriert von this answer. Es ist vielleicht keine gute Lösung, aber es könnte zumindest den Test fehlschlagen lassen.

public static bool IsThisAsync(Action action) 
{ 
    return action.Method.IsDefined(typeof(AsyncStateMachineAttribute), 
     false); 
} 

// This method SHOULD NOT, BUT WILL be used in this example 
public static void Execute(Action action) 
{ 
    try 
    { 
     if (IsThisAsync(action)) 
     { 
      Console.WriteLine("Is async"); 
      throw new ArgumentException("Action cannot be async.", nameof(action)); 
     } 
     else 
     { 
      Console.WriteLine("Is not async"); 
     } 

     action(); 
    } 
    catch (MySpecialException) 
    { 

    } 
} 

und Prüfungen:

[TestClass] 
public class MyWrapperTests 
{ 
    // Will not pass 
    [TestMethod] 
    public void ShouldAllowAsyncAction() 
    { 
     // This will throw an exception 
     MyWrapper.Execute(async() => 
     { 
      Assert.IsTrue(true); 
      await Task.Run(() => 
      { 
       Console.WriteLine("Kind of async"); 
      }); 
     }); 
    } 

    // Will pass, since ArgumentException is expected. 
    [TestMethod] 
    [ExpectedException(typeof(ArgumentException))] 
    public void ShouldThrowArgumentExceptionWhenAsync() 
    { 
     // This will throw an exception. But that's expected. 
     MyWrapper.Execute(async() => 
     { 
      Assert.IsTrue(true); 
      await Task.Run(() => 
      { 
       Console.WriteLine("Kind of async"); 
      }); 
     }); 
    } 

    // Passes 
    [TestMethod] 
    public void ShouldAllowSyncAction() 
    { 
     MyWrapper.Execute(() => 
     { 
      Assert.IsTrue(true); 
     }); 
    } 
} 
Verwandte Themen