.net 如果TestInitialize是异步方法,则执行上下文(AsyncLocal)将丢失值

rjzwgtxy  于 2023-03-13  发布在  .NET
关注(0)|答案(1)|浏览(127)

考虑以下最小重现示例(.NET 7、MSTest 2.2.10):

[TestClass]
public class UnitTest1
{
    private AsyncLocal<string> _local = new AsyncLocal<string>();

    [TestInitialize]
    public async Task Init()
    {
        _local.Value = "SomeValue";
        Console.WriteLine(_local.Value != null); // yields True

        await Task.FromResult(0);
        Console.WriteLine(_local.Value != null); // yields True
    }

    [TestMethod]
    public void TestMethod1()
    {
        Console.WriteLine(_local.Value != null); // expected True, actual False

    }

    [TestCleanup]
    public void Cleanup()
    {
        Console.WriteLine(_local.Value != null); // expected True, actual False
    }
}

如果我从测试初始化器中删除行await Task.FromResult(0),并将Init声明为public void Init(),那么一切都按预期工作(所有WriteLines输出True)。
换句话说:当(且仅当)测试初始化器是async时,测试初始化器中设置的AsyncLocal值会丢失。这对我的用例来说很不方便,因为我正在测试的类内部使用AsyncLocal,因此我不能使用TestInitialize来初始化它。
我知道我可以通过使测试初始化器同步(并将所有异步操作 Package 在Task.Run(...).Result中)来解决这个问题。这是预期行为还是我在MSTest中发现了错误?

3pmvbmvn

3pmvbmvn1#

AsyncLocal<T>是为代码作用域的局部变量设计的,也就是说,如果A()设置了一个AsyncLocal<T>.Value,然后调用B(),那么B()应该会看到那个值。
在这种情况下,TestInitialize方法不调用TestMethod方法,因此尝试在TestInitialize中设置AsyncLocal<T>是不正确的。相反,应在每个单元测试的arrange部分设置它。如果设置很复杂,则使用helper方法生成值,然后在每个单元测试的arrange部分设置它。
如果我从测试初始化器中删除wait Task.FromResult(0)行,并将Init声明为public void Init(),那么一切都按预期工作(所有WriteLines输出True)。
这碰巧奏效了,但并不保证。发生的是这样的:setting an AsyncLocal<T> from a synchronous method modifies the logical call context in the closest async method further up the call stack(更多细节在我的博客上)。我建议不要依赖这种行为,因为它经常令人惊讶。
这是预期行为还是我在MSTest中发现了错误?
这是预期行为,具体来说,AsyncLocal<T>的预期行为;这和微软科技测试没有任何关系。

相关问题