Giải Tin Học 10 trang 146
Lời giải chi tiết SGK Lớp 10 · Môn Tin Há»c · Trang 146–149
Bài 30: Kiểm thử và gỡ lỗi chương trình
⚠️ Một lưu ý nhỏ: phần mã nguồn trong file gốc bị lỗi font khá nặng. Tôi đã giải mã và phục dựng lại các đoạn chương trình dựa trên logic và phần kết quả in ra. Những chỗ phục dựng tôi sẽ ghi rõ.
🔍 Kiến thức cần nhớ
1. Bốn phương pháp kiểm thử (test) và gỡ lỗi (debug) chương trình
a) Quan sát mã lỗi Runtime và bắt lỗi ngoại lệ
Khi chương trình đang chạy bị dừng lại (lỗi Runtime), ta đọc thông báo lỗi và lỗi ngoại lệ để xác định dòng lệnh sinh ra lỗi, từ đó phân tích và sửa.
b) Kiểm thử với các bộ dữ liệu test
Mỗi bộ test gồm đầu vào và kết quả đầu ra đã biết trước. Cần chú ý:
- Có nhiều bộ test theo các tiêu chí khác nhau (độ lớn, tính đa dạng).
- Có bộ test ngẫu nhiên (tăng khả năng phát hiện lỗi).
- Có bộ test dữ liệu ở vùng biên — vì lỗi thường phát sinh tại biên hoặc lân cận biên.
c) In các thông số trung gian
Chèn các lệnh print() vào giữa chương trình để in giá trị các biến trung gian, qua đó kiểm tra thuật toán có đúng không.
d) Sử dụng công cụ break point (điểm dừng)
Đặt điểm dừng để chương trình tạm dừng tại đó, cho phép quan sát giá trị các biến bên trong. Kết hợp với phương pháp in giá trị trung gian sẽ hiệu quả hơn.
💡 Mẹo nhớ: 4 công cụ chính = Mã lỗi → Bộ test → In trung gian → Break point. Nhớ câu: "Đọc lỗi, thử dữ liệu, in giữa chừng, dừng đúng chỗ."
2. Tư tưởng kiểm thử quan trọng nhất
⚠️ Lưu ý thi: Chương trình chạy không báo lỗi ngoại lệ KHÔNG có nghĩa là không có lỗi. Các lỗi không tường minh này gọi là bug, rất khó phát hiện và khó sửa.
⚙️ Ví dụ minh hoạ (đã phân tích trong bài)
Bài toán: Nhập hai số tự nhiên m, n, tính ƯCLN bằng thuật toán Euclid trừ liên tiếp:
- gcd(m, m) = m
- Nếu m > n: gcd(m, n) = gcd(m − n, n)
- Nếu m < n: gcd(m, n) = gcd(m, n − m)
Chương trình có chèn lệnh in giá trị trung gian:
m = int(input("m = "))
n = int(input("n = "))
k = 0
while m != n:
k = k + 1
print("Vòng lặp", k, m, n) # in giá trị trung gian
if m > n:
m = m - n
else:
n = n - m
print("ƯCLN =", m)
Chạy thử với m = 20, n = 16, kết quả khớp với bài:
| Vòng lặp | m | n |
|---|---|---|
| 1 | 20 | 16 → m = 20 − 16 = 4 |
| 2 | 4 | 16 → n = 16 − 4 = 12 |
| 3 | 4 | 12 → n = 12 − 4 = 8 |
| 4 | 4 | 8 → n = 8 − 4 = 4 |
| Kết thúc | 4 | 4 → ƯCLN = 4 |
Cùng bài toán này có thể dùng break point đặt tại đầu vòng while, mỗi lần dừng ghi lại giá trị m, n. Cả hai cách (in trung gian và đặt điểm dừng) đều hiệu quả.
⚙️ Bài tập
Câu LT1
Đề bài: Chương trình của em khi chạy phát sinh lỗi ngoại lệ ZeroDivisionError. Đó là lỗi gì và em sẽ xử lí lỗi này như thế nào?
Cách 1 — Hiểu bản chất lỗi
ZeroDivisionError là lỗi chia một số cho 0 (hoặc lấy phần dư với 0), ví dụ a / b hay a % b khi b = 0. Trong toán học phép chia cho 0 không xác định, nên Python dừng chương trình và báo lỗi ngoại lệ này.
Cách xử lí: kiểm tra mẫu số trước khi chia.
a = int(input("a = "))
b = int(input("b = "))
if b != 0:
print("a / b =", a / b)
else:
print("Lỗi: không thể chia cho 0!")
Cách 2 — Dùng cấu trúc bắt lỗi ngoại lệ try…except
a = int(input("a = "))
b = int(input("b = "))
try:
print("a / b =", a / b)
except ZeroDivisionError:
print("Lỗi: mẫu số bằng 0, không thực hiện được phép chia!")
💡 Mẹo nhớ: Thấy ZeroDivisionError → ngay lập tức nghĩ tới "mẫu số = 0". Khắc phục: kiểm tra trước (if) hoặc bắt lỗi (try…except).
Câu LT2
Đề bài: Chương trình sau có lỗi không? Nếu có thì tìm và sửa lỗi.
(Phục dựng từ bản gốc bị lỗi font — chương trình nhập hai số rồi in tổng:)
a = int(input("a = "))
b = int(input("a = ")) # ⟵ chỗ nghi vấn
print("Tổng =", a + b)
Cách 1 — Đọc kĩ từng dòng lệnh
Chương trình không phát sinh lỗi ngoại lệ (chạy được, in ra kết quả). Tuy nhiên có một lỗi logic/hiển thị: dòng nhập biến b lại dùng câu nhắc "a = ". Người dùng sẽ tưởng đang nhập lại a, dễ nhập sai dữ liệu.
Sửa lại câu nhắc cho đúng biến:
a = int(input("a = "))
b = int(input("b = ")) # sửa câu nhắc thành "b = "
print("Tổng =", a + b)
Cách 2 — Kiểm thử bằng bộ dữ liệu test
Cho bộ test: a = 3, b = 5, kết quả đúng phải là Tổng = 8.
Chạy chương trình gốc: màn hình hiện hai lần a = → đây chính là dấu hiệu lỗi ở câu nhắc, dù tổng vẫn cho ra 8. Việc dùng bộ test giúp ta phát hiện điểm bất thường trong giao diện nhập liệu.
⚠️ Lưu ý thi: Có những lỗi không làm chương trình dừng (không sinh ngoại lệ) nhưng vẫn là bug. Phải đọc kĩ từng dòng, không chỉ dựa vào việc chương trình "chạy được".
Câu VD1
Đề bài: Chương trình sau có chức năng sắp xếp một dãy số cho trước. Hãy kiểm tra xem chương trình có lỗi không? Nếu có thì tìm và sửa lỗi.
A = [10, 1, 5, 2, 8, 0, 4]
for i in range(len(A) - 1):
j = i
while j >= 1 and A[j] < A[j - 1]:
A[j], A[j - 1] = A[j - 1], A[j]
j = j - 1
print(A)
Đây là thuật toán sắp xếp chèn (insertion sort) theo thứ tự tăng dần.
Cách 1 — In giá trị trung gian để kiểm soát
Mảng có 7 phần tử nên chỉ số hợp lệ là 0…6. Nhưng vòng lặp dùng range(len(A) - 1) = range(6), tức i chỉ chạy 0, 1, 2, 3, 4, 5. Phần tử cuối cùng (chỉ số 6, giá trị 4) không bao giờ được đưa vào chèn.
Ta in mảng sau mỗi lần lặp để thấy rõ:
A = [10, 1, 5, 2, 8, 0, 4]
for i in range(len(A) - 1):
j = i
while j >= 1 and A[j] < A[j - 1]:
A[j], A[j - 1] = A[j - 1], A[j]
j = j - 1
print("Sau i =", i, ":", A)
print("Kết quả:", A)
Theo dõi, kết quả cuối là [0, 1, 2, 5, 8, 10, 4] — phần tử 4 vẫn nằm sai vị trí ở cuối. Vậy chương trình có lỗi.
Cách 2 — Sửa biên của vòng lặp (lỗi vùng biên)
Nguyên nhân là sai cận trên của vòng for. Cần để i chạy đến hết chỉ số cuối:
A = [10, 1, 5, 2, 8, 0, 4]
for i in range(len(A)): # sửa: bỏ "- 1"
j = i
while j >= 1 and A[j] < A[j - 1]:
A[j], A[j - 1] = A[j - 1], A[j]
j = j - 1
print(A) # Kết quả: [0, 1, 2, 4, 5, 8, 10]
(Có thể viết gọn hơn thành range(1, len(A)) vì khi i = 0 vòng while không chạy.)
💡 Mẹo nhớ: Với mảng n phần tử, chỉ số chạy từ 0 đến n − 1, nên muốn duyệt hết dùngrange(len(A)). Viếtlen(A) - 1là dạng lỗi biên kinh điển — bỏ sót phần tử cuối.
⚠️ Lưu ý thi: Lỗi "thiếu một phần tử ở biên" (off-by-one) cực kì hay ra đề. Luôn kiểm thử với dữ liệu vùng biên, đặc biệt là phần tử đầu và cuối dãy.
Câu VD2
Đề bài: Để kiểm thử một chương trình, nếu chỉ bằng việc kiểm tra thông qua các bộ dữ liệu test thì có bảo đảm tìm ra hết lỗi của chương trình hay không? Vì sao?
Cách 1 — Lập luận
Không bảo đảm tìm ra hết lỗi.
Lí do: miền dữ liệu đầu vào của một chương trình thường rất lớn hoặc vô hạn, nên ta không thể thử mọi bộ dữ liệu có thể xảy ra. Mỗi bộ test chỉ kiểm tra chương trình tại một số trường hợp cụ thể. Nếu bug chỉ xuất hiện với những đầu vào mà ta chưa nghĩ tới và chưa thử, thì việc test sẽ không phát hiện được.
Nói cách khác: kiểm thử chỉ có thể chứng minh chương trình CÓ lỗi (khi một bộ test cho kết quả sai), chứ không thể chứng minh chương trình KHÔNG còn lỗi.
Cách 2 — Minh hoạ bằng chính câu VD1
Nếu chỉ test thuật toán sắp xếp với các dãy mà phần tử cuối vốn đã đúng vị trí (ví dụ [1, 3, 2, 5], phần tử cuối 5 lớn nhất nên không cần chèn), chương trình lỗi ở câu VD1 vẫn cho kết quả đúng → ta tưởng nó không có lỗi. Chỉ khi gặp dãy mà phần tử cuối cần di chuyển (như […, 0, 4]) lỗi mới lộ ra. Điều này cho thấy bộ test "may mắn" có thể che giấu bug.
🎯 Ghi nhớ: Kiểm thử bằng bộ dữ liệu test giúp phát hiện lỗi chứ không bảo đảm hết lỗi. Vì vậy cần kết hợp nhiều phương pháp: đọc mã lỗi, in giá trị trung gian, đặt break point, và đặc biệt thiết kế bộ test đa dạng — gồm dữ liệu lớn, dữ liệu ngẫu nhiên và dữ liệu vùng biên.
