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àokế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ặpmn
12016 → m = 20 − 16 = 4
2416 → n = 16 − 4 = 12
3412 → n = 12 − 4 = 8
448 → n = 8 − 4 = 4
Kết thúc44 → Ư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ùng range(len(A)). Viết len(A) - 1 là 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ử đầucuố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.
Chế độ đọc sách →