Hướng dẫn Pytest 06 - Sử dụng Marks để đánh dấu test case

Sử dụng Marks để đánh dấu test case

Thông qua việc sử dụng pytest.mark, bạn có thể dễ dàng thiết lập metadata cho các test case. Ví dụ, một số marks tích hợp sẵn thường được sử dụng:

  • skip - Luôn bỏ qua test case này
  • skipif - Bỏ qua test case khi gặp điều kiện cụ thể
  • xfail - Khi gặp điều kiện cụ thể, tạo ra kết quả "expected failure"
  • parametrize - Chạy cùng một test case nhiều lần với các tham số khác nhau

Việc tạo custom marks hoặc áp dụng marks cho toàn bộ test class hoặc module rất đơn giản. Tài liệu có chứa các ví dụ về marks, bạn có thể tham khảo thêm tại phần sử dụng custom marks.

Lưu ý:
Marks chỉ có hiệu lực với test case, không áp dụng cho các phương thức fixtures.

Buộc phải khai báo marks: tùy chọn --strict

Khi sử dụng tham số dòng lệnh --strict, bất kỳ mark nào chưa được đăng ký trong file pytest.ini đều sẽ gây ra exception.

Cách đăng ký marks:

[pytest]
markers =
    chay_cham
    chay_lien_tuc

Điều này giúp ngăn người dùng vô tình gõ sai tên mark. Để bắt buộc thực thi quy tắc này, test suite nên thêm --strict vào addopts:

[pytest]
addopts = --strict
markers =
    chay_cham
    chay_lien_tuc

Cải tiến về Marks và cách lặp qua marks

Chức năng mới trong phiên bản 3.6
Cách triển khai marks truyền thống của pytest được thực hiện bằng việc đơn giản thêm thuộc tính vào __dict__ của hàm test. Kết quả là, marks có thể được truyền một cách không mong muốn thông qua kế thừa class. Ngoài ra, marks được áp dụng bằng decorator @pytest.mark và marks được thêm thông qua node.add_marker được lưu trữ ở các vị trí khác nhau, dẫn đến API để truy xuất chúng cũng không nhất quán.

Như vậy, nếu không tìm hiểu sâu vào cấu trúc bên trong của code test, gần như không thể sử dụng đúng cách parametrize một cách kỹ thuật, dẫn đến các bug khó hiểu và tinh vi khi sử dụng ở cấp độ nâng cao.

Tùy thuộc vào cách khai báo/thay đổi mark, bạn đều có thể nhận được một đối tượng MarkerInfo, đối tượng này cũng có thể chứa các marks từ class cùng cấp. Khi sử dụng parametrize marks hoặc node.add_marker, các marks MarkDecorators được khai báo trước đó bằng decorator sẽ bị loại bỏ. Đối tượng MarkerInfo thực chất là một view gộp của nhiều marks có cùng tên, tất nhiên MarkerInfo cũng có thể được sử dụng như một mark đơn lẻ.

Điều quan trọng nhất là, mặc dù marks được khai báo trên class/module, nhưng thực tế marks chỉ có thể được truy cập trong các functions. Lý do là module, class và functions/methods không thể truy cập marks theo cùng một cách.

Trong phiên bản pytest 3.6, một API mới để truy cập marks đã được giới thiệu nhằm giải quyết các vấn đề trong thiết kế ban đầu, cung cấp phương thức _pytest.nodes.Node.iter_marks() để lặp qua marks một cách nhất quán và xử lý lại nội bộ, điều này đã giải quyết tốt các vấn đề trong thiết kế ban đầu.

Nâng cấp code

Không khuyến khích sử dụng hàm Node.get_marker(name) ban đầu, vì nó trả về một đối tượng MarkerInfo nội bộ, đối tượng này chứa tên gộp và tất cả các tham số của tất cả các marks được áp dụng cho node đó.

Thông thường có hai cách tiếp cận để xử lý marks:

Trường hợp 1: Marks ghi đè lẫn nhau
Thứ tự quan trọng, nhưng bạn chỉ cần coi marks của mình như các đơn vị riêng biệt. Ví dụ, log_level('debug') trong test case sẽ ghi đè log_level('info') ở cấp module.

Trong trường hợp này, bạn có thể sử dụng Node.get_closest_marker(name):

# Thay thế cách này:
marker = item.get_marker("log_level")
if marker:
    level = marker.args[0]

# Bằng cách này:
marker = item.get_closest_marker("log_level")
if marker:
    level = marker.args[0]

Trường hợp 2: Sử dụng marks có điều kiện
Ví dụ, mark skipif(condition) nghĩa là bạn chỉ muốn test tất cả các trường hợp không thỏa mãn điều kiện, thứ tự không quan trọng. Bạn có thể coi mark này như một tập hợp thỏa mãn điều kiện đó.

Trong trường hợp này, lặp qua từng mark và xử lý riêng các tham số *args**kwargs của chúng.

# Thay thế cách này:
skipif = item.get_marker("skipif")
if skipif:
    for condition in skipif.args:
        # eval condition
        ...

# Bằng cách này:
for skipif in item.iter_markers("skipif"):
    condition = skipif.args[0]
    # eval condition

Nếu bạn không chắc chắn hoặc gặp bất kỳ khó khăn nào, bạn có thể cân nhắc việc tạo một issue để được hỗ trợ.

Lưu ý:
Trong các phiên bản chính tiếp theo của Pytest, chúng tôi sẽ giới thiệu marks dựa trên class, tại đó marks sẽ không còn bị giới hạn trong các instance của Mark.

Thẻ: pytest python testing marks test-automation

Đăng vào ngày 17 tháng 05 lúc 19:24