当我逐个执行测试时,所有测试都有效,并且没有问题。但是当我一起执行它们时,我会遇到NullReferenceException
和SemaphoreFullException
等问题。
经过大量挖掘后,我注意到我的测试线程似乎互相干扰:即使在测试结束后,(重复?)背景调用似乎在执行另一个测试时仍在继续。
有问题的应用程序是从通用应用程序单元测试项目执行的WinRT组件。您将在下面看到的所有呼叫都是异步执行的。为了给我的backgroundtasks完成时间(如果测试继续,同时假设等待异步后台任务,它将在测试结束时关闭)我让主线程休眠一段适当的时间
new ManualResetEvent(false).WaitOne(millisecondsTimeout: 60 * 1000);
此外,我还为拆解方法添加了一个睡眠,以确保一切都已完成:
[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");
}
我相信即使测试完成,该方法调用的原因仍然落后。这怎么可能?如何防止测试与彼此交织在一起?
如果这不是原因,那是什么?
答案 0 :(得分:1)
这有些令人反感,因为事实证明我的想法是错误。
我错误地认为我可以在EventBus
(传输OnSuccesfulLogin
)static
中制作事件,因为它们并不是要动态注册听众。
由于static
元素保存在 AppDomain 级别,这意味着每次执行我的测试时都会将自己的连接添加到已经存在的静态侦听器,这会导致调用的增量累积。
通过使EventBus
为具有实例字段的非静态类型,所有测试现在都按预期运行。
感谢@NETscape让我走上了正确的道路。
答案 1 :(得分:0)
单元测试异步方法和问题的问题:
Async Unit Tests, Part 1: The Wrong Way
解决方案
Async Unit Tests, Part 2: The Right Way
示例:
[TestMethod]
public async void FourDividedByTwoIsTwoAsync()
{
int result = await MyClass.Divide(4, 2);
Assert.AreEqual(2, result);
}