Thể dục
Trong bài tập này, bạn sử dụng Pytest để kiểm tra một hàm. Sau đó, bạn tìm và khắc phục một số sự cố tiềm ẩn với hàm gây ra kiểm tra lỗi. Xem xét các lỗi và sử dụng báo cáo lỗi phong phú của Pytest là điều cần thiết để xác định và khắc phục các kiểm tra hoặc lỗi có vấn đề trong mã sản xuất.
Đối với bài tập này, chúng ta sử dụng một hàm được gọi là admin_command() chấp nhận lệnh hệ thống làm đầu vào và tùy chọn đặt tiền tố bằng công cụ sudo hệ thống. Hàm này có một lỗi mà bạn khám phá bằng cách viết bài kiểm tra.
Bước 1 - Thêm một tệp với các bài kiểm tra cho bài tập này
Sử dụng quy ước tên tệp của Python cho các tệp thử nghiệm, tạo một tệp thử nghiệm mới. Đặt tên cho tệp test_exercise.py và thêm mã sau:
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return commandHàm
admin_command()danh sách dưới dạng dữ liệu nhập bằng cách dùng đối sốcommandvà tùy chọn có thể đặt tiền tố cho danh sách bằngsudo. Nếu tham đối từsudođược đặt thànhFalse, thì nó trả về cùng một lệnh được cung cấp với input.Trong cùng một tệp, chắp thêm các kiểm tra cho hàm
admin_command()này. Các kiểm tra sử dụng một phương pháp trợ giúp trả về một lệnh mẫu:class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
Ghi
Không phổ biến khi có các kiểm tra trong cùng một tệp với mã thực tế. Để đơn giản, các ví dụ trong bài tập này có mã thực tế trong cùng một tập tin. Trong các dự án Python trong thế giới thực, các thử nghiệm thường được phân tách bởi các tệp và thư mục từ mã mà chúng đang thử nghiệm.
Bước 2 - Chạy các kiểm tra và xác định sự cố
Bây giờ, tệp thử nghiệm đã có một hàm để kiểm tra và một vài kiểm tra để xác minh hành vi của nó, đã đến lúc chạy các bài kiểm tra và làm việc với lỗi.
Thực hiện tệp với Python:
$ pytest test_exercise.pyQuá trình chạy sẽ hoàn tất khi một kiểm tra đi qua và một lần thất bại và đầu ra lỗi phải tương tự như đầu ra sau đây:
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================Đầu ra không thành công trên kiểm
test_sudo()thử nghiệm. Pytest đang cung cấp thông tin chi tiết về hai danh sách đang được so sánh. Trong trường hợp này,resultbiến số không có lệnhsudotrong đó, đó là điều mà kiểm tra mong đợi.
Bước 3 - Sửa lỗi và làm cho các bài kiểm tra vượt qua
Trước khi thực hiện bất kỳ thay đổi nào, bạn phải hiểu tại sao có một sự thất bại ngay từ đầu. Mặc dù bạn có thể thấy rằng sự mong đợi không được đáp ứng (sudo không phải là kết quả), bạn phải tìm hiểu lý do tại sao.
Hãy xem các dòng mã sau đây từ hàm admin_command() khi điều kiện sudo=True được đáp ứng:
if sudo:
["sudo"] + command
Thao tác của danh sách không được dùng để trả về giá trị. Vì nó không được trả về, hàm sẽ trả về lệnh mà không sudo luôn.
Cập nhật hàm
admin_command()để trả về thao tác danh sách sao cho kết quả đã sửa đổi được sử dụng khi yêusudolệnh. Hàm được cập nhật sẽ có dạng như sau:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return commandChạy lại kiểm tra với Pytest. Hãy thử tăng mức độ chi tiết của đầu ra bằng cách sử
-vvới Pytest:$ pytest -v test_exercise.pyBây giờ hãy xác minh đầu ra. Nó sẽ hiển thị hai bài kiểm tra qua bây giờ:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
Ghi
Vì hàm có thể làm việc với nhiều giá trị hơn của vỏ khác nhau, nên thêm nhiều kiểm tra để bao gồm các biến thể đó. Điều này sẽ ngăn các thay đổi trong tương lai đối với hàm gây ra hành vi khác (không mong muốn).
Bước 4 - Thêm mã mới với các kiểm tra
Sau khi thêm kiểm tra trong các bước trước đó, bạn sẽ cảm thấy thoải mái khi thực hiện nhiều thay đổi hơn đối với chức năng và xác minh chúng bằng các kiểm tra. Ngay cả khi các thay đổi không được bảo hiểm bởi các bài kiểm tra hiện có, bạn có thể cảm thấy tự tin rằng bạn không phá vỡ bất kỳ giả định trước đó.
Trong trường hợp này, hàm admin_command() tin một cách mù quáng rằng đối số command luôn là một danh sách. Hãy cải thiện điều đó bằng cách đảm bảo rằng một ngoại lệ với thông báo lỗi hữu ích được nêu ra.
Trước tiên, tạo một thử nghiệm chụp hành vi. Mặc dù hàm chưa được cập nhật, hãy thử một phương pháp thử nghiệm đầu tiên (còn được gọi là Test Driven Development hoặc TDD).
- Cập nhật test_exercise.py tệp mới để tệp
pytestnhập ở trên cùng. Kiểm tra này sử dụng người trợ giúp nội bộ từpytestframework:
import pytest- Bây giờ chắp thêm một bài kiểm tra mới cho lớp học để kiểm tra ngoại lệ. Kiểm tra này dự kiến một
TypeErrortừ hàm khi giá trị được chuyển cho nó không phải là danh sách:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)- Cập nhật test_exercise.py tệp mới để tệp
Chạy lại các bài kiểm tra với Pytest. Họ nên tất cả vượt qua:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================Việc kiểm tra là đủ tốt để kiểm tra
TypeErrornhưng sẽ là tốt để thêm mã với một thông báo lỗi hữu ích.Cập nhật hàm để nâng cao hiệu
TypeErrorrõ ràng với thông báo lỗi hữu ích:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return commandCuối cùng, cập nhật
test_non_list_commands()pháp để kiểm tra thông báo lỗi:def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"Kiểm tra cập nhật sử
errorlàm biến chứa tất cả thông tin ngoại lệ. Sửerror.value.args, bạn có thể xem các đối số của ngoại lệ. Trong trường hợp này, tham đối đầu tiên có chuỗi lỗi mà kiểm tra có thể kiểm tra.
Kiểm tra công việc của bạn
Tại thời điểm này, bạn nên có một tệp thử nghiệm Python có tên test_exercise.py bao gồm:
- Hàm
admin_command()chấp nhận một đối số và một đối số từ khóa. - Ngoại
TypeErrorvới thông báo lỗi hữu ích trong hàmadmin_command()này. - Một lớp
TestAdminCommand()kiểm tra có một phương pháp trợcommand()và ba phương pháp kiểm tra giúp kiểm traadmin_command()năng.
Tất cả các xét nghiệm nên vượt qua mà không có lỗi khi bạn chạy chúng trong thiết bị đầu cuối.