Detailed explanation of pytest-fixtured automated testing

The function of fixture

1. Same as unittest’s setup and teardown, used as initialization settings before and after testing.

Use of fixtures

1. Used as a precondition

2.Fixture’s scope of action

1. Used as a precondition

@pytest.fixture()
def a():
    return 3
 
def test_b(a):
    assert a==3

2. Fixture scope
First instantiate a higher-scope fixture. The default is scope="function", and each test function will be executed once.

session: only called once when multiple .py files are executed
module: called once for each .py
class: called for each class Once
function: Each function is called once
3. Fixture is executed as setup/teardown
The one before yield is executed before the test , the ones after yield will be executed after testing

@pytest.fixture
def a():
    print("setup")
    yield
    print("teardown")
 
def test_b(a):
    print("测试")

4.fixture with

# @pytest.fixture()
# def write_file():
#     f = open("myfile.txt","w")
#     f.write("hello")
#     yield
#     f.close()
@pytest.fixture()
def write_file():
    with open("myfile.txt","w") as f:
        f.write("hello1")
 
def test_write_file(write_file):
    print("ceshi")

5. Fixture request can request the context of the test

@pytest.fixture(params=[1,3])
def a(request):
    return request.param
 
def test_b(a):
    assert a in [1,3]

6.fixture factory mode

In a single test, if you want to call the fixture multiple times, you can use the factory pattern. The result of the fixture does not return data, but the function that generated the data.

@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")

7. Fixture with parameters

The test will be executed as many times as the params list has several values. Example params=[1,2], the test case will be executed twice

@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
def data_set(request):
    return request.param
 
 
def test_data(data_set):
    assert data_set in [0,1]

8. Modularization: calling another fixture from one fixture

fixture a can call another fixture c

@pytest.fixture()
def c():
    print("c")
 
@pytest.fixture()
def a(c):
    print("a")
 
def test_b(a):
    pass

9. Group test cases by fixtures
pytest minimizes the number of fixtures during a test run. If there is a parameterized fixture, then all tests using it will first be executed using one instance and then the finalizer will be called before the next fixture instance is created. Among other things, this simplifies testing of applications that create and use global state.

@pytest.fixture(scope="module", params=[1, 2])
def input_x(request):
    print("setup")
    param = request.param
    yield param
    print("teardown")
 
def test_a(input_x):
    print("test_a")
    assert input_x in [1,2]
 
def test_b(input_x):
    print("test_b")
    assert input_x in [1,2]
 
if __name__ == '__main__':
    pytest.main(['-q','fixture_1.py'])
运行结果
fixture_1.py setup
.test_a
.test_b
teardown
setup
.test_a
.test_b
teardown

As shown above, setting scope='module' will execute all use cases that call the fixture, but the fixture is only executed once.

10. Use fixtures in classes, modules, and projects

userfixtures can call fixtures defined in other modules.

@pytest.mark.usefixtures("x")
class TestCase:
 
    def test_1(self):
        print("test_1")
 
    def test_2(self):
        print("test_2")
 
 
if __name__ == '__main__':
    pytest.main(['-q','test_2.py'])

a.usefixture, you can add multiple fixtures

@pytest.mark.usefixtures("a", "b")
def test():
    pass

b. Fixtures can be specified at the test module level using the common functionality of the tagging mechanism.

pytestmark = pytest.mark.usefixtures("a")
 
def test_a():
 
def test_b():

c. You can put all the fixtures required for testing in the project into the ini file

#pytest.ini
 
[pytest]
usefixtures = x
 
#test_case.py
@pytest.mark.usefixtures
def test_a():
    pass

11. Automatically use fixtures

autouse=True

Sometimes you may want to automatically call a fixture without explicitly declaring function parameters or using the usefixtures decorator.

import pytest
 
 
class DB:
    def __init__(self):
        self.intransaction = []
 
    def begin(self, name):
        self.intransaction.append(name)
 
    def rollback(self):
        self.intransaction.pop()
 
 
@pytest.fixture(scope="module")
def db():
    return DB()
 
 
class TestClass:
    @pytest.fixture(autouse=True)
    def transact(self, request, db):
        db.begin(request.function.__name__)
        yield
        db.rollback()
 
    def test_method1(self, db):
        assert db.intransaction == ["test_method1"]
 
    def test_method2(self, db):
        assert db.intransaction == ["test_method2"]

Class-level transact fixtures are marked with autouse=true, which means that all test methods in the class will use this fixture without declaring it in the test function signature or class-level usefixtures decorator.

autouse fixtures follow the scope= keyword argument: if there is an autoouse fixture with scope='session', it can only be run once, no matter where it is defined. scope='class' means it will run once per class and so on.
If an auto-use fixture is defined in a test module, all its test functions will automatically use it.
If an automatically used fixture is defined in the conftest.py file, all tests in all test modules in its directory will call the fixture.
Finally, use caution: if you define an automatic use fixture in a plugin, it will be called in all tests in all projects where the plugin is installed. This is useful if the fixture only still works under certain settings (e.g. in an ini file). Such a global fixture should always quickly determine whether it should do any work and avoid other expensive imports or calculations.
Please note that the transact fixtures above are most likely fixtures that you wish not to enable in your project. The canonical way to do this is to put the transaction definition in the conftest.py file without using autouse:

# content of conftest.py
@pytest.fixture
def transact(request, db):
    db.begin()
    yield
    db.rollback()

Then for example by declaring the need for TestClass to use it:

@pytest.mark.usefixtures("transact")
class TestClass:
    def test_method1(self):
        ...

All test methods in this TestClass will use the transaction fixture, while other test classes or functions in the module will not use it unless they also add a transact reference.

fixture override

1. For test folder level, fixtures with the same name can be overwritten

tests/
    __init__.py
 
    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/
        __init__.py
 
        conftest.py
            # content of tests/subfolder/conftest.py
            import pytest
 
            @pytest.fixture
            def username(username):
                return 'overridden-' + username
 
        test_something.py
            # content of tests/subfolder/test_something.py
            def test_username(username):
                assert username == 'overridden-username'

2. Cover the fixture in the test module

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

3. Overwrite within the file

tests/
    __init__.py
 
    conftest.py
        # content of tests/conftest.py
        import pytest
 
        @pytest.fixture
        def username():
            return 'username'
 
        @pytest.fixture
        def other_username(username):
            return 'other-' + username
 
    test_something.py
        # content of tests/test_something.py
        import pytest
 
        @pytest.mark.parametrize('username', ['directly-overridden-username'])
        def test_username(username):
            assert username == 'directly-overridden-username'
 
        @pytest.mark.parametrize('username', ['directly-overridden-username-other'])
        def test_username_other(other_username):
            assert other_username == 'other-directly-overridden-username-other'

4. For some test modules, the parameterized fixture is covered by the non-parametric version, and the non-parametric fixture is covered by the parameterized version. Apparently the same goes for the test folder level.

tests/
    __init__.py
 
    conftest.py
        # content of tests/conftest.py
        import pytest
 
        @pytest.fixture(params=['one', 'two', 'three'])
        def parametrized_username(request):
            return request.param
 
        @pytest.fixture
        def non_parametrized_username(request):
            return 'username'
 
    test_something.py
        # content of tests/test_something.py
        import pytest
 
        @pytest.fixture
        def parametrized_username():
            return 'overridden-username'
 
        @pytest.fixture(params=['one', 'two', 'three'])
        def non_parametrized_username(request):
            return request.param
 
        def test_username(parametrized_username):
            assert parametrized_username == 'overridden-username'
 
        def test_parametrized_username(non_parametrized_username):
            assert non_parametrized_username in ['one', 'two', 'three']
 
    test_something_else.py
        # content of tests/test_something_else.py
        def test_username(parametrized_username):
            assert parametrized_username in ['one', 'two', 'three']
 
        def test_username(non_parametrized_username):
            assert non_parametrized_username == 'username'

Practical Tips

1.conftest.py shared fixture. If there are too many fixtures defined and need to be called from multiple places, you can put the fixture in the conftest.py file and do not need to import it when using it.

Summarize:

Thank you to everyone who reads my article carefully! ! !

As someone who has experienced it, I also hope that everyone will avoid some detours. If you don’t want to experience the feeling of not being able to find information, no one to answer questions, and giving up after persisting for a few days while studying, here I will share with you some learning about automated testing. Resources, I hope they can help you on your way forward

Software Testing Interview Document

We must study to find a high-paying job. The following interview questions are from the latest interview materials from first-tier Internet companies such as Alibaba, Tencent, Byte, etc., and some Byte bosses have given authoritative answers. After finishing this set I believe everyone can find a satisfactory job based on the interview information.

 

 

Guess you like

Origin blog.csdn.net/2301_79535733/article/details/134957961