单元测试与wrk压测工具
单元测试
以下是项目树结构:
dytest/
├── confess/
│ ├── confess.go
│ └── confess_test.go
├── go.mod
└── main.go// confess.go
package confess
import "fmt"
func Confess(you, me string) (string, error) {
if you == "" || me == "" {
return "", fmt.Errorf("both params required")
}
if you == me {
return fmt.Sprintf("%s ❤️ 自己", you), nil // 分支 A
}
if len(you)+len(me) > 20 { // 假设我们需要两值之和20也能过该分支,但是出错导致问题只有>
return "", fmt.Errorf("name too long") // 分支 B
}
return fmt.Sprintf("%s ❤️ %s", you, me), nil // 正常分支
}// confess_test.go
package confess
import "testing"
func TestConfess(t *testing.T) {
// 1. 正常场景
t.Run("ok", func(t *testing.T) {
got, err := Confess("Alice", "Bob")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "Alice ❤️ Bob" {
t.Errorf("got %q, want %q", got, "Alice ❤️ Bob")
}
})
// 2. 非法场景:空参数 → 必须返回错误
t.Run("empty param", func(t *testing.T) {
_, err := Confess("", "Bob")
if err == nil { // ← 关键断言:错误必须存在
t.Error("expected error, got nil")
}
})
// 3. 边界场景:名字太长 → 也必须返回错误
t.Run("name too long", func(t *testing.T) {
_, err := Confess("1234567810", "1234567820") // 总长 20,触发不到分支 B
if err == nil { // ← 断言:必须报错
t.Error("expected 'name too long' error, got nil")
}
})
// 4. 自恋场景:you == me
t.Run("self love", func(t *testing.T) {
got, err := Confess("Alice", "Alice")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// 故意写错想要值,演示失败
if got != "Alice ❤️ Alice" { // ← 实际返回 "Alice ❤️ 自己"
t.Errorf("got %q, want %q", got, "Alice ❤️ Alice")
}
})
}上面是需要单元测试的代码以及单元测试用例,现在的单元测试用例我们可以从代码看出,场景3和场景4都有一些问题:
# go test -coverprofile=cover
PS D:\GoLand\dytest\confess> go test -coverprofile=cover
--- FAIL: TestConfess (0.00s)
--- FAIL: TestConfess/name_too_long (0.00s)
confess_test.go:29: expected 'name too long' error, got nil
--- FAIL: TestConfess/self_love (0.00s)
confess_test.go:41: got "Alice ❤️ 自己", want "Alice ❤️ Alice"
FAIL
coverage: 85.7% of statements
exit status 1
FAIL dytest/confess 1.221s这里我们可以从返回结果看出,场景3的测试用例并没有被单元测试代码覆盖到,返回的结果是单元测试用例代码 error 返回的结果。
测试用例:
_, err := Confess("1234567810", "1234567820") // 总长 20,触发不到分支 B单元测试代码:
if len(you)+len(me) > 20 { // 假设我们需要两值之和20也能过该分支,但是出错导致问题只有>
return "", fmt.Errorf("name too long") // 分支 B
}我们改成:
if len(you)+len(me) >= 20 { // 根据测试用例报错补全逻辑
return "", fmt.Errorf("name too long") // 分支 B
}这样就好了:
PS D:\GoLand\dytest\confess> go test -coverprofile=cover
--- FAIL: TestConfess (0.00s)
--- FAIL: TestConfess/self_love (0.00s)
confess_test.go:41: got "Alice ❤️ 自己", want "Alice ❤️ Alice"
FAIL
coverage: 100.0% of statements
exit status 1
FAIL dytest/confess 1.270s这就是单元测试用例去测试需要单元测试代码的好处,能够让一些逻辑更完善。
场景4则是测试用例有问题,我们作为开发工程师也能够根据测试用例的错误来反馈给测试工程师相关测试用例问题。
// 4. 自恋场景:you == me
t.Run("self love", func(t *testing.T) {
got, err := Confess("Alice", "Alice")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// 故意写错想要值,演示失败
if got != "Alice ❤️ Alice" { // ← 实际返回 "Alice ❤️ 自己"
t.Errorf("got %q, want %q", got, "Alice ❤️ Alice")
}
})改成:
// 4. 自恋场景:you == me
t.Run("self love", func(t *testing.T) {
got, err := Confess("Alice", "Alice")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
})就符合工程师需求了。
以及相应的 cmd 中执行的测试命令:
go test -coverprofile=cover会在当前目录下生成出 cover 测试结果文件。
我们可以根据这个文件使用 tool 进行可视化分析:
go tool cover -html=cover -o cover.html执行完之后就会在目录下生成一个 html 文件,打开就可以看到单元测试对应的结果了。
比如说我们前面场景3如果没修复,只修复了场景4的话,执行命令:
PS D:\GoLand\dytest\confess> go test -coverprofile=cover
--- FAIL: TestConfess (0.00s)
--- FAIL: TestConfess/name_too_long (0.00s)
confess_test.go:29: expected 'name too long' error, got nil
FAIL
coverage: 85.7% of statements
exit status 1
FAIL dytest/confess 1.276s
PS D:\GoLand\dytest\confess> go tool cover -html=cover -o cover.html打开 html 文件之后就会展示我们没有通过测试用例对应的代码:

我们修复好之后:
PS D:\GoLand\dytest\confess> go test -coverprofile=cover
PASS
coverage: 100.0% of statements
ok dytest/confess 1.276s
PS D:\GoLand\dytest\confess> go tool cover -html=cover -o cover.html
直观地反馈出来单元测试代码的具体测试情况。
wrk压力测试
需要在 linux 环境下安装:
git clone https://github.com/wg/wrk
cd wrk/
make
cp wrk /usr/local/sbin/使用方法:
使用方法: wrk <选项> <被测HTTP服务的URL>
Options:
-c, --connections <N> 跟服务器建立并保持的TCP连接数量
-d, --duration <T> 压测时间
-t, --threads <N> 使用多少个线程进行压测
-s, --script <S> 指定Lua脚本路径
-H, --header <H> 为每一个HTTP请求添加HTTP头
--latency 在压测结束后,打印延迟统计信息
--timeout <T> 超时时间
-v, --version 打印正在使用的wrk的详细版本信息
<N>代表数字参数,支持国际单位 (1k, 1M, 1G)
<T>代表时间参数,支持时间单位 (2s, 2m, 2h)举个例子,我们想压力测试一个网站:
wrk -c 40 -d 10 -t 4 --latency "https://rolingg.top"这里即是:开40个线程,压测10s,使用4个线程进行压测,并且压测结束后打印延迟统计信息。
Running 10s test @ https://rolingg.top
4 threads and 40 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 44.10ms 67.16ms 438.26ms 81.63%
Req/Sec 1.13k 476.31 2.83k 72.36%
Latency Distribution
50% 4.10ms
75% 71.23ms
90% 161.27ms
99% 215.35ms
44933 requests in 10.01s, 8.41MB read
Non-2xx or 3xx responses: 44933
Requests/sec: 4490.54
Transfer/sec: 860.58KBAvg:平均时间
Latency:延迟
Stdev:标准差
Max:最大值
+/- Stdev:正负标准差占比
Latency Distribution:延迟分布
44933 requests in 10.01s, 8.41MBMB read:在10.01s内共处理了71083个请求,读了13.29MB数据内容。
Non-2xx or 3xx responses: 44933:在这段时间内发出的 44933 个 HTTP 请求里,没有一条返回状态码是 2xx(成功)或 3xx(重定向),全部是 4xx / 5xx 等错误响应(最常见就是 404、403、500、502 等)。
Requests/sec: 4490.54 平均每秒处理4490.54个请求(也就是QPS)
Transfer/sec: 860.58K 平均每秒传输数据量860.58KB我们再来一个请求都成功的:
~ wrk -c 40 -d 10 -t 4 --latency "https://baidu.com/"
Running 10s test @ https://baidu.com/
4 threads and 40 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 54.09ms 3.10ms 69.28ms 66.93%
Req/Sec 183.98 18.23 212.00 92.88%
Latency Distribution
50% 54.76ms
75% 55.80ms
90% 58.28ms
99% 61.96ms
7248 requests in 10.01s, 1.53MB read
Requests/sec: 723.81
Transfer/sec: 156.92KBwrk 压测大概就是这样子了。
RoLingG | 博客
评论(0)