C# 异步方法中的单元测试异常

C# 异步方法中的单元测试异常,c#,exception,async-await,vstest,C#,Exception,Async Await,Vstest,我试图在下面使用VSTest对一个异步方法进行单元测试。但是,测试通过了asynchmathsstatic.Divide(4,0)以及asynchmathsstatic.Divide(4,1)),即使只在第一种情况下引发了异常 [TestClass] public class UnitTest1 { [TestMethod] public void DivideTest1() { // Func<Task> action = async()=

我试图在下面使用VSTest对一个异步方法进行单元测试。但是,测试通过了
asynchmathsstatic.Divide(4,0)
以及
asynchmathsstatic.Divide(4,1))
,即使只在第一种情况下引发了异常

[TestClass]
public class UnitTest1
{
    [TestMethod]

    public void DivideTest1()
    {
        // Func<Task> action = async()=> {  AsyncMathsStatic.Divide(4, 0); };
        //action.Should().Throw<DivideByZeroException>();

        Assert.ThrowsExceptionAsync<DivideByZeroException>(async () => 
        AsyncMathsStatic.Divide(4, 0));
    }
}

public class AsyncMathsStatic
{
    public static async void Divide(int v1, int v2)
    {
        try
        {
            if (v1/v2 > 1)
            {
                // do something time consuming
            }   
        }
        catch (DivideByZeroException ex)
        {
            throw;
        }
    }
}
[TestClass]
公共类UnitTest1
{
[测试方法]
公共无效分割测试1()
{
//Func action=async()=>{asynchmathsstatic.Divide(4,0);};
//action.Should().Throw();
Assert.ThrowsExceptionAsync(异步()=>
AsynchMathsStatic.Divide(4,0));
}
}
公共类异步数学静态
{
公共静态异步void Divide(intv1,intv2)
{
尝试
{
如果(v1/v2>1)
{
//做一些耗时的事情
}   
}
捕获(除零例外)
{
投掷;
}
}
}

了解异步方法的工作原理很重要,可以了解这里发生了什么。所有异步方法开始同步运行,就像任何其他方法一样。但是,在对未完成的
任务
执行操作的第一个
等待
时,该方法返回。通常,它会返回自己的
任务
,然后调用者可以等待

但是如果方法签名是
async void
,那么它将不返回任何内容。此时,该方法尚未完成运行,但调用方法将永远不知道何时或是否完成

这可能就是这里发生的事情。方法在命中第一个
wait
时返回,并认为测试成功完成。它永远不会看到以后抛出的异常

修复方法是返回一个
任务
,因此可以等待该方法:

public static async Task Divide(int v1, int v2)

async void
的唯一合法用途是用于事件处理程序,因为您别无选择,只能将它们设置为
void
。但通常情况下,这也没问题,因为事件应该是“哦,顺便说一句”,事件处理程序的成功完成通常不会影响调用它的任何对象的进一步操作(但也有一些例外)。

了解异步方法的工作原理很重要,以了解这里发生了什么。所有异步方法开始同步运行,就像任何其他方法一样。但是,在对未完成的
任务
执行操作的第一个
等待
时,该方法返回。通常,它会返回自己的
任务
,然后调用者可以等待

但是如果方法签名是
async void
,那么它将不返回任何内容。此时,该方法尚未完成运行,但调用方法将永远不知道何时或是否完成

这可能就是这里发生的事情。方法在命中第一个
wait
时返回,并认为测试成功完成。它永远不会看到以后抛出的异常

修复方法是返回一个
任务
,因此可以等待该方法:

public static async Task Divide(int v1, int v2)

async void
的唯一合法用途是用于事件处理程序,因为您别无选择,只能将它们设置为
void
。但通常情况下,这也没问题,因为事件应该是“哦,顺便说一句”,事件处理程序的成功完成通常不会影响任何调用它的操作(但也有一些例外).

如果您的测试环境不允许使用异步测试方法,请使用以下方法:

       [TestMethod]
    public void TestMethod1()
    {
        Task.Run(async () =>
        {
           // test setup

            var result = await SomeAsyncTask();
            Assert.IsTrue(result.Id != 0, "Id not changed");

        }).GetAwaiter().GetResult();
    }
正如@Paulo所指出的,如果您的测试环境允许异步测试方法,那么您可以使用:

      [TestMethod]
    public async Task TestMethod1()
    {
       // test setup

        var result = await SomeAsyncMethod();
        Assert.IsTrue(result.Id != 0, "Id not changed");
    }

如果您的测试环境不允许使用异步测试方法,请使用以下方法:

       [TestMethod]
    public void TestMethod1()
    {
        Task.Run(async () =>
        {
           // test setup

            var result = await SomeAsyncTask();
            Assert.IsTrue(result.Id != 0, "Id not changed");

        }).GetAwaiter().GetResult();
    }
正如@Paulo所指出的,如果您的测试环境允许异步测试方法,那么您可以使用:

      [TestMethod]
    public async Task TestMethod1()
    {
       // test setup

        var result = await SomeAsyncMethod();
        Assert.IsTrue(result.Id != 0, "Id not changed");
    }

即使该方法是同步的,在未完成任务的第一次等待之前,执行始终是同步的。您的代码甚至没有
wait
s

async void
方法是不可等待的

未等待的任务由方法返回,就像该方法是
异步void

以下代码显示了所有这些情况:

static async Task Main()
{
    try
    {
        Console.WriteLine("Before calling "+ nameof(AsyncMathsStatic.Divide1));
        var t = AsyncMathsStatic.Divide1(4, 0);
        Console.WriteLine("After calling "+ nameof(AsyncMathsStatic.Divide1));
        await t;
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Exception thrown in " + nameof(Main));
    }

    try
    {
        Console.WriteLine("Before calling "+ nameof(AsyncMathsStatic.Divide2));
        var t = AsyncMathsStatic.Divide2(4, 0);
        Console.WriteLine("After calling "+ nameof(AsyncMathsStatic.Divide2));
        await t;
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Exception thrown in " + nameof(Main));
    }
}

public class AsyncMathsStatic
{
    public static async Task Divide1(int v1, int v2)
    {
        try
        {
            Console.WriteLine(nameof(Divide1) + ".1");
            await Task.Yield();
            Console.WriteLine(nameof(Divide1) + ".2");
            if (v1 / v2 > 1)
            {
                await Task.Yield();
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Exception thrown in " + nameof(Divide1));
            throw;
        }
    }
    public static async Task Divide2(int v1, int v2)
    {
        try
        {
            Console.WriteLine(nameof(Divide2) + ".1");
            await Task.CompletedTask;
            Console.WriteLine(nameof(Divide2) + ".2");
            if (v1 / v2 > 1)
            {
                await Task.Yield();
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Exception thrown in " + nameof(Divide2));
            throw;
        }
    }
}

/* Output
Before calling Divide1
Divide1.1
After calling Divide1
Divide1.2
Exception thrown in Divide1
Exception thrown in Main
Before calling Divide2
Divide2.1
Divide2.2
Exception thrown in Divide2
After calling Divide2
Exception thrown in Main
*/
这证明了,如果你的邮件与你发布的内容完全一致,你就应该抛出它


我在这里遗漏了什么吗?

即使该方法是同步的,在第一次等待未完成的任务之前,执行始终是同步的。您的代码甚至没有
wait
s

async void
方法是不可等待的

未等待的任务由方法返回,就像该方法是
异步void

以下代码显示了所有这些情况:

static async Task Main()
{
    try
    {
        Console.WriteLine("Before calling "+ nameof(AsyncMathsStatic.Divide1));
        var t = AsyncMathsStatic.Divide1(4, 0);
        Console.WriteLine("After calling "+ nameof(AsyncMathsStatic.Divide1));
        await t;
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Exception thrown in " + nameof(Main));
    }

    try
    {
        Console.WriteLine("Before calling "+ nameof(AsyncMathsStatic.Divide2));
        var t = AsyncMathsStatic.Divide2(4, 0);
        Console.WriteLine("After calling "+ nameof(AsyncMathsStatic.Divide2));
        await t;
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Exception thrown in " + nameof(Main));
    }
}

public class AsyncMathsStatic
{
    public static async Task Divide1(int v1, int v2)
    {
        try
        {
            Console.WriteLine(nameof(Divide1) + ".1");
            await Task.Yield();
            Console.WriteLine(nameof(Divide1) + ".2");
            if (v1 / v2 > 1)
            {
                await Task.Yield();
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Exception thrown in " + nameof(Divide1));
            throw;
        }
    }
    public static async Task Divide2(int v1, int v2)
    {
        try
        {
            Console.WriteLine(nameof(Divide2) + ".1");
            await Task.CompletedTask;
            Console.WriteLine(nameof(Divide2) + ".2");
            if (v1 / v2 > 1)
            {
                await Task.Yield();
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Exception thrown in " + nameof(Divide2));
            throw;
        }
    }
}

/* Output
Before calling Divide1
Divide1.1
After calling Divide1
Divide1.2
Exception thrown in Divide1
Exception thrown in Main
Before calling Divide2
Divide2.1
Divide2.2
Exception thrown in Divide2
After calling Divide2
Exception thrown in Main
*/
这证明了,如果你的邮件与你发布的内容完全一致,你就应该抛出它


我在这里遗漏了什么吗?

如图所示,代码应该通过测试。你没有展示什么?忽略编译器错误和使用
async void
会给您带来很多麻烦。无论是否存在异常,这两种情况下都可以通过测试。这就是全部代码。您关于async void的注释帮助我将其更改为async Task。如图所示,代码应该通过测试。你没有展示什么?忽略编译器错误和使用
async void
会给您带来很多麻烦。无论是否存在异常,这两种情况下都可以通过测试。这就是所有的代码。您关于async void的评论帮助我将其更改为async Task。为什么要使用
Task.Run
。您可以使用
async Task
而不是
void
方法作为测试方法。旧习惯。我已经发布了一些说明。为什么要使用
Task.Run
。您可以使用
async Task
而不是
void
方法作为测试方法。旧习惯。我已经发布了一些澄清。我希望引起报道。但是t中没有异步和可等待的东西