1. Khởi động (Engagement)
Bài học trước em đã biết khái niệm lỗi ngoại lệ khi chạy chương trình Python. Tuy nhiên, một chương trình chạy không có lỗi ngoại lệ (chương trình không bị dừng) thì không có nghĩa là chương trình không có lỗi. Thậm chí các "lỗi" không tường minh này (các lỗi này được gọi bug) càng khó phát hiện và khó sửa.
💡 Câu hỏi suy ngẫm: Theo em, làm thế nào để kiểm tra (test) và gỡ lỗi (debug) một chương trình? Môi trường lập trình có công cụ nào hỗ trợ việc đó không?
Trong bài học này, chúng ta sẽ tìm hiểu các phương pháp và công cụ để kiểm thử chương trình, giúp phát hiện và sửa lỗi hiệu quả hơn.
2. Hình thành kiến thức (Knowledge)
1. Một vài phương pháp kiểm thử chương trình
Có rất nhiều phương pháp và công cụ khác nhau để kiểm thử chương trình. Các công cụ này không những có mục đích tìm ra lỗi (hay bug) của chương trình mà còn có tác dụng phòng ngừa và ngăn chặn các lỗi phát sinh tiếp trong tương lai.
a) Quan sát mã lỗi Runtime và bắt lỗi ngoại lệ
Nếu chương trình có lỗi Runtime (tức là đang chạy bị dừng lại), cần quan sát các mã lỗi (mã lỗi ngoại lệ) để kiểm tra vị trí dòng lệnh sinh ra lỗi này. Từ đó phân tích, tìm và sửa lỗi.
b) Kiểm thử chương trình với các bộ dữ liệu test
Chương trình cần được thử với một số bộ dữ liệu test gồm đầu vào tiêu biểu phụ thuộc đặc thù của bài toán và kết quả đầu ra đã biết trước. Các bộ test có thể có đầu vào theo các tiêu chí khác nhau như độ lớn và tính đa dạng của dữ liệu.
Cần chú ý một số điểm sau:
- Cần có nhiều bộ test (theo các tiêu chí khác nhau như độ lớn, tính đa dạng của dữ liệu,...)
- Cần có bộ test ngẫu nhiên. Việc sinh ngẫu nhiên dữ liệu đầu vào trong miền xác định của chương trình làm tăng khả năng tìm lỗi nếu có
- Cần có bộ test dữ liệu ở vùng biên. Ví dụ dữ liệu đầu vào là cặp (x, y) xác định trên miền 0 ≤ x, y ≤ 1. Khi đó cần kiểm tra chương trình với bộ dữ liệu biên là (0; 0), (0; 1), (1; 0) và (1; 1). Thực tế cho thấy thường phát sinh lỗi tại các vùng biên hoặc lân cận của biên. Một ví dụ khác của dữ liệu biên là cần tìm các bộ test với n và các giá trị (x₁, x₂,..., xₙ) rất lớn (vùng cận biên lớn)
c) In các thông số trung gian
In các thông số trung gian ra màn hình để theo dõi giá trị của các biến tại các thời điểm khác nhau trong quá trình thực hiện chương trình. Đây là cách đơn giản nhưng hiệu quả để kiểm tra dòng chảy của chương trình và tìm ra các lỗi logic.
2. Một số lỗi ngoại lệ thường gặp
Chúng ta đã biết, nếu gặp lỗi ngoại lệ, chương trình Python sẽ dừng lại và báo lỗi. Một trong những vấn đề được đưa ra khi kiểm soát lỗi là làm thế nào để vẫn phát hiện lỗi, xử lí lỗi nhưng chương trình không bị dừng lại trong khi thực hiện.
Bảng các lỗi ngoại lệ thường gặp:
| Mã lỗi ngoại lệ | Mô tả lỗi |
|---|---|
| ZeroDivisionError | Lỗi này xảy ra khi lệnh thực hiện phép chia cho giá trị 0 |
| IndexError | Lỗi xảy ra khi lệnh cố gắng truy cập phần tử của danh sách nhưng chỉ số vượt quá giới hạn |
| NameError | Lỗi xảy ra khi chương trình muốn tìm một tên nhưng không thấy. Ví dụ khi lệnh gọi một hàm nhưng không có hàm đó |
| TypeError | Lỗi kiểu dữ liệu. Một số ví dụ lỗi loại này: lệnh truy cập một phần tử của danh sách nhưng chỉ số không là số nguyên; lệnh tính biểu thức số nhưng lại có một toán hạng không phải là số |
| ValueError | Lỗi liên quan đến giá trị của đối tượng. Lỗi khi thực hiện lệnh chuyển đổi kiểu dữ liệu, đối số của hàm có giá trị mà hàm không hỗ trợ. Ví dụ khi thực hiện lệnh int("1.55") sẽ sinh lỗi loại này |
| IndentationError | Lỗi khi các dòng lệnh thụt vào không thẳng hàng hoặc không đúng vị trí |
| SyntaxError | Lỗi cú pháp |
3. Luyện tập (Practice)
Bài tập thực hành
Bài tập 1: Nhận biết lỗi ngoại lệ
Các lệnh sau có sinh lỗi chương trình không? Nếu có thì mã lỗi là gì?
a)
Phân tích:
- Danh sách A có 5 phần tử (chỉ số 0-4)
- Vòng lặp chạy từ 1 đến 5 (len(A) + 1 = 6)
- Khi k = 5, A[5] sẽ gây ra IndexError
b)
Phân tích:
- Không có lỗi, Python tự động chuyển số 1010101 thành xâu khi nối với xâu
Bài tập 2: Phân tích lỗi có thể xảy ra
Để tính giá trị trung bình của một danh sách số A, người lập trình đã dùng lệnh sau để tính:
Lệnh này có thể sinh lỗi ngoại lệ không? Nếu có thì là những lỗi gì?
Phân tích các lỗi có thể xảy ra:
- ZeroDivisionError:
- Khi danh sách A rỗng (len(A) = 0)
- sum(A) = 0, len(A) = 0 → chia cho 0
- TypeError:
- Khi danh sách A chứa các phần tử không phải số
- Ví dụ: A = [1, 2, "abc", 3]
- sum(A) không thể thực hiện được
- NameError:
- Khi biến A chưa được định nghĩa
Bài tập 3: Xác định mã lỗi
Hãy nêu mã lỗi ngoại lệ của mỗi lệnh sau nếu xảy ra lỗi:
a) A[1.5] → TypeError (chỉ số phải là số nguyên)
b) int("abc") → ValueError (không thể chuyển "abc" thành số nguyên)
c) "10"*3.5 → TypeError (không thể nhân xâu với số thực)
d) 12 + x(10) → TypeError (x không phải là hàm hoặc không được định nghĩa)
Thực hành tại máy:
Viết chương trình nhập các số nguyên m, n từ bàn phím, cách nhau bởi dấu cách. Chương trình đưa ra tổng, hiệu, thương của hai số đã nhập.
Chương trình mẫu:
Các khả năng sinh lỗi của chương trình:
- Các số m, n khi nhập vào không là số nguyên → ValueError
- Giữa hai số m, n không có dấu cách → IndexError
- Số n nhập vào là số 0 → ZeroDivisionError
4. Vận dụng (Application)
Bài tập vận dụng
Bài tập 1: Chương trình tìm ước số với kiểm thử
Giả sử em được yêu cầu viết một chương trình nhập số tự nhiên n từ bàn phím, kết quả đưa ra là danh sách các ước số thực sự của n, tính cả 1 và không tính n. Hãy viết chương trình và kiểm tra các khả năng sinh lỗi khi thực hiện chương trình.
Chương trình mẫu:
Kiểm thử các trường hợp:
- Dữ liệu biên: n = 1, n = 2
- Dữ liệu bất thường: n = 0, n = -5, nhập "abc"
- Dữ liệu thông thường: n = 10, n = 100
Bài tập 2: Tạo chương trình sinh lỗi NameError
Em hãy viết một chương trình nhỏ để khi chạy sẽ sinh mã lỗi NameError.
Chương trình mẫu:
Bài tập 3: Chương trình nhập danh sách số với kiểm thử
Viết chương trình nhập số tự nhiên n và nhập lần lượt n số nguyên đưa vào danh sách số A. Sau khi nhập xong in danh sách A ra màn hình.
Chương trình mẫu với xử lý lỗi:
Thảo luận:
- Tại sao cần kiểm thử chương trình với nhiều bộ dữ liệu khác nhau?
- Lợi ích của việc in thông số trung gian khi gỡ lỗi là gì?
- Khi nào nên dùng try...except và khi nào nên dùng if...else để kiểm tra lỗi?
Ghi nhớ kiến thức 🎯
- ✅ Kiểm thử: dùng các bộ test để tìm lỗi trong chương trình.
- ✅ Dữ liệu biên: các giá trị ranh giới dễ gây lỗi nhất.
- ✅ In thông số trung gian: cách đơn giản để theo dõi dòng chảy chương trình.
- ✅ ZeroDivisionError: lỗi chia cho 0.
- ✅ IndexError: lỗi truy cập chỉ số ngoài phạm vi.
- ✅ ValueError: lỗi chuyển đổi kiểu dữ liệu.
- ✅ Kiểm thử giúp chương trình đáng tin cậy hơn.