异步测试在自己的测试完成后执行其他测试中的方法
本文关键字:测试 其他 方法 执行 自己的 异步 | 更新日期: 2023-09-27 18:12:54
当我一个接一个地执行测试时,所有的测试都工作,没有问题。但是,当我同时执行它们时,我会遇到NullReferenceException
和SemaphoreFullException
这样的问题。
经过大量的挖掘,我注意到我的测试线程似乎相互干扰:即使在测试结束后,(重复的?)后台调用似乎在执行另一个测试时继续。
所讨论的应用程序是从通用应用单元测试项目中执行的WinRT组件。您将在下面看到的所有调用都是异步执行的。为了给我的后台任务完成的时间(如果测试继续,而假定等待异步后台任务,它将关闭一旦它达到测试的结束),我把主线程睡眠一个适当的时间与
new ManualResetEvent(false).WaitOne(millisecondsTimeout: 60 * 1000);
另外,我还为teardown方法添加了一个sleep,以确保一切都完成了:
[TestCleanup]
public void Cleanup()
{
new ManualResetEvent(false).WaitOne(millisecondsTimeout: 60 * 1000);
}
为了诊断到目前为止的情况,我查看了测试的执行计划,由于它们非常相似,我只是在代码通过的每个相关位置放置了Debug.WriteLine(session.GetHashCode())
。下面您可以在mscorlib、线程结束消息和符号加载消息中找到从FileNotFoundExceptions
中剥离的结果。
所讨论的Session
对象是由用户创建并将其传递给Core
,然后将其传递给MainApi
和StorageApi
的对象。前者将会话对象封装到Dispatcher
中,并将该分派器传递给UserController
,而StorageApi
只是将会话对象传递给StorageController
,然后再传递给DatabaseController
。
在整个层次结构中使用和传递相同的会话对象,并且所有对象都使用在其位置可用的引用。
信号量在创建数据库时起作用。在EventBus
中有一个属性
internal static SemaphoreSlim TablesCreatedSemaphore = new SemaphoreSlim(0, 1);
,用于确保在创建表之前不会请求任何数据。这可以通过放置
await EventBus.TablesCreatedSemaphore.WaitAsync();
在执行SuccesfulLogin
事件和从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
来显示其名称,但显然最后一个没有这样做。两个带有
Exception
的测试是失败的测试,很可能是正确的异常。第一个测试只有它创建的对象的哈希码,第二个测试调用它的方法时使用的对象的哈希码作为前一个测试,最后一个测试调用它的方法时使用的哈希码来自前两个测试。
这些发现和我的假设符合我注意到的其他一些奇怪的行为(有时
Session
对象不会在一个地方有身份验证信息,但在其他地方确实有它,或者数据会在数据库中插入两次,尽管主键冲突-尽管我不知道为什么这甚至是可能的)。我认为这可能只是一个延迟刷新数据到输出窗口的问题,但它似乎有点及时。
更改测试的执行顺序仍然会导致前两个测试成功,而第三个测试失败。每次运行的行为都是相同的:每个测试对每个前一个测试的会话对象执行一个额外的调用。
[TestMethod]
public async Task Integration_DownloadAsset_WithValidId_DownloadsFileToLocalFolder()
{
Debug.WriteLine("Integration_DownloadAsset_WithValidId_DownloadsFileToLocalFolder");
var core = new Core(new Session());
await core.Api.GetApiKeyAsync(new GetApiKeyRequestParameters
{
// Authentication parameters
});
//TODO: why is this necessary?
new ManualResetEvent(false).WaitOne(millisecondsTimeout: 60 * 1000);
await core.Api.DownloadAssetAsync("1891");
// Allow the program to download the file
new ManualResetEvent(false).WaitOne(millisecondsTimeout: 60 * 1000);
var localFolder = await (await (await ApplicationData.Current.LocalFolder.GetFolderAsync("Data")).GetFolderAsync("AccountData")).GetFolderAsync("Files");
var fileNames = (await localFolder.GetFilesAsync()).Select(x => x.Name).ToList();
CollectionAssert.Contains(fileNames, "1891");
}
<标题>我相信原因是,方法调用以某种方式留在后面,即使测试已经完成。这是怎么可能的,我怎样才能防止测试相互纠缠?
如果这不是原因,那是什么?
标题>标题>标题>这有点扫兴,因为原来是我的脑子出了问题。
我错误地认为我可以在EventBus
(传输OnSuccesfulLogin
) static
内创建事件,因为它们不意味着动态注册侦听器。
由于static
元素保存在AppDomain级别上,这意味着对于我的每次测试执行都将向已经存在的静态侦听器添加自己的连接,从而导致调用的增量积累。
通过将EventBus
设置为具有实例字段的非静态类型,现在所有测试都可以正常运行。
感谢@NETscape让我走上了正确的道路。
单元测试异步方法的问题和陷阱:
异步单元测试,第1部分:错误的方法
解决方案异步单元测试,第2部分:正确的方法
- 安装AsyncUnitTests-MSTest NuGet包到你的测试中项目。
- 添加using Nito.AsyncEx.UnitTests;线。
- 改变你[TestClass] attribute to [AsyncTestClass].
的例子:
[TestMethod]
public async void FourDividedByTwoIsTwoAsync()
{
int result = await MyClass.Divide(4, 2);
Assert.AreEqual(2, result);
}