Bài 57.228 phút đọc
Thiết kế ca kiểm thử
Sau bài này bạn sẽ làm được
- Chia đầu vào thành các lớp tương đương
- Liệt kê giá trị biên cho một hàm
- Viết kiểm thử cho mọi nhánh lỗi
- Biết khi nào một bài kiểm thử là vô dụng
Bạn không thể thử hết mọi đầu vào. Một hàm nhận một int có bốn tỉ đầu vào. Thiết kế ca kiểm thử là nghệ thuật chọn ra vài chục cái đại diện cho cả bốn tỉ, và bài này chứng minh cách chọn đúng bằng một lỗi thật.
#Lớp tương đương
Lớp tương đương
Một tập hợp các đầu vào mà bạn tin rằng chương trình xử lý theo cùng một cách. Nếu một đại diện của lớp chạy đúng, bạn giả định cả lớp chạy đúng. Chia miền đầu vào thành các lớp và lấy một đại diện cho mỗi lớp.
Hàm cần kiểm thử
/* Xep loai theo diem: 0..100.
Tra ve 'A'..'F', hoac '?' neu ngoai pham vi. */
char xep_loai(int diem) {
if (diem < 0 || diem > 100) return '?';
if (diem >= 90) return 'A';
if (diem >= 80) return 'B';
if (diem >= 70) return 'C';
if (diem >= 60) return 'D';
return 'F';
}| Lớp tương đương | Khoảng | Đại diện | Kết quả mong đợi |
|---|---|---|---|
| Dưới miền hợp lệ | int nhỏ hơn 0 | -5 | ? |
| Loại F | 0 tới 59 | 30 | F |
| Loại D | 60 tới 69 | 65 | D |
| Loại C | 70 tới 79 | 75 | C |
| Loại B | 80 tới 89 | 85 | B |
| Loại A | 90 tới 100 | 95 | A |
| Trên miền hợp lệ | lớn hơn 100 | 150 | ? |
Bảy bài kiểm thử cho bảy lớp
static void thu_lop_tuong_duong(void) {
KT_NHOM("xep_loai: mot dai dien moi lop tuong duong");
KT_BANG_INT(xep_loai(-5), '?');
KT_BANG_INT(xep_loai(30), 'F');
KT_BANG_INT(xep_loai(65), 'D');
KT_BANG_INT(xep_loai(75), 'C');
KT_BANG_INT(xep_loai(85), 'B');
KT_BANG_INT(xep_loai(95), 'A');
KT_BANG_INT(xep_loai(150), '?');
}#Giá trị biên
Phân tích giá trị biên
Lỗi tập trung ở ranh giới giữa các lớp tương đương. Với mỗi ranh giới
b, kiểm thử ba giá trị: b - 1, b, và b + 1.Năm ranh giới, mười lăm bài kiểm thử
static void thu_gia_tri_bien(void) {
KT_NHOM("xep_loai: gia tri bien, moi bien 3 diem");
/* bien 0 */
KT_BANG_INT(xep_loai(-1), '?');
KT_BANG_INT(xep_loai(0), 'F');
KT_BANG_INT(xep_loai(1), 'F');
/* bien 60 */
KT_BANG_INT(xep_loai(59), 'F');
KT_BANG_INT(xep_loai(60), 'D');
KT_BANG_INT(xep_loai(61), 'D');
/* bien 70, 80, 90 */
KT_BANG_INT(xep_loai(69), 'D');
KT_BANG_INT(xep_loai(70), 'C');
KT_BANG_INT(xep_loai(79), 'C');
KT_BANG_INT(xep_loai(80), 'B');
KT_BANG_INT(xep_loai(89), 'B');
KT_BANG_INT(xep_loai(90), 'A');
/* bien 100 */
KT_BANG_INT(xep_loai(99), 'A');
KT_BANG_INT(xep_loai(100), 'A');
KT_BANG_INT(xep_loai(101), '?');
}Biên của một bộ đệm, khó hơn nhiều
/* Chep nguon vao dich, cat bot neu can. Luon ket thuc bang NUL.
Tra ve do dai da chep, hoac (size_t)-1 neu doi so sai. */
size_t chep_an_toan(char *dich, size_t dich_co, const char *nguon) {
if (dich == NULL || nguon == NULL || dich_co == 0) return (size_t)-1;
size_t n = strlen(nguon);
if (n > dich_co - 1) n = dich_co - 1;
memcpy(dich, nguon, n);
dich[n] = '\0';
return n;
}
static void thu_chep(void) {
KT_NHOM("chep_an_toan: bien cua kich thuoc bo dem");
char d[8];
/* nguon ngan hon bo dem */
KT_BANG_INT(chep_an_toan(d, sizeof d, "abc"), 3);
KT_BANG_CHUOI(d, "abc");
/* nguon dai DUNG BANG suc chua (7 ky tu + NUL) */
KT_BANG_INT(chep_an_toan(d, sizeof d, "1234567"), 7);
KT_BANG_CHUOI(d, "1234567");
/* nguon dai hon 1 ky tu: phai bi cat */
KT_BANG_INT(chep_an_toan(d, sizeof d, "12345678"), 7);
KT_BANG_CHUOI(d, "1234567");
/* nguon rong */
KT_BANG_INT(chep_an_toan(d, sizeof d, ""), 0);
KT_BANG_CHUOI(d, "");
/* bo dem 1 byte: chi chua duoc NUL */
char m[1];
KT_BANG_INT(chep_an_toan(m, 1, "abc"), 0);
KT_BANG_CHUOI(m, "");
/* duong loi */
KT_BANG_INT(chep_an_toan(NULL, 8, "abc"), (size_t)-1);
KT_BANG_INT(chep_an_toan(d, 0, "abc"), (size_t)-1);
KT_BANG_INT(chep_an_toan(d, 8, NULL), (size_t)-1);
}terminal
gcc -std=c11 -D__USE_MINGW_ANSI_STDIO=1 -Wall -Wextra -g kt_bien.c bien.c -o kt_bien.exe
./kt_bien.exe | tail -6
ok chep_an_toan(m, 1, "abc") == 0 ok m == "" ok chep_an_toan(NULL, 8, "abc") == (size_t)-1 ok chep_an_toan(d, 0, "abc") == (size_t)-1 ok chep_an_toan(d, 8, NULL) == (size_t)-1 35 kiem tra, 0 hong
echo $?
0
#Chứng minh bằng một lỗi thật
Bây giờ đưa vào hai lỗi kinh điển, mỗi lỗi đúng một ký tự, rồi chạy lại cùng bộ kiểm thử.
bien_loi.c, hai lỗi lệch một
/* Loi 1: >= thanh > */
if (diem > 90) return 'A'; /* dung: diem >= 90 */
/* Loi 2: thieu tru mot */
if (n > dich_co) n = dich_co - 1; /* dung: n > dich_co - 1 */terminal
gcc -std=c11 -D__USE_MINGW_ANSI_STDIO=1 -Wall -Wextra -g kt_bien.c bien_loi.c -o kt_loi.exe
./kt_loi.exe | grep -E "HONG|kiem tra,"
HONG xep_loai(90) == 'A' (66 != 65) (kt_bien.c:34)
HONG chep_an_toan(d, sizeof d, "12345678") == 7 (8 != 7) (kt_bien.c:54)
HONG d == "1234567" ("12345678" != "1234567") (kt_bien.c:55)
35 kiem tra, 3 hong#Đường lỗi
Bốn cách ép nhánh lỗi chạy
/* 1. Truyen doi so xau. De nhat, dung duoc voi nhanh 1 va 4. */
KT_BANG_INT(nap_cau_hinh("/khong/ton/tai", &c), -1);
KT_BANG_INT(nap_cau_hinh(TEP_HONG, &c), -1);
/* 2. Tao tep tam voi noi dung dac biet. */
static const char *tao_tep_tam(const char *noi_dung) {
static char dd[L_tmpnam];
tmpnam(dd);
FILE *t = fopen(dd, "wb");
if (noi_dung) fputs(noi_dung, t);
fclose(t);
return dd;
}
KT_BANG_INT(nap_cau_hinh(tao_tep_tam(""), &c), -1); /* nhanh 3 */
KT_BANG_INT(nap_cau_hinh(tao_tep_tam("rac rac"), &c), -1); /* nhanh 4 */
/* 3. Tiem loi vao malloc. Xem Bai 57.3. */
static int malloc_hong_sau = -1;
void *malloc_gia(size_t n) {
if (malloc_hong_sau == 0) return NULL;
if (malloc_hong_sau > 0) --malloc_hong_sau;
return malloc(n);
}
malloc_hong_sau = 0;
KT_BANG_INT(nap_cau_hinh(TEP_TOT, &c), -1); /* nhanh 2 */
/* 4. Gioi han tai nguyen cua tien trinh. */
/* setrlimit(RLIMIT_NOFILE, ...) de ep fopen that bai
setrlimit(RLIMIT_AS, ...) de ep malloc that bai
Chi tren POSIX, va anh huong ca tien trinh, nen dung
trong mot tien trinh fork rieng. */#Bài kiểm thử vô dụng
Bốn bài kiểm thử không đáng viết
/* 1. Kiem thu lai thu vien chuan */
KT_BANG_INT(strlen("abc"), 3);
KT_DUNG(malloc(10) != NULL);
/* 2. Kiem thu lai chinh cai vua gan */
Ds *d = ds_tao(4);
d->co = 5;
KT_BANG_INT(d->co, 5); /* dat gi thi doc nay */
/* 3. Lap lai hien thuc trong bai kiem thu */
KT_BANG_INT(tong(a, n), a[0] + a[1] + a[2]);
/* Neu tong() sai vi hieu sai cong thuc, bai kiem thu
cung sai y het, va no van xanh. */
/* 4. Khong co khang dinh nao */
static void thu_khoi_tao(void) {
Ds *d = ds_tao(4);
ds_them(d, 1);
ds_them(d, 2);
ds_huy(d);
/* Bai nay chi kiem tra "khong sap". Do LA mot gia tri
nao do, nhung hay noi ro y dinh do bang mot khang dinh. */
}
Bài kiểm thử có giá trị
/* 1. Kiem thu HANH VI cua ma BAN viet */
KT_BANG_INT(chuoi_cat_trang(a), 8);
/* 2. Kiem thu bat bien SAU mot chuoi thao tac */
Ds *d = ds_tao(4);
for (int i = 0; i < 100; ++i) ds_them(d, i);
KT_BANG_INT(ds_so_luong(d), 100); /* da mo rong dung */
KT_BANG_INT(ds_lay_hoac(d, 99, -1), 99);
ds_huy(d);
/* 3. Kiem thu bang GIA TRI DA BIET, tinh doc lap */
KT_BANG_INT(tong(mang_3_phan_tu, 3), 6); /* 1+2+3, tinh tay */
/* 4. Kiem thu voi khang dinh ro rang */
static void thu_khoi_tao(void) {
Ds *d = ds_tao(4);
KT_DUNG(d != NULL);
KT_BANG_INT(ds_so_luong(d), 0); /* moi tao PHAI rong */
ds_huy(d);
}
Tự làm thử
- Liệt kê lớp tương đương và giá trị biên cho một hàm trong dự án của bạn.
- Chạy
kt_bien.cvới cả hai phiên bản và xác nhận nhóm lớp tương đương không bắt được lỗi. - Đưa thêm ba đột biến khác vào
bien.cvà xem bộ kiểm thử có bắt được không. - Viết bài kiểm thử cho cả bốn nhánh lỗi của một hàm có nhiều tài nguyên.
- Thêm bộ đếm cấp phát và kiểm chứng rằng nhánh lỗi không rò rỉ.
- Viết một bài kiểm thử dựa trên tính chất cho hàm sắp xếp của bạn, với hạt cố định.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Chia miền đầu vào thành lớp tương đương, lấy một đại diện mỗi lớp.
- Với mỗi ranh giới, thử
b-1,b,b+1; đây là chỗ lỗi tập trung. - Đã chứng minh: bảy bài lớp tương đương xanh hết trong khi mã có hai lỗi lệch một, một trong đó là tràn bộ đệm.
- Mỗi nhánh lỗi phải có bài kiểm thử, và bài đó phải kiểm tra cả việc dọn dẹp chứ không chỉ giá trị trả về.
- Bài kiểm thử vô dụng: kiểm thử thư viện chuẩn, đọc lại cái vừa gán, lặp lại hiện thực, hoặc không có khẳng định.