C# 异步测试在自己的测试完成后在其他测试中执行方法

C# 异步测试在自己的测试完成后在其他测试中执行方法,c#,visual-studio,asynchronous,visual-studio-2013,integration-testing,C#,Visual Studio,Asynchronous,Visual Studio 2013,Integration Testing,当我一个接一个地执行我的测试时,所有的测试都可以工作,没有问题。然而,当我同时执行它们时,我会遇到类似NullReferenceException和semaphorivelexception的问题 经过大量挖掘之后,我注意到我的测试线程似乎相互干扰:即使在测试完成之后,当另一个测试正在执行时,(重复的?)后台调用似乎仍在继续 所讨论的应用程序是从通用应用程序单元测试项目执行的WinRT组件。下面将看到的所有调用都是异步执行的。为了给我的后台任务一个完成的时间(如果测试在等待异步后台任务的同时继续

当我一个接一个地执行我的测试时,所有的测试都可以工作,没有问题。然而,当我同时执行它们时,我会遇到类似
NullReferenceException
semaphorivelexception
的问题

经过大量挖掘之后,我注意到我的测试线程似乎相互干扰:即使在测试完成之后,当另一个测试正在执行时,(重复的?)后台调用似乎仍在继续

所讨论的应用程序是从通用应用程序单元测试项目执行的WinRT组件。下面将看到的所有调用都是异步执行的。为了给我的后台任务一个完成的时间(如果测试在等待异步后台任务的同时继续进行,它将在测试结束后关闭),我使用

new ManualResetEvent(false).WaitOne(millisecondsTimeout: 60 * 1000);
此外,我还为“拆卸”方法添加了睡眠,以确保完成所有操作:

[TestCleanup]
public void Cleanup()
{
   new ManualResetEvent(false).WaitOne(millisecondsTimeout: 60 * 1000);
}
为了诊断到目前为止我所做的工作,我查看了测试的执行计划,由于它们非常相似,我只需在代码通过的每个相关位置放置一个
Debug.WriteLine(session.GetHashCode())
。在下面,您可以找到从mscorlib、线程结束消息和符号加载消息中的
FileNotFoundExceptions
中剥离的结果

这个有问题的
会话
对象是由用户创建并将其传递给
核心
,然后再传递给
MainApi
StorageApi
。前者将其包装到
调度程序中
并将该调度程序传递到
UserController
,而
StorageApi
只是将会话对象传递到
StorageController
,然后再传递到
DatabaseController

在整个层次结构中,使用并传递相同的会话对象,所有对象都使用其所在位置的引用

当创建数据库时,信号量起作用。在
EventBus
中有一个属性

internal static SemaphoreSlim TablesCreatedSemaphore = new SemaphoreSlim(0, 1);
用于确保在创建表之前不会请求任何数据。这是通过放置

await EventBus.TablesCreatedSemaphore.WaitAsync();
在执行
successfullogin
事件和从API请求数据并放置

EventBus.TablesCreatedSemaphore.Release();
就在创建最后一个表的位置之后

Integration_DownloadAsset_WithInvalidId_ThrowsSomeException
*************************************************
                   NEW EXECUTION
*************************************************
Core (ctor): 53578018
Main Api (ctor): 53578018
Api dispatcher (ctor): 53578018
UserController (ctor): 53578018
Storage Api (ctor): 53578018
Storage controller (ctor): 53578018
Database controller (ctor): 53578018
User controller (GetApiKeyAsync:pre-call): 53578018
Session (IsLoggedIn): 53578018
User controller (GetApiKeyAsync:pre-successfullogin-event): 53578018
Storage controller (CreateFolderStructure): 53578018
Database Controller(CreateDatabase): 53578018
Session (IsLoggedIn): 53578018
Session (IsLoggedIn): 53578018
Database controller (ctor): 53578018



Integration_DownloadAsset_WhenFileAlreadyExists_IsLocalReturnsTrue
A first chance exception of type 'System.Exception' occurred in mscorlib.dll


Integration_Login_WithValidLogin_RemovesUnusedAssetsFromFolder
A first chance exception of type 'System.Exception' occurred in mscorlib.dll

Integration_DownloadAsset_WithValidId_DownloadsFileToLocalFolder
*************************************************
                   NEW EXECUTION
*************************************************
Core (ctor): 20039337
Main Api (ctor): 20039337
Api dispatcher (ctor): 20039337
UserController (ctor): 20039337
Storage Api (ctor): 20039337
Storage controller (ctor): 20039337
Database controller (ctor): 20039337
User controller (GetApiKeyAsync:pre-call): 20039337
Session (IsLoggedIn): 20039337
User controller (GetApiKeyAsync:pre-successfullogin-event): 20039337
Storage controller (CreateFolderStructure): 53578018
Storage controller (CreateFolderStructure): 20039337
Database Controller(CreateDatabase): 53578018
Database Controller(CreateDatabase): 20039337
Session (IsLoggedIn): 20039337
Session (IsLoggedIn): 20039337
Database controller (ctor): 20039337

*************************************************
                   NEW EXECUTION
*************************************************
Core (ctor): 20995649
Main Api (ctor): 20995649
Api dispatcher (ctor): 20995649
UserController (ctor): 20995649
Storage Api (ctor): 20995649
Storage controller (ctor): 20995649
Database controller (ctor): 20995649
User controller (GetApiKeyAsync:pre-call): 20995649
Session (IsLoggedIn): 20995649
User controller (GetApiKeyAsync:pre-successfullogin-event): 20995649
Storage controller (CreateFolderStructure): 53578018
Storage controller (CreateFolderStructure): 20039337
Database Controller(CreateDatabase): 53578018
Database Controller(CreateDatabase): 20039337
Storage controller (CreateFolderStructure): 20995649
Database Controller(CreateDatabase): 20995649
Session (IsLoggedIn): 20995649
A first chance exception of type 'System.Threading.SemaphoreFullException' occurred in mscorlib.dll
A first chance exception of type 'System.Threading.SemaphoreFullException' occurred in mscorlib.dll
笔记
  • 每个测试都通过将其打印到
    Debug.WriteLine
    来显示其名称,但最后一个测试显然没有这样做

  • 带有
    异常的两个测试都是失败的测试,很可能是正确的异常

  • 第一个测试只包含它创建的对象的hashcode,第二个测试使用hashcode作为上一个测试的对象调用它的方法,最后一个测试使用前两个测试中的hashcode调用它的方法

  • 这些发现和我的假设与我注意到的其他一些奇怪的行为一致(有时,
    会话
    对象在一个地方没有身份验证信息,但在其他地方有身份验证信息,或者尽管主键冲突,数据仍会在数据库中插入两次—尽管我不知道为什么一开始就可能这样)

  • 我曾考虑过,这可能只是将数据延迟刷新到输出窗口的问题,但这似乎有点时机成熟

  • 更改测试的执行顺序仍然会导致前两个测试成功,第三个测试失败。每次运行的行为都是相同的:每个测试都会对前一个测试的会话对象执行额外的调用

示例测试 问题: 我相信原因是,即使测试已经完成,方法调用也会以某种方式留在后面。这是怎么可能的?我如何防止测试相互纠缠


如果这不是原因,那么单元测试异步方法的问题是什么

解决方案

  • 将安装到测试中 项目
  • 使用Nito.AsyncEx.UnitTests;行添加一个
  • 换衣服 [TestClass]属性设置为[AsyncTestClass]
例如:

[TestMethod]
public async void FourDividedByTwoIsTwoAsync()
{
    int result = await MyClass.Divide(4, 2);
    Assert.AreEqual(2, result);
}

这有点让人扫兴,因为事实证明,我的大脑就是那个虫子

我错误地认为我可以在
EventBus
(它传输
onsuccessfullogin
静态
中生成事件,因为它们不是用来动态注册侦听器的

由于
static
元素保存在AppDomain级别,这意味着每次执行我的测试都会向已经存在的静态侦听器添加自己的连接,从而导致调用的增量累积

通过使
EventBus
成为带有实例字段的非静态类型,所有测试现在都可以正常运行


感谢@NETscape让我走上了正确的道路。

这些是真正的单元测试吗?还是真正的集成测试?因为如果它们是单元测试,你的API将被截短,它将同步执行,你就不会有这个问题。在一个适当的单元测试中,你几乎从来没有涉及多个线程。啊,错误地标记了它。它们是集成测试(它是一个API包装,集成就是它的全部)因此,这可能不是解决方案,但其中关于异步测试的说明与我等待线程的情况有关。您能否将其包含在您的答案中,这样它就不仅仅是几个链接?@Jeroenvanevel希望它能起作用。更新了答案,以包括博客文章的重要部分。AsyncUnitTests包仅适用于VS2010;而op正在使用VS2013。
[TestMethod]
public async void FourDividedByTwoIsTwoAsync()
{
    int result = await MyClass.Divide(4, 2);
    Assert.AreEqual(2, result);
}