[Fact]
public async Task GetUserById_ReturnsUser_WhenUserExists()
{
// Arrange
var service = new UserService();
// Act
var user = await service.GetUserByIdAsync(1);
// Assert
Assert.NotNull(user);
Assert.Equal("Alice", user.Name);
}
var user = await service.GetUserByIdAsync(1); Assert.NotNull(user);
xUnit会自动等待`Task`完成,但开发者仍需显式`await`以保证断言作用于最终结果。
第二章:常见异步Assert陷阱深度剖析
2.1 忽略Task未等待导致的断言失效
在异步编程中,若未正确等待任务完成便执行断言,可能导致测试结果不可靠。
常见错误模式
开发者常误以为启动任务即会立即执行,忽视了其并发特性:
func TestAsyncOperation(t *testing.T) {
var result int
go func() { result = 42 }()
assert.Equal(t, 42, result) // 断言可能失败
}
上述代码中,goroutine 的执行时机不确定,主协程可能在赋值前就进行了断言。
正确等待策略
应使用同步机制确保任务完成。例如通过 sync.WaitGroup:
func TestAsyncOperation(t *testing.T) {
var result int
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
result = 42
}()
wg.Wait()
assert.Equal(t, 42, result) // 此时断言稳定
}
var counter int
func TestIncrement(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter++
}()
}
wg.Wait()
assert.Equal(t, 10, counter) // 可能失败
}
[TestMethod]
public void GetUserAsync_ShouldReturnUser()
{
var result = userService.GetUserAsync(1);
Assert.IsNotNull(result); // 错误:仅断言Task不为null
}
上述代码仅确认返回了一个任务对象,未验证异步操作完成后的实际用户数据。
正确做法:等待任务完成
应使用 `await` 解包 `Task`,确保获取最终结果:
[TestMethod]
public async Task GetUserAsync_ShouldReturnUser()
{
var result = await userService.GetUserAsync(1);
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Id);
}
[Fact]
public async Task Should_Throw_Exception_When_Input_Is_Null()
{
var service = new DataService();
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
async () => await service.ProcessAsync(null)
);
Assert.Equal("input", exception.ParamName);
}
采用滑动时间窗口判断异步结果,避免因瞬时状态误判而触发错误。例如,在 Go 测试中可结合 time.After 与通道监听:
select {
case result := <-resultChan:
assert.Equal(t, expected, result)
case <-time.After(2 * time.Second):
t.Fatal("timeout waiting for async result")
}
var mu sync.Mutex
var result int
func TestParallelAsync(t *testing.T) {
t.Parallel()
go func() {
mu.Lock()
result++
mu.Unlock()
}()
time.Sleep(100 * time.Millisecond)
mu.Lock()
assert.Equal(t, 1, result)
mu.Unlock()
}