Bài 11.3. Python - Kiểm tra một lớp

Trong phần đầu của chương này, bạn đã viết các bài kiểm tra cho một hàm đơn lẻ. Bây giờ bạn sẽ viết các bài kiểm tra cho một lớp. Bạn sẽ sử dụng các lớp trong nhiều chương trình của riêng bạn, vì vậy rất hữu ích khi có thể chứng minh rằng các lớp của bạn hoạt động đúng. Nếu bạn có các bài kiểm tra thành công cho một lớp mà bạn đang làm việc, bạn có thể tự tin rằng các cải tiến bạn thực hiện đối với lớp sẽ không vô tình làm hỏng hành vi hiện tại của nó.

Một số loại khẳng định

Cho đến nay, bạn đã thấy chỉ một loại khẳng định: một tuyên bố rằng một chuỗi có một giá trị cụ thể. Khi viết một bài kiểm tra, bạn có thể đưa ra bất kỳ tuyên bố nào có thể được biểu diễn dưới dạng một câu lệnh điều kiện. Nếu điều kiện là True như mong đợi, giả định của bạn về cách phần đó của chương trình hoạt động sẽ được xác nhận; bạn có thể tự tin rằng không có lỗi nào tồn tại. Nếu điều kiện bạn giả định là True thực sự là False, bài kiểm tra sẽ thất bại và bạn sẽ biết có một vấn đề cần giải quyết. Bảng 11-1 hiển thị một số loại khẳng định hữu ích nhất mà bạn có thể bao gồm trong các bài kiểm tra ban đầu của mình.

Bảng 11-1: Các câu lệnh khẳng định thường được sử dụng trong các bài kiểm tra
Khẳng định Tuyên bố
assert a == b Khẳng định rằng hai giá trị bằng nhau.
assert a != b Khẳng định rằng hai giá trị không bằng nhau.
assert a Khẳng định rằng a đánh giá là True.
assert not a Khẳng định rằng a đánh giá là False.
assert element in list Khẳng định rằng một phần tử có trong danh sách.
assert element not in list Khẳng định rằng một phần tử không có trong danh sách.

Đây chỉ là một vài ví dụ; bất kỳ điều gì có thể được biểu diễn dưới dạng một câu lệnh điều kiện đều có thể được bao gồm trong một bài kiểm tra.

Một lớp để kiểm tra

Kiểm tra một lớp tương tự như kiểm tra một hàm, vì phần lớn công việc liên quan đến việc kiểm tra hành vi của các phương thức trong lớp. Tuy nhiên, có một vài điểm khác biệt, vì vậy hãy viết một lớp để kiểm tra. Hãy xem xét một lớp giúp quản lý các khảo sát ẩn danh:

# filepath: /f:/blog/python-vy/11/survey.py
class AnonymousSurvey:
    """Collect anonymous answers to a survey question."""

    def __init__(self, question):
        """Store a question, and prepare to store responses."""
        self.question = question
        self.responses = []

    def show_question(self):
        """Show the survey question."""
        print(self.question)

    def store_response(self, new_response):
        """Store a single response to the survey."""
        self.responses.append(new_response)

    def show_results(self):
        """Show all the responses that have been given."""
        print("Survey results:")
        for response in self.responses:
            print(f"- {response}")

Lớp này bắt đầu với một câu hỏi khảo sát mà bạn cung cấp và bao gồm một danh sách trống để lưu trữ các phản hồi. Lớp có các phương thức để in câu hỏi khảo sát, thêm một phản hồi mới vào danh sách phản hồi và in tất cả các phản hồi được lưu trữ trong danh sách. Để tạo một thể hiện từ lớp này, tất cả những gì bạn cần cung cấp là một câu hỏi. Khi bạn có một thể hiện đại diện cho một khảo sát cụ thể, bạn hiển thị câu hỏi khảo sát bằng show_question(), lưu trữ một phản hồi bằng store_response(), và hiển thị kết quả bằng show_results().

Để chứng minh rằng lớp AnonymousSurvey hoạt động, hãy viết một chương trình sử dụng lớp này:

# filepath: /f:/blog/python-vy/11/language_survey.py
from survey import AnonymousSurvey

# Define a question, and make a survey.
question = "What language did you first learn to speak?"
language_survey = AnonymousSurvey(question)

# Show the question, and store responses to the question.
language_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:
    response = input("Language: ")
    if response == 'q':
        break
    language_survey.store_response(response)

# Show the survey results.
print("\nThank you to everyone who participated in the survey!")
language_survey.show_results()

Chương trình này định nghĩa một câu hỏi ("What language did you first learn to speak?") và tạo một đối tượng AnonymousSurvey với câu hỏi đó. Chương trình gọi show_question() để hiển thị câu hỏi và sau đó yêu cầu các phản hồi. Mỗi phản hồi được lưu trữ khi nó được nhận. Khi tất cả các phản hồi đã được nhập (người dùng nhập 'q' để thoát), show_results() in ra kết quả khảo sát:

What language did you first learn to speak?
Enter 'q' at any time to quit.
Language: English
Language: Spanish
Language: English
Language: Mandarin
Language: q

Thank you to everyone who participated in the survey!
Survey results:
- English
- Spanish
- English
- Mandarin

Lớp này hoạt động cho một khảo sát ẩn danh đơn giản, nhưng giả sử chúng ta muốn cải thiện AnonymousSurvey và module mà nó nằm trong, survey. Chúng ta có thể cho phép mỗi người dùng nhập nhiều hơn một phản hồi, chúng ta có thể viết một phương thức để liệt kê chỉ các phản hồi duy nhất và báo cáo số lần mỗi phản hồi được đưa ra, hoặc chúng ta thậm chí có thể viết một lớp khác để quản lý các khảo sát không ẩn danh.

Thực hiện các thay đổi như vậy sẽ có nguy cơ ảnh hưởng đến hành vi hiện tại của lớp AnonymousSurvey. Ví dụ, có thể trong khi cố gắng cho phép mỗi người dùng nhập nhiều phản hồi, chúng ta có thể vô tình thay đổi cách xử lý các phản hồi đơn lẻ. Để đảm bảo chúng ta không làm hỏng hành vi hiện tại khi phát triển module này, chúng ta có thể viết các bài kiểm tra cho lớp.

Kiểm tra lớp AnonymousSurvey

Hãy viết một bài kiểm tra để xác minh một khía cạnh của cách lớp AnonymousSurvey hoạt động. Chúng ta sẽ viết một bài kiểm tra để xác minh rằng một phản hồi đơn lẻ cho câu hỏi khảo sát được lưu trữ đúng cách:

# filepath: /f:/blog/python-vy/11/test_survey.py
from survey import AnonymousSurvey

def test_store_single_response():
    """Test that a single response is stored properly."""
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    language_survey.store_response('English')
    assert 'English' in language_survey.responses

Chúng ta bắt đầu bằng cách nhập khẩu lớp mà chúng ta muốn kiểm tra, AnonymousSurvey. Hàm kiểm tra đầu tiên sẽ xác minh rằng khi chúng ta lưu trữ một phản hồi cho câu hỏi khảo sát, phản hồi đó sẽ nằm trong danh sách các phản hồi của khảo sát. Một tên hàm mô tả tốt cho hàm này là test_store_single_response(). Nếu bài kiểm tra này thất bại, chúng ta sẽ biết từ tên hàm trong tóm tắt bài kiểm tra rằng có vấn đề khi lưu trữ một phản hồi đơn lẻ cho khảo sát.

Để kiểm tra hành vi của một lớp, chúng ta cần tạo một thể hiện của lớp. Chúng ta tạo một thể hiện gọi là language_survey với câu hỏi "What language did you first learn to speak?". Chúng ta lưu trữ một phản hồi đơn lẻ, 'English', bằng cách sử dụng phương thức store_response(). Sau đó, chúng ta xác minh rằng phản hồi đã được lưu trữ đúng cách bằng cách khẳng định rằng 'English' có trong danh sách language_survey.responses.

Mặc định, chạy lệnh pytest mà không có đối số sẽ chạy tất cả các bài kiểm tra mà pytest phát hiện trong thư mục hiện tại. Để tập trung vào các bài kiểm tra trong một tệp, truyền tên của tệp kiểm tra mà bạn muốn chạy. Ở đây chúng ta sẽ chạy chỉ một bài kiểm tra mà chúng ta đã viết cho AnonymousSurvey:

$ pytest test_survey.py
========================= test session starts =========================
--snip--
test_survey.py . [100%]
========================== 1 passed in 0.01s ==========================

Đây là một khởi đầu tốt, nhưng một khảo sát chỉ hữu ích nếu nó tạo ra nhiều hơn một phản hồi. Hãy xác minh rằng ba phản hồi có thể được lưu trữ đúng cách. Để làm điều này, chúng ta thêm một phương thức khác vào TestAnonymousSurvey:

# filepath: /f:/blog/python-vy/11/test_survey.py
from survey import AnonymousSurvey

def test_store_single_response():
    """Test that a single response is stored properly."""
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    language_survey.store_response('English')
    assert 'English' in language_survey.responses

def test_store_three_responses():
    """Test that three individual responses are stored properly."""
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    responses = ['English', 'Spanish', 'Mandarin']
    for response in responses:
        language_survey.store_response(response)
    for response in responses:
        assert response in language_survey.responses

Chúng ta gọi hàm mới là test_store_three_responses(). Chúng ta tạo một đối tượng khảo sát giống như chúng ta đã làm trong test_store_single_response(). Chúng ta định nghĩa một danh sách chứa ba phản hồi khác nhau và sau đó gọi store_response() cho mỗi phản hồi này. Khi các phản hồi đã được lưu trữ, chúng ta viết một vòng lặp khác và khẳng định rằng mỗi phản hồi bây giờ có trong danh sách language_survey.responses.

Khi chúng ta chạy lại tệp kiểm tra, cả hai bài kiểm tra (cho một phản hồi đơn lẻ và cho ba phản hồi) đều thành công:

$ pytest test_survey.py
========================= test session starts =========================
--snip--
test_survey.py .. [100%]
========================== 2 passed in 0.01s ==========================

Điều này hoạt động hoàn hảo. Tuy nhiên, các bài kiểm tra này hơi lặp lại, vì vậy chúng ta sẽ sử dụng một tính năng khác của pytest để làm cho chúng hiệu quả hơn.

Sử dụng Fixtures

Trong test_survey.py, chúng ta đã tạo một thể hiện mới của AnonymousSurvey trong mỗi hàm kiểm tra. Điều này ổn trong ví dụ ngắn mà chúng ta đang làm việc, nhưng trong một dự án thực tế với hàng chục hoặc hàng trăm bài kiểm tra, điều này sẽ gây ra vấn đề.

Trong kiểm tra, một fixture giúp thiết lập một môi trường kiểm tra. Thường thì điều này có nghĩa là tạo một tài nguyên được sử dụng bởi nhiều bài kiểm tra. Chúng ta tạo một fixture trong pytest bằng cách viết một hàm với decorator @pytest.fixture. Một decorator là một chỉ thị được đặt ngay trước một định nghĩa hàm; Python áp dụng chỉ thị này cho hàm trước khi nó chạy, để thay đổi cách mã hàm hoạt động. Đừng lo lắng nếu điều này nghe có vẻ phức tạp; bạn có thể bắt đầu sử dụng các decorator từ các gói bên thứ ba trước khi học cách viết chúng.

Hãy sử dụng một fixture để tạo một thể hiện khảo sát duy nhất có thể được sử dụng trong cả hai hàm kiểm tra trong test_survey.py:

# filepath: /f:/blog/python-vy/11/test_survey.py
import pytest
from survey import AnonymousSurvey

@pytest.fixture
def language_survey():
    """A survey that will be available to all test functions."""
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    return language_survey

def test_store_single_response(language_survey):
    """Test that a single response is stored properly."""
    language_survey.store_response('English')
    assert 'English' in language_survey.responses

def test_store_three_responses(language_survey):
    """Test that three individual responses are stored properly."""
    responses = ['English', 'Spanish', 'Mandarin']
    for response in responses:
        language_survey.store_response(response)
    for response in responses:
        assert response in language_survey.responses

Chúng ta cần nhập khẩu pytest bây giờ, vì chúng ta đang sử dụng một decorator được định nghĩa trong pytest. Chúng ta áp dụng decorator @pytest.fixture cho hàm mới language_survey(). Hàm này xây dựng một đối tượng AnonymousSurvey và trả về khảo sát mới.

Lưu ý rằng định nghĩa của cả hai hàm kiểm tra đã thay đổi; mỗi hàm kiểm tra bây giờ có một tham số gọi là language_survey. Khi một tham số trong một hàm kiểm tra khớp với tên của một hàm có decorator @pytest.fixture, fixture sẽ được chạy tự động và giá trị trả về sẽ được truyền vào hàm kiểm tra. Trong ví dụ này, hàm language_survey() cung cấp cả test_store_single_response()test_store_three_responses() với một thể hiện language_survey.

Không có mã mới trong bất kỳ hàm kiểm tra nào, nhưng lưu ý rằng hai dòng đã được loại bỏ khỏi mỗi hàm: dòng định nghĩa một câu hỏi và dòng tạo một đối tượng AnonymousSurvey.

Khi chúng ta chạy lại tệp kiểm tra, cả hai bài kiểm tra vẫn thành công. Các bài kiểm tra này sẽ đặc biệt hữu ích khi cố gắng mở rộng AnonymousSurvey để xử lý nhiều phản hồi cho mỗi người. Sau khi sửa đổi mã để chấp nhận nhiều phản hồi, bạn có thể chạy các bài kiểm tra này và đảm bảo rằng bạn không ảnh hưởng đến khả năng lưu trữ một phản hồi đơn lẻ hoặc một loạt các phản hồi riêng lẻ.

Cấu trúc trên gần như chắc chắn sẽ trông phức tạp; nó chứa một số mã trừu tượng nhất mà bạn đã thấy cho đến nay. Bạn không cần phải sử dụng fixtures ngay lập tức; tốt hơn là viết các bài kiểm tra có nhiều mã lặp lại hơn là không viết bài kiểm tra nào cả. Chỉ cần biết rằng khi bạn đã viết đủ bài kiểm tra mà sự lặp lại đang cản trở, có một cách đã được thiết lập để xử lý sự lặp lại. Ngoài ra, fixtures trong các ví dụ đơn giản như thế này không thực sự làm cho mã ngắn hơn hoặc dễ theo dõi hơn. Nhưng trong các dự án có nhiều bài kiểm tra, hoặc trong các tình huống mà cần nhiều dòng để xây dựng một tài nguyên được sử dụng trong nhiều bài kiểm tra, fixtures có thể cải thiện đáng kể mã kiểm tra của bạn.

Khi bạn muốn viết một fixture, hãy viết một hàm tạo ra tài nguyên được sử dụng bởi nhiều hàm kiểm tra. Thêm decorator @pytest.fixture vào hàm mới, và thêm tên của hàm này làm tham số cho mỗi hàm kiểm tra sử dụng tài nguyên này. Các bài kiểm tra của bạn sẽ ngắn hơn và dễ viết và bảo trì hơn từ thời điểm đó trở đi.

Comments

There are no comments at the moment.