Chế giễu bằng Python: unittest.mock, patch và những sai lầm khiến bài kiểm tra nói dối

  1. Trang chủ
  2. Blog
  3. Tự động hóa thử nghiệm

Tự động hóa thử nghiệm

Chế nhạo bằng Python: unittest.mock, patch và những sai lầm khiến thử nghiệm nói dối

Mô phỏng cho phép bạn kiểm tra mã mà không cần mạng, cơ sở dữ liệu hoặc đồng hồ thực. Sử dụng bất cẩn, họ cũng để mã bị hỏng vượt qua. Đây là cách sử dụng unittest.mock tốt nhất với mỗi lần chạy ví dụ.

Đọc 8 phút
Mocking trong Python: unittest.mock, patch và những lỗi khiến test nói dối

Điểm chính

  • mock đại diện cho một đối tượng thực, chẳng hạn như ứng dụng khách API, vì vậy các thử nghiệm chạy nhanh và ngoại tuyến.
  • Sử dụng return_value cho câu trả lời cố định và side_effect cho lỗi hoặc chuỗi.
  • Vá tên ở nơi nó được sử dụng, chứ không phải ở nơi nó được xác định. Đây là lỗi phổ biến nhất.
  • Sử dụng spec hoặc autospec để tránh lỗi chính tả và đối số sai thay vì chuyển âm thầm.

Hàm của bạn gọi API thời tiết. Bài kiểm tra của bạn không nên phụ thuộc vào Internet, khóa API hoặc thời tiết ngày mai. Tính năng tích hợp sẵn của Python unittest.mock cho phép bạn thay thế lệnh gọi đó bằng một lệnh thay thế trả về chính xác những gì bạn chọn.

Chúng tôi đã viết một mô-đun weather nhỏ và chín bài kiểm tra, tất cả đều chạy bằng Python 3.13 nhỏ nhất. Tất cả chín đều vượt qua và kết quả đầu ra được in bên dưới là từ lần chạy đó.

Tại sao lại chế nhạo?

  • Tốc độ: không cần gọi mạng hoặc thiết lập cơ sở dữ liệu.
  • Độ tin cậy: các thử nghiệm không thất bại do dịch vụ của bên thứ ba không hoạt động.
  • Kiểm soát: bạn có thể mô phỏng thời gian chờ, lỗi và dữ liệu kỳ lạ theo yêu cầu.
  • Cách ly: bạn kiểm tra logic của mình chứ không phải máy chủ của người khác.

Mô phỏng chủ yếu thuộc về kiểm thử đơn vị. Kiểm tra tích hợp vẫn phải nói chuyện với các phần phụ thuộc thực tế hoặc thực tế.

Mô phỏng cơ bản

from unittest.mock import Mock

api = Mock()
api.get_price.return_value = 499

print(api.get_price("pen"))                # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list)        # [call('pen')]
print(api.anything_at_all())               # <Mock name='mock.anything_at_all()' ...>

Mock ghi lại mọi cuộc gọi để bạn có thể kiểm tra xem nó được sử dụng như thế nào. Tuy nhiên, hãy chú ý đến dòng cuối cùng. Một Mock đơn giản sẽ phát minh ra bất kỳ thuộc tính nào bạn yêu cầu. Sự linh hoạt đó rất thuận tiện nhưng cũng nguy hiểm, như chúng ta sẽ thấy.

MagicMock là ý tưởng tương tự nhưng cũng hỗ trợ các phương thức “ma thuật” như __len__ và __iter__. patch() sử dụng MagicMock theo mặc định.

side_effect: lỗi và trình tự

side_effect kiểm soát những gì xảy ra trên mỗi cuộc gọi. Đặt nó thành một ngoại lệ để mô phỏng lỗi:

api.fetch.side_effect = ConnectionError("down")
api.fetch()   # raises ConnectionError

Đặt nó vào danh sách để trả về các kết quả khác nhau trong các cuộc gọi liên tiếp. Điều này là hoàn hảo để thử nghiệm logic thử lại:

def fetch_with_retry(api, attempts=3):
    for _ in range(attempts):
        try:
            return api.fetch()
        except ConnectionError:
            continue
    raise ConnectionError("all attempts failed")

api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api))    # {'ok': True}
print(api.fetch.call_count)     # 2

Cuộc gọi đầu tiên không thành công, cuộc gọi thứ hai thành công và quá trình kiểm tra đã chứng minh chức năng được thử lại chính xác một lần. Việc mô phỏng điều này với một dịch vụ thực tế là gần như không thể.

patch(): thay thế tạm thời một cái gì đó

Thường thì phần phụ thuộc không được truyền vào. Nó được nhập vào bên trong mô-đun. patch() hoán đổi nó trong suốt thời gian thử nghiệm và khôi phục nó sau đó:

# weather.py
from http_client import get as requests_get

def report(city):
    data = requests_get(f"https://api.example.com/weather?city={city}")
    return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch

with patch("weather.requests_get") as fake_get:
    fake_get.return_value = {"temp_c": 31}
    print(weather.report("Pune"))   # Pune: 31°C
    fake_get.assert_called_once_with(
        "https://api.example.com/weather?city=Pune")

Bạn có thể sử dụng patch làm trình quản lý bối cảnh như thế này hoặc làm công cụ trang trí, chuyển mô hình này làm đối số.

Bản vá nơi nó được sử dụng, không phải nơi nó được xác định

Đây là sai lầm hầu như ai cũng mắc phải một lần. Hàm get thực sự tồn tại trong http_client, vì vậy việc vá nó ở đó là điều tự nhiên:

with patch("http_client.get") as fake_get:
    fake_get.return_value = {"temp_c": 99}
    weather.report("Pune")

# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune

Chức năng thực sự đã chạy. Khi weather.py thực hiện from http_client import get as requests_get, nó tạo ra tên riêng trỏ vào hàm ban đầu. Bản vá http_client.get đã đổi tên khác nên weather chưa bao giờ thấy bản mô phỏng. http_client.get của chúng tôi cố tình tăng cược nên sai sót rất lớn. Trong một dự án thực, nó có thể lặng lẽ gọi một API trực tiếp.

Quy tắc: vá tên trong mô-đun đang thử nghiệm, viết là "module_under_test.name". Nếu mô-đun đã thực hiện import http_client và gọi http_client.get() thì việc vá lỗi "http_client.get" là chính xác vì việc tra cứu diễn ra tại thời điểm gọi.

spec và autospec: làm cho các bản mô phỏng trở nên nghiêm ngặt

Một Mock đơn giản chấp nhận mọi thứ, kể cả lỗi chính tả. Với một thông số kỹ thuật, nó chỉ cho phép các thuộc tính mà lớp thực có:

api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'

loose = Mock()
loose.get_temprature("Pune")    # returns a Mock, no error at all

Bản mô phỏng lỏng lẻo vui vẻ chấp nhận phương pháp sai chính tả, vì vậy một bài kiểm tra sử dụng nó có thể vượt qua mặc dù mã thực sẽ bị lỗi. autospec đi xa hơn và kiểm tra chữ ký phương thức:

with patch("weather.WeatherClient", autospec=True) as Fake:
    client = Fake.return_value
    client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments

Thích autospec=True hoặc create_autospec() khi chế nhạo lớp học của riêng bạn. Nó giữ cho các mô tả trung thực khi mã thực thay đổi.

Ngày và giờ chế nhạo

Mã phụ thuộc vào ngày hôm nay rất khó kiểm tra. Bọc đồng hồ trong một chức năng nhỏ và vá:

def today():
    return datetime.date.today()

def is_weekend():
    return today().weekday() >= 5

@patch("weather.today")
def test_date(self, fake_today):
    fake_today.return_value = datetime.date(2026, 11, 7)
    print(weather.is_weekend())   # True (a Saturday)

Bạn không thể dễ dàng vá trực tiếp datetime.date.today vì các loại tích hợp không cho phép đặt thuộc tính của chúng. Các thư viện như Freezegun và Time-machine xử lý việc này một cách đầy đủ hơn.

Khẳng định trên mô hình

phương pháp Séc
assert_called() Đã gọi ít nhất một lần
assert_called_once() Được gọi đúng một lần
assert_called_with(...) Cuộc gọi cuối cùng đã sử dụng các đối số này
assert_called_once_with(...) Chính xác một cuộc gọi, với những đối số này
assert_any_call(...) Bất kỳ cuộc gọi nào đều sử dụng các đối số này
assert_not_called() Chưa bao giờ gọi

Khi một xác nhận không thành công, thông báo sẽ rõ ràng. Chúng tôi đã gọi assert_called_once() trên một phương thức chưa bao giờ chạy:

AssertionError: Expected 'save' to have been called once. Called 0 times.

Các phiên bản Python cũ hơn cho phép các phương thức xác nhận sai chính tả, chẳng hạn như assret_called_once, được chuyển âm thầm. Các phiên bản hiện đại có lỗi đối với các tên bắt đầu bằng assert hoặc các lỗi chính tả phổ biến của nó, nhưng thông số kỹ thuật vẫn là biện pháp bảo vệ mạnh mẽ hơn.

pytest và mocks

Mọi thứ ở trên cũng hoạt động trong pytest. Nhiều người dùng pytest cũng sử dụng công cụ cố định monkeypatch tích hợp để thay thế đơn giản hoặc plugin pytest-mock, cung cấp công cụ cố định mocker tự động hoàn tác các bản vá. Quy tắc “bản vá được sử dụng” áp dụng cho tất cả chúng. Xem hướng dẫn pytest của chúng tôi để biết cách thiết lập và cố định.

Khi nào không nên chế giễu

  • Đừng chế nhạo những gì bạn đang thử nghiệm. Nếu mô hình làm được tất cả công việc thì bài kiểm tra không chứng minh được điều gì.
  • Tránh chế giễu các đối tượng có giá trị đơn giản. Hãy sử dụng đồ thật.
  • Đừng mô phỏng mọi thứ. Các thử nghiệm chứa đầy mô phỏng thường bị hỏng bất cứ khi nào mã được tái cấu trúc, ngay cả khi hành vi đúng.
  • Giữ một số thử nghiệm tích hợp thực tế. Bản mô phỏng mã hóa các giả định của bạn về một dịch vụ và những giả định đó có thể sai.

Mô phỏng cũng dễ dàng tăng cường phạm vi bảo hiểm của mã vì các đường dẫn mô phỏng chạy mà không có bất kỳ hành vi thực sự nào đằng sau chúng.

Những lỗi chế giễu thường gặp

  • Đổ sai tên. Đã đề cập ở trên và vẫn là nguyên nhân số một khiến “bản mô phỏng của tôi bị bỏ qua”.
  • Quên đặt return_value. Mã được kiểm tra sau đó nhận được một đối tượng Mock thay vì dữ liệu. Nó có thể đi thẳng qua hàm của bạn và vô tình vượt qua bài kiểm tra, chẳng hạn như khi bạn chỉ kiểm tra xem kết quả có đúng hay không.
  • Xác nhận trên bản mô phỏng thay vì kết quả. Kiểm tra xem save() đã được gọi yếu hơn so với kiểm tra những gì đã được lưu. Sử dụng assert_called_once_with(...) với các đối số thực hoặc tốt hơn là xác nhận kết quả của hàm.
  • Các bản vá bị rò rỉ. Gọi patcher.start() mà không có stop() sẽ để lại bản giả tại chỗ cho các lần kiểm tra sau. Sử dụng trình quản lý bối cảnh, trình trang trí hoặc addCleanup(patcher.stop).
  • Các trình trang trí được xếp chồng lên nhau không đúng thứ tự. Với một số trình trang trí @patch, trình trang trí ở dưới cùng được chuyển làm đối số đầu tiên. Việc trộn chúng một cách âm thầm sẽ cấu hình sai mô hình.
  • Chế nhạo thư viện mà bạn không sở hữu qua nhiều bài kiểm tra. Thay vào đó, hãy gói nó trong một lớp học nhỏ của riêng bạn và mô phỏng nó. Sau đó, các bài kiểm tra của bạn sẽ mô tả nhu cầu của bạn chứ không phải API của thư viện.

Giả, sơ khai và giả mạo

Những từ này thường được sử dụng một cách lỏng lẻo, nhưng sự khác biệt sẽ giúp ích khi thảo luận về các bài kiểm tra. sơ khai chỉ trả về các câu trả lời soạn sẵn. mock cũng ghi lại các cuộc gọi để bạn có thể xác minh cách nó được sử dụng. fake là cách triển khai đơn giản, đang hoạt động, chẳng hạn như kho lưu trữ trong bộ nhớ thay vì cơ sở dữ liệu. Mock của Python có thể hoạt động như một bản sơ khai hoặc một bản mô phỏng. Hàng giả là các lớp thông thường mà bạn tự viết và chúng thường dẫn đến các thử nghiệm tồn tại trong quá trình tái cấu trúc tốt hơn, vì chúng kiểm tra hành vi thay vì các lệnh gọi chính xác.

Một nguyên tắc nhỏ là thử nghiệm các khía cạnh của hệ thống của bạn: mạng, đồng hồ, số ngẫu nhiên, hệ thống tệp và các dịch vụ trả phí của bên thứ ba. Bên trong những cạnh đó, thích đồ vật thật hơn. Các thử nghiệm được xây dựng theo cách này sẽ hoạt động nhanh và vẫn thực thi mã bạn thực sự đã viết.

Lời kết

Mocking là một công cụ chính xác: thay thế các phần phụ thuộc chậm hoặc không thể đoán trước, kiểm soát những gì chúng trả về và kiểm tra cách mã của bạn sử dụng chúng. Hai thói quen ngăn ngừa hầu hết các vấn đề. Vá tên trong mô-đun đang được kiểm tra và sử dụng autospec để các cuộc gọi mô phỏng của bạn từ chối đối tượng thực sẽ từ chối.

Câu hỏi thường gặp

Chế giễu trong Python là gì?

Chế độ mô phỏng thay thế một đối tượng thực, chẳng hạn như ứng dụng khách API hoặc cơ sở dữ liệu, bằng một đối tượng giả mà bạn kiểm soát trong quá trình thử nghiệm. Python bao gồm điều này trong mô-đun unittest.mock.

Sự khác biệt giữa Mock và MagicMock là gì?

MagicMock là một Mô phỏng cũng hỗ trợ các phương pháp ma thuật như __len__, __iter__ và __enter__. patch() tạo MagicMock theo mặc định.

Tại sao bản vá của tôi không hoạt động?

Thông thường là do bạn đã vá nơi đối tượng được xác định thay vì nơi nó được sử dụng. Vá tên bên trong mô-đun đang được thử nghiệm.

Sự khác biệt giữa return_value và side_effect là gì?

return_value đặt kết quả cố định. side_effect có thể đưa ra một ngoại lệ, trả về các giá trị từ danh sách theo thứ tự hoặc gọi một hàm.

Autospec làm gì?

Nó xây dựng một bản mô phỏng từ đối tượng thực, do đó, việc thiếu các thuộc tính và đối số sai sẽ gây ra lỗi, giống như với mã thực.

Tiếp tục học hỏi

Làm việc với Stack Quora

Cần trợ giúp về kiểm tra, WordPress hoặc SEO?

Chúng tôi giúp các nhóm phát triển phần mềm tốt hơn và phát triển trang web của họ. Hãy cho chúng tôi biết bạn đang làm gì và chúng tôi sẽ đề xuất bước tiếp theo rõ ràng.

Tiếp tục đọc

Thêm hướng dẫn về thử nghiệm, AI và phát triển hiện đại

Bạn thích bài viết này? Chọn một chủ đề và chuyển ngay sang phần hướng dẫn mới nhất của chúng tôi.

Trí tuệ nhân tạo

Giải thích về ảo giác AI: Tại sao các mô hình ngôn ngữ lại tạo nên mọi thứ và cách giảm thiểu nó

Ảo giác AI đã giải thích: chúng là gì, tại sao mô hình ngôn ngữ tạo ra chúng, các loại phổ biến, cách giảm thiểu chúng bằng cách căn cứ và nhắc nhở …

Đọc bài viết →
DevOps

Giải thích về các biến môi trường: Tệp .env, bí mật và cấu hình được thực hiện đúng

Các biến môi trường được giải thích bằng các ví dụ Python mà chúng tôi đã chạy: đọc và thiết lập chúng, tại sao mọi giá trị đều là một chuỗi, tệp .e…

Đọc bài viết →
Phát triển phần mềm

Giải thích về giới hạn tỷ lệ API: Thuật toán, tiêu đề và xử lý lỗi 429

Giới hạn tốc độ API được giải thích bằng mô phỏng mà chúng tôi đã chạy: nhóm mã thông báo, cửa sổ cố định, cửa sổ trượt, 429 phản hồi, tiêu đề giới …

Đọc bài viết →
Tự động hóa kiểm thử

Chế giễu bằng Python: unittest.mock, patch và những sai lầm khiến bài kiểm tra nói dối

Mô phỏng bằng Python với unittest.mock, đã được thử nghiệm: Mock và return_value, side_effect, vá lỗi nơi sử dụng tên, thông số kỹ thuật và thông số…

Đọc bài viết →
Kiểm thử phần mềm

Giải thích về phạm vi bảo hiểm của mã: Phạm vi tuyến so với chi nhánh và ý nghĩa thực sự 100%

Phạm vi bảo hiểm mã được giải thích bằng cách chạy phạm vi bảo hiểm thực tế: phạm vi bảo hiểm dòng, chi nhánh và điều kiện, đọc báo cáo, lý do tại s…

Đọc bài viết →
Cơ sở dữ liệu & SQL

DELETE vs TRUNCATE vs DROP trong SQL: Sự khác biệt, khôi phục và thời điểm sử dụng mỗi loại

DELETE vs TRUNCATE vs DROP được giải thích bằng SQL đã được thử nghiệm: mỗi loại loại bỏ, khôi phục, kích hoạt, đặt lại danh tính, tốc độ và Postgre…

Đọc bài viết →
Cơ sở dữ liệu & SQL

Giải thích về Chế độ xem SQL: Tạo, sử dụng và tránh những cạm bẫy, kèm theo ví dụ

Chế độ xem SQL được giải thích bằng các truy vấn chúng tôi đã chạy: tạo chế độ xem, kết quả trực tiếp, ẩn cột, hiệu suất, chế độ xem có thể cập nhật…

Đọc bài viết →
Phát triển phần mềm

Giao diện TypeScript so với loại: Sự khác biệt thực sự, được thử nghiệm bằng trình biên dịch

Giao diện TypeScript so với loại, được kiểm tra bằng trình biên dịch thực: cả hai có thể làm gì, chỉ một người có thể làm gì, hợp nhất khai báo, mở …

Đọc bài viết →
Phát triển phần mềm

Giải thích về Python *args và **kwargs: Hàm linh hoạt với ví dụ rõ ràng

Python *args và **kwargs được giải thích bằng mã chúng tôi đã chạy: đóng gói và giải nén, thứ tự tham số, đối số chỉ từ khóa và chỉ vị trí, trình tr…

Đọc bài viết →
Phát triển phần mềm

Hàm Lambda của Python: Cú pháp, cách sử dụng thực tế và khi nào không nên sử dụng chúng

Các hàm lambda của Python được giải thích bằng mã chúng tôi đã chạy: cú pháp, sắp xếp bằng khóa, bản đồ và bộ lọc, các phần đóng, bẫy liên kết muộn …

Đọc bài viết →
Trí tuệ nhân tạo

Đại lý AI là gì? Cách chúng hoạt động, với vòng lặp tác nhân đơn giản, bạn có thể chạy

Tác nhân AI là gì? Hướng dẫn đơn giản về vòng lặp tác nhân, công cụ, bộ nhớ và lan can, với một vòng lặp Python nhỏ mà chúng tôi đã chạy, những rủi …

Đọc bài viết →
DevOps

Giám sát và Khả năng quan sát: Sự khác biệt thực sự, với nhật ký, số liệu và dấu vết

Giải thích về giám sát và khả năng quan sát: lỗi đã biết và chưa biết, nhật ký, số liệu và dấu vết, hướng dẫn về sự cố thanh toán, tín hiệu vàng, Op…

Đọc bài viết →
DevOps

Docker Volumes vs Bind Mounts: Duy trì dữ liệu vùng chứa đúng cách

Giải thích về khối lượng Docker và liên kết gắn kết: tại sao dữ liệu vùng chứa biến mất, khối lượng được đặt tên, liên kết gắn kết, tmpfs, ví dụ về …

Đọc bài viết →
Tự động hóa kiểm thử

TDD vs BDD: Key Differences, One Feature Built Both Ways and When to Use Each

TDD và BDD được giải thích bằng một tính năng giảm giá được xây dựng theo cả hai cách: tái cấu trúc màu đỏ-xanh với mã chúng tôi đã chạy, kịch bản C…

Đọc bài viết →
Phát triển phần mềm

Git merge vs rebase: khác biệt, ví dụ và khi nào dùng cái nào

Git merge vs rebase được giải thích bằng đầu ra log thực tế, bảng so sánh, quy tắc vàng của rebase và quy trình làm việc nhóm an toàn.

Đọc bài viết →
Phát triển phần mềm

List comprehension trong Python: cú pháp, ví dụ và khi nào nên tránh

List comprehension trong Python: cú pháp, lọc dữ liệu, if-else, vòng lặp lồng nhau, thời gian đo thực tế và khi nào vòng lặp for thông thường tốt hơn.

Đọc bài viết →
Cơ sở dữ liệu & SQL

Giải thích SQL JOIN kèm ví dụ (Inner, Left, Right, Full)

Hiểu SQL JOIN từng bước với bảng và truy vấn mẫu: INNER, LEFT, RIGHT, FULL, CROSS và SELF, cùng lỗi thường gặp và mẹo hiệu năng.

Đọc bài viết →