【发布时间】:2019-07-19 23:32:50
【问题描述】:
我正在使用异步 xUnit 测试,但我注意到传递行为不一致:
public async Task FetchData()
{
//Arrange
var result = await arrangedService.FetchDataAsync().ConfigureAwait(false);
//Assert
}
我已经检查了这个测试执行的调用堆栈,并验证了我的所有库代码在每个任务之后都调用了.ConfigureAwait(false)。然而,尽管如此,这个测试和其他测试在执行Run All 时会间歇性地失败,但是当我在调试器上运行时通过断言和手动检查。很明显,我没有正确地做某事。我已经尝试在测试本身中删除对ConfigureAwait(false) 的调用,以防有特殊的xUnit 同步上下文,但它没有改变任何东西。以一致的方式测试异步代码的最佳方法是什么?
编辑好的,这是我尝试创建一个正在运行的代码的超级简化示例,以提供正在发生的事情的示例:
using Graph = Microsoft.Azure.ActiveDirectory.GraphClient;
public async Task FetchData()
{
var adUsers = baseUsers //IEnumerable<Graph.User>
.Cast<Graph.IUser>()
.ToList();
var nextPageUsers = Enumerable
.Range(GoodIdMin, GoodIdMax)
.Select(number => new Graph.User
{
Mail = (-number).ToString()
})
.Cast<Graph.IUser>()
.ToList();
var mockUserPages = new Mock<IPagedCollection<Graph.IUser>>();
mockUserPages
.Setup(pages => pages.MorePagesAvailable)
.Returns(true);
mockUserPages
.Setup(pages => pages.CurrentPage)
.Returns(new ReadOnlyCollection<Graph.IUser>(adUsers));
mockUserPages
.Setup(pages => pages.GetNextPageAsync())
.ReturnsAsync(mockUserPages.Object)
.Callback(() =>
{
mockUserPages
.Setup(pages => pages.CurrentPage)
.Returns(new ReadOnlyCollection<Graph.IUser>(nextPageUsers));
mockUserPages
.Setup(pages => pages.MorePagesAvailable)
.Returns(false);
});
var mockUsers = new Mock<Graph.IUserCollection>();
mockUsers
.Setup(src => src.ExecuteAsync())
.ReturnsAsync(mockUserPages.Object);
var mockGraphClient = new Mock<Graph.IActiveDirectoryClient>();
mockGraphClient
.Setup(src => src.Users)
.Returns(mockUsers.Object);
var mockDbUsers = CreateBasicMockDbSet(baseUsers.Take(10)
.Select(user => new User
{
Mail = user.Mail
})
.AsQueryable());
var mockContext = new Mock<MyDbContext>();
mockContext
.Setup(context => context.Set<User>())
.Returns(mockDbUsers.Object);
var mockGraphProvider = new Mock<IGraphProvider>();
mockGraphProvider
.Setup(src => src.GetClient()) //Creates an IActiveDirectoryClient
.Returns(mockGraphClient.Object);
var getter = new UserGetter(mockContext.Object, mockGraphProvider.Object);
var result = await getter.GetData().ConfigureAwait(false);
Assert.True(result.Success); //Not the actual assert
}
这是在var result = ... 行上执行的代码:
public UserGetterResult GetData()
{
var adUsers = await GetAdUsers().ConfigureAwait(false);
var dbUsers = Context.Set<User>().ToList(); //This is the injected context from before
return new UserGetterResult //Just a POCO
{
AdUsers = adUsers
.Except(/*Expression that indicates whether
or not this user is in the database*/)
.ProjectTo<User>()
.ToList(),
DbUsers = dbUsers.ProjectTo<User>().ToList() //Automapper 6.1.1
};
}
private async Task<List<User>> GetAdUsers()
{
var userPages = await client //Injected IActiveDirectoryClient from before
.Users
.ExecuteAsync()
.ConfigureAwait(false);
var users = userPages.CurrentPage.ToList();
while(userPages.MorePagesAvailable)
{
userPages = await userPages.GetNextPageAsync().ConfigureAwait(false);
users.AddRange(userPages.CurrentPage);
}
return users;
}
代码的目的是获取AD中但不在数据库中的用户列表和数据库中的用户列表。
EDIT EDIT由于我忘记将其包含在原始更新中,因此错误都发生在调用 `IUserCollection.ExecuteAsync() 时。
【问题讨论】:
-
如果您的测试间歇性地失败,那么很可能是某个地方的竞争条件。如果您找到一种方法来始终如一地调试它们,那么您将被全球开发人员视为英雄
-
@Nkosi 好的,我已尽我所能完整地重新创建它。
-
@KevinGosse 我怀疑这是一个竞争条件,因为所有模拟服务都包含在测试范围内,即使它们使用线程安全读取对象,如 List
。 -
快速提问,我假设被测代码在生产环境中工作?
-
抱歉回复晚了,通过典型用例手动测试代码一直运行良好。
标签: c# asynchronous moq xunit