为什么需要模拟网络延迟测试异步函数
异步函数在处理网络请求、消息队列消费等场景时应用广泛,但网络波动、服务过载等情况会导致请求延迟甚至超时。如果异步函数没有做好延迟和超时处理,很容易出现goroutine泄漏、数据不一致、服务雪崩等问题。通过模拟网络延迟,可以在开发阶段提前暴露这些潜在问题,验证异步函数的鲁棒性。

Golang模拟网络延迟的常用方案
方案一:使用time.Sleep直接模拟延迟
这是最简单的延迟模拟方式,适合在单元测试中快速构造延迟场景,不需要依赖外部网络环境。我们可以在异步函数调用的目标方法或者 mock 的接口中插入time.Sleep,模拟不同长度的延迟。
package main
import (
"fmt"
"time"
)
// 模拟异步调用的目标方法,添加延迟
func mockRemoteCall(delay time.Duration) string {
time.Sleep(delay)
return "call success"
}
// 待测试的异步函数
func asyncProcess() chan string {
resultChan := make(chan string, 1)
go func() {
// 调用模拟的远程方法,这里模拟500ms延迟
res := mockRemoteCall(500 * time.Millisecond)
resultChan <- res
}()
return resultChan
}
func main() {
ch := asyncProcess()
select {
case res := <-ch:
fmt.Println("异步调用结果:", res)
case <-time.After(1 * time.Second):
fmt.Println("异步调用超时")
}
}
方案二:自定义HTTP Transport模拟网络延迟
如果异步函数是通过HTTP请求调用外部服务,我们可以自定义http.Transport,在发送请求前添加延迟,更贴近真实网络请求的延迟场景。
package main
import (
"fmt"
"net/http"
"time"
)
// 自定义Transport,实现RoundTrip方法添加延迟
type delayTransport struct {
baseTransport http.RoundTripper
delay time.Duration
}
func (t *delayTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// 发送请求前先休眠,模拟网络延迟
time.Sleep(t.delay)
return t.baseTransport.RoundTrip(req)
}
func main() {
// 创建自定义Transport,模拟300ms网络延迟
transport := &delayTransport{
baseTransport: http.DefaultTransport,
delay: 300 * time.Millisecond,
}
client := &http.Client{
Transport: transport,
Timeout: 800 * time.Millisecond,
}
// 异步发起HTTP请求
resultChan := make(chan error, 1)
go func() {
resp, err := client.Get("http://ipipp.com/test")
if err != nil {
resultChan <- err
return
}
defer resp.Body.Close()
resultChan <- nil
}()
select {
case err := <-resultChan:
if err != nil {
fmt.Println("请求失败:", err)
} else {
fmt.Println("请求成功")
}
case <-time.After(1 * time.Second):
fmt.Println("请求超时")
}
}
方案三:使用TCP代理转发添加延迟
如果需要模拟更复杂的网络场景,比如随机延迟、延迟波动,可以通过启动本地TCP代理,在转发请求时添加延迟,适合集成测试场景。
package main
import (
"fmt"
"io"
"net"
"time"
)
// 启动TCP代理,转发请求并添加延迟
func startDelayProxy(listenAddr string, targetAddr string, delay time.Duration) {
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
fmt.Println("启动代理失败:", err)
return
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go func(clientConn net.Conn) {
defer clientConn.Close()
// 连接目标服务
targetConn, err := net.Dial("tcp", targetAddr)
if err != nil {
return
}
defer targetConn.Close()
// 转发客户端到目标的数据,添加延迟
go func() {
time.Sleep(delay)
io.Copy(targetConn, clientConn)
}()
// 转发目标到客户端的数据,添加延迟
go func() {
time.Sleep(delay)
io.Copy(clientConn, targetConn)
}()
}(conn)
}
}
func main() {
// 启动代理,监听本地8080端口,转发到ipipp.com的80端口,添加200ms延迟
go startDelayProxy(":8080", "ipipp.com:80", 200*time.Millisecond)
// 等待代理启动
time.Sleep(100 * time.Millisecond)
// 异步调用代理地址,测试延迟场景
resultChan := make(chan string, 1)
go func() {
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
resultChan <- "连接失败"
return
}
defer conn.Close()
resultChan <- "连接成功"
}()
select {
case res := <-resultChan:
fmt.Println(res)
case <-time.After(1 * time.Second):
fmt.Println("连接超时")
}
// 保持程序运行
time.Sleep(10 * time.Second)
}
如何设计异步函数鲁棒性测试用例
模拟出网络延迟后,需要设计对应的测试用例验证异步函数的鲁棒性,常见的测试点包括:
- 异步函数在短延迟场景下的正常执行逻辑是否正确
- 异步函数在接近超时阈值的延迟场景下的超时处理是否符合预期
- 异步函数在超过超时阈值的延迟场景下是否能正确释放资源,避免goroutine泄漏
- 网络延迟波动场景下,异步函数的重试机制、降级逻辑是否生效
可以结合testing包编写单元测试,使用表格驱动测试覆盖不同的延迟场景:
package main
import (
"testing"
"time"
)
// 待测试的异步函数,接受延迟参数和超时参数
func testAsyncFunc(delay time.Duration, timeout time.Duration) error {
resultChan := make(chan struct{}, 1)
go func() {
time.Sleep(delay)
resultChan <- struct{}{}
}()
select {
case <-resultChan:
return nil
case <-time.After(timeout):
return fmt.Errorf("执行超时")
}
}
func TestAsyncFuncRobustness(t *testing.T) {
testCases := []struct {
name string
delay time.Duration
timeout time.Duration
expectOk bool
}{
{"短延迟正常场景", 100 * time.Millisecond, 500 * time.Millisecond, true},
{"接近超时场景", 400 * time.Millisecond, 500 * time.Millisecond, true},
{"超时场景", 600 * time.Millisecond, 500 * time.Millisecond, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := testAsyncFunc(tc.delay, tc.timeout)
if tc.expectOk && err != nil {
t.Errorf("预期执行成功,实际错误: %v", err)
}
if !tc.expectOk && err == nil {
t.Errorf("预期执行失败,实际执行成功")
}
})
}
}
注意事项
模拟网络延迟时需要注意,延迟时间不要设置过长,避免影响测试执行效率。如果是集成测试,尽量使用独立的测试环境,不要对生产环境的服务地址添加延迟。另外,异步函数的超时时间设置要合理,既要覆盖正常延迟场景,也要避免超时时间过短导致正常请求被误判为失败。