Chuyển đến nội dung chính
Mô-đun 9 · Bài học 2 trong tổng số 542/50 trong khóa học~14 min
Bài học theo mô-đun (2/5)

Kiểm tra dựa trên bảng

Kiểm tra dựa trên bảng là mẫu đặc trưng của Go: thay vì viết N hàm TestThisSpecificCase riêng biệt, bạn khai báo một lát trường hợp và lặp lại. Có MỘT khối xác nhận và dữ liệu thử nghiệm được tách khỏi logic.

Mẫu cơ sở

Go
func TestAbs(t *testing.T) {
    cases := []struct {
        name string
        in   int
        want int
    }{
        {"pos", 3, 3},
        {"neg", -3, 3},
        {"zero", 0, 0},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Abs(tc.in); got != tc.want {
                t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
            }
        })
    }
}

Ba thành phần:

  1. Lát cấu trúc ẩn danh với các trường thông thường: name, đầu vào, đầu ra dự kiến ​​(và có thể là lỗi dự kiến).
  2. for _, tc := range cases — vòng lặp thực thi.
  3. t.Run(tc.name, func(t *testing.T) { ... }) — mọi trường hợp đều trở thành một bài kiểm tra phụ có tên riêng.

Tại sao t.Run?

t.Run tạo một kiểm tra phụ có:

  • Tên cá nhân hiển thị ở đầu ra: TestAbs/pos, TestAbs/neg, ...
  • t *testing.T của chính nó: lỗi trong một trường hợp không ngăn được các trường hợp khác (trừ khi sử dụng t.Fatal).
  • Lọc dòng lệnh: go test -run TestAbs/neg chỉ chạy trường hợp đó.
  • Song song tùy chọn: gọi t.Parallel() bên trong hàm để song song hóa.
Bash
go test -v -run TestAbs/neg

Các trường hợp có lỗi dự kiến

Go
cases := []struct {
    name    string
    in      string
    want    int
    wantErr bool
}{
    {"ok", "42", 42, false},
    {"bad", "x", 0, true},
}
for _, tc := range cases {
    t.Run(tc.name, func(t *testing.T) {
        got, err := Atoi(tc.in)
        if (err != nil) != tc.wantErr {
            t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
        }
        if !tc.wantErr && got != tc.want {
            t.Errorf("got %d, want %d", got, tc.want)
        }
    })
}

Cạm bẫy đóng vòng lặp (trước phiên bản 1.22)

Trong các phiên bản trước Go 1.22, biến tc đã được chia sẻ qua nhiều lần lặp lại: việc gọi t.Parallel() bên trong t.Run đã khiến tất cả các thử nghiệm phụ chạy theo cùng một trường hợp cuối cùng. Cách khắc phục là:

Go
for _, tc := range cases {
    tc := tc // local rebind (pre-1.22)
    t.Run(tc.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

Đặt tên các trường hợp

Thực tiễn tốt nhất:

  • Tên ngắn gọn nhưng giàu sức gợi: "empty input", "too large", "negative".
  • Không có khoảng trắng nếu bạn muốn lọc dễ dàng bằng -run (hoặc sử dụng dấu gạch dưới).
  • Luôn bao gồm trường hợp "đường dẫn hạnh phúc", trường hợp "cạnh" (không, trống) và ít nhất một trường hợp lỗi.

Bài tập

tập thể dục#go.m9.l2.e1
Nỗ lực: 0Đang tải…

Xác định một lát trường hợp của cấu trúc ẩn danh với các trường trong và mong muốn, được điền 3 trường hợp để kiểm tra Abs (dương, âm, 0).

Đang tải trình chỉnh sửa…

Giải pháp khả dụng sau 3 lần thử

tập thể dục#go.m9.l2.e2
Nỗ lực: 0Đang tải…

Thêm t.Run với tc.name để biến mỗi trường hợp thành một bài kiểm tra phụ được đặt tên.

Đang tải trình chỉnh sửa…

Giải pháp khả dụng sau 3 lần thử

Quiz#go.m9.l2.e3
Sẵn sàng

Ưu điểm chính của mẫu định hướng bảng so với N hàm Kiểm tra riêng biệt là gì?

Go
for _, tc := range cases {
  t.Run(tc.name, func(t *testing.T) { ... })
}
Tùy chọn trả lời