About

Work - Learn - Share

Lê Quang Thành : quangthanh010290@gmail.com | thanhlev@amazon.com.vn

Pytest và cách sử dụng

Table of contents

  1. Revision history
  2. Giới thiệu
  3. Hoạt động
    1. Ví dụ
    2. Giải thích
  4. Sử dụng fixtures
    1. Ví dụ
    2. Các biến thể của fixtures
      1. nested fixtures
      2. Sử dụng lại fixtures
      3. Autouse fixtures
      4. Fixture trả về một hàm
      5. Fixture có tham số [HOT]
      6. Chia sẽ fixtures giữa các test khác nhau [HOT]
        1. Phạm vi của shared fixture
    3. Clean up fixtures sau khi chạy test
      1. Dùng yield
    4. Override fixtures
      1. Dùng finalizer
  5. Sử dụng marks
    1. Dùng mark để cung cấp input thay cho fixture
    2. Chỉ chạy test khi mark tồn tại
  6. Các cấu hình cho pytest
  7. Plugin
  8. Tham khảo

Revision history

Revision Date Remark
0.1 Feb-07-2023 Init document
0.2 Feb-08-2023 Add marks
0.3 Feb-08-2023 Add configuration example
0.4 Feb-09-2023 Add plugin

Giới thiệu

Hoạt động

User và pytest framework giao tiếp với nhau thông qua các test file, bên dưới là một ví dụ:

Ví dụ

# content of demo/test_sample.py
def sumOf(a,b):
    return a + b


def test_answer1():
    assert sumOf(3,6) == 8
def test_answer2():
    assert sumOf(3,1) == 4
cd demo
pytest

============================= test session starts ==============================
platform linux -- Python 3.10.6, pytest-7.2.1, pluggy-1.0.0
rootdir: /home/thanhle/build/test
collected 2 items

test_sample.py F.                                                        [100%]

=================================== FAILURES ===================================
_________________________________ test_answer1 _________________________________

    def test_answer1():
>       assert sumOf(3,6) == 8
E       assert 9 == 8
E        +  where 9 = sumOf(3, 6)

test_sample.py:6: AssertionError
=========================== short test summary info ============================
FAILED test_sample.py::test_answer1 - assert 9 == 8
========================= 1 failed, 1 passed in 0.02s ==========================
thanhle@thanhle ~/build/test

Giải thích

Sử dụng fixtures

Ví dụ

# content of demo/test_sample.py
def sumOf(a,b):
    return a + b

def test_answer(a_val, b_val):
    assert sumOf(a_val, b_val) == a_val + b_val

@pytest.fixture
def a_val():
    return 10

@pytest.fixture
def b_val():
    return 5
import pytest

@pytest.fixture
def a_val():
    return 10
@pytest.fixture
def b_val():
    return 5

def sumOf(a,b):
    return a + b


def test_answer(a_val, b_val):
    assert sumOf(a_val, b_val) == a_val + b_val

Các biến thể của fixtures

nested fixtures

# Ví dụ về nested fixtures
import pytest
# Arrange
@pytest.fixture
def first_entry():
    return "a"
# Arrange
@pytest.fixture
def order(first_entry):
    return [first_entry]
def test_string(order):
    # Act
    order.append("b")
    # Assert
    assert order == ["a", "b"]

Sử dụng lại fixtures

# Ví dụ về sử dụng lại fixtures
import pytest

@pytest.fixture
def a_val():
    return 10


def test_answer1(a_val):
    tmp = a_val
    print("a_val", a_val)
    a_val = a_val + 1
    assert a_val == tmp + 1
def test_answer2(a_val):
    assert a_val == 11 <<<<< Fail  đây, a_val = 10
# Ví dụ Autouse fixtures
@pytest.fixture(autouse=True)
def a_val():
    return 10

Fixture trả về một hàm

@pytest.fixture
def make_customer_record():
    def _make_customer_record(name):
        return {"name": name, "orders": []}
    return _make_customer_record
def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")

Fixture có tham số [HOT]

# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
    smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
    yield smtp_connection
    print(f"finalizing {smtp_connection}")
    smtp_connection.close()

Chia sẽ fixtures giữa các test khác nhau [HOT]

# content of conftest.py
import smtplib
import pytest

@pytest.fixture(scope="module") # Phạm vi của fixture
def smtp_connection():
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)

file test

# content of test_module.py
def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert b"smtp.gmail.com" in msg

def test_noop(smtp_connection):
    response, msg = smtp_connection.noop()
    assert response == 250
Phạm vi của shared fixture

Fixture được tạo ra và bị hủy dựa trên phạm vi hoạt động của nó

Clean up fixtures sau khi chạy test

Pytest hỗ trợ việc khởi tạo và clean-up fixtures bằng 2 cách: dùng yield và dùng finalizer

Dùng yield

Xét ví dụ bên dưới

@pytest.fixture
def sending_user(mail_admin):
    user = mail_admin.create_user()
    yield user
    mail_admin.delete_user(user)

Info! Thứ tự cleanup fixture sẽ ngược lại với thứ tự khởi tạo fixture

Override fixtures

tests/
    conftest.py
    # content of tests/conftest.py
        import pytest
        @pytest.fixture
            def username():
                return 'username'

    test_something.py
        # content of tests/test_something.py
        def test_username(username):
            assert username == 'username'
    subfolder/
        conftest.py
            # content of tests/subfolder/conftest.py
            import pytest
            @pytest.fixture
            def username(username):
                return 'overridden-' + username
        test_something_else.py
            # content of tests/subfolder/test_something_else.py
            def test_username(username):
                assert username == 'overridden-username'

Dùng finalizer

@pytest.fixture
def receiving_user(mail_admin, request):
    user = mail_admin.create_user()
    def delete_user():
        mail_admin.delete_user(user)
    request.addfinalizer(delete_user)
    return user

Warning! finalizer được gọi lúc khỏi tạo fixture và nếu có lỗi gì trong quá trình thêm này thì test vẫn tiếp tục chạy.

Sử dụng marks

Dùng mark để cung cấp input thay cho fixture

import pytest
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected
    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected

import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
# Các cặp input được tạo ra: x=0/y=2, x=1/y=2, x=0/y=3, và x=1/y=3
def test_foo(x, y):
    pass

Chỉ chạy test khi mark tồn tại

import pytest

@pytest.fixture
def a_val():
    return 10

def test_answer1(a_val):
    tmp = a_val
    print("a_val", a_val)
    a_val = a_val + 1
    assert a_val == tmp + 12

@pytest.mark.thanhle
def test_answer2(a_val):
    assert a_val == 10

[pytest]
markers =
  thanhle: test mark

Các cấu hình cho pytest

pytest -o console_output_style=classic -o cache_dir=/tmp/mycache

# content of pytest.ini
[pytest]

# Test file to scan
python_files =
    test_*.py
    check_*.py
    example_*.py

# App options
addopts =
  -s
  --embedded-services esp,idf
  --tb short
  --skip-check-coredump y
  --maxfail=2 -rf # exit after 2 failures, report fail info
console_output_style =
    classic: classic pytest output.
    progress: like classic pytest output, but with a progress indicator
    count: like progress, but shows progress as the number of tests completed instead of a percent
# ignore DeprecationWarning
filterwarnings =
  ignore::DeprecationWarning:matplotlib.*:
  ignore::DeprecationWarning:google.protobuf.*:
  ignore::_pytest.warning_types.PytestExperimentalApiWarning

# log related

log_cli = True # Enable log display during test run (also known as “live logging”). The default is False.
log_cli_level = INFO
log_cli_format = %(asctime)s %(levelname)s %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
log_file = logs/pytest-logs.txt

# junit related
# xunit1 produces old style output, compatible with the xunit 1.0 format
# xunit2 produces xunit 2.0 style output, which should be more compatible with latest Jenkins versions

junit_family = xunit1

## log all to `system-out` when case fail
# log: write only logging captured output.
# system-out: write captured stdout contents.
# system-err: write captured stderr contents.
# out-err: write both captured stdout and stderr contents.
# all: write captured logging, stdout and stderr contents.
# no (the default): no captured output is written.

# Write captured log messages to JUnit report: one of no|log|system-out|system-err|out-err|all
junit_logging = log
# Capture log information for passing tests to JUnit report
junit_log_passing_tests = True


Plugin

Tên Chức năng
pytest-django write tests for django apps, using pytest integration.
pytest-cov coverage reporting, compatible with distributed testing
pytest-embedded A pytest plugin that has multiple services available for various functionalities. Designed for embedded testing.
Info! Xem thêm bài viết về pytest-embedded

Tham khảo