When we run a test function, we want to make sure that the test function cleans up after itself. That way, they don’t interfere with any other test functions, and they don’t accumulate more and more test data over time.

Those of you who have used UnitTest know that the teardown function does the same thing, so let’s just call the code that cleans up the mess teardown code.

Fixtures in PyTest also provide a useful system in which you can define teardown code.

There are two ways to do this: Yield and Addfinalizer

A: Yield fixtures

1, yield, and return

In the yield fixtures function, the keyword yield is used instead of return, passing some objects in the fixture to the fixture function or test function that calls them. Just like any other regular fixture function. The difference is simply:

  1. yieldReplace thereturn
  2. The teardown code is placed inyieldafter

2. The execution order of yield

When pyTest executes fixture functions, it calls them sequentially based on a linear relationship between fixture functions. However, when the test function ends, PyTest always executes the post-Yield code in the fixture in the same order. Let’s look at the examples. There is no official example quoted here. Write a more intuitive one:

import pytest @pytest.fixture def fixture_one(): Fixture def fixture_two(fixture_one): print("\n perform fixture_one") return 1 @pytest.fixture def fixture_two(fixture_one): Fixture def fixture_adding(fixture_one,) @pytest.fixture def fixture_adding(fixture_one,) fixture_two): Print ("\n execute fixture_ADDING ") result = fixture_one + fixture_two yield result print("\n execute fixture_adding teardown code ") def Test_demo (fixture_two, fixture_ADDING): print("\n executes the test function test_demo") assert fixture_ADDING == 3Copy the code

Fixtures call multiple fixtures in your code, and test functions call multiple fixtures. From the previous chapters, you can now tease out the sequence:

  1. The test_demo test calls fixture_two, then fixture_ADDING, to the fixture function.
  2. Within the fixture function fixture_two, the other fixture function fixture_one is called.
  3. In the fixture function fixture_ADDING, I called fixture_one and fixture_two.

So, the order of fixture functions is fixture_one, fixture_two, and fixture_ADDING. So you know the order in which the teardown code is executed after the test: fixture_ADDING, fixture_two.

Run the code to verify that the result matches our combing:

= = = = = = = = = = = = = = = = = = = = = = = = = = = = = test session starts = = = = = = = = = = = = = = = = = = = = = = = = = = = = = platform win32 - Python 3.6.8, Pytest-5.4.3, py-1.9.0, pluggy-0.13.1 RootDir: D:\ practice \demo_fixture plugins: Allure - pytest - 2.8.32, celery - 4.3.0, Faker - 4.14.2, base - - 1.4.2 url, HTML - 2.1.1, Metadata-1.10.0 COLLECTED 1 item test_module.py Run fixture_one Run fixture_two Run fixture_ADDING. Execute the test function test_demo fixture_two teardown code executed fixture_adding teardown code [100%] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 1 passed in 0.09 s = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =Copy the code

The results are consistent with what we have just combed out.

However, it’s worth noting that even if teardown’s code executes in the correct order, there’s no guarantee that the code will execute correctly. For example, some code in teardown executes incorrectly, preventing other cleanup actions from being executed. Here comes another point: what a robust fixture structure should look like. This official document is explained separately, as it is here.

Second, the addfinalizer

1. Request. addfinalizer turns functions into finalizers

To handle teardown in PyTest, you can add a finalizer directly, in addition to using the fixture function with yield. Let’s get straight to the sample code:

Import pytest@pytest. fixture() def demo_fixture(request): print("\n This fixture is executed once before each case ") def demo_finalizer(): Def test_01(demo_fixture): request. Addfinalizer (demo_finalizer) def test_01(demo_fixture): Print (" \ n = = = performed case: test_01 = = = ") def test_02 (demo_fixture) : print (" \ n = = = performed case: test_02 = = = ")Copy the code

Take a look at the results:

= = = = = = = = = = = = = = = = = = = = = = = = = = = = = test session starts = = = = = = = = = = = = = = = = = = = = = = = = = = = = = platform win32 - Python 3.6.8, Pytest-5.4.3, py-1.9.0, pluggy-0.13.1 RootDir: D:\ practice \demo_fixture plugins: Allure - pytest - 2.8.32, celery - 4.3.0, Faker - 4.14.2, base - - 1.4.2 url, HTML - 2.1.1, Metadata-1.10.0 COLLECTED 2 items test_module.py This fixture executes once before each case. === Executes the case: Test_01 === Teardown executed after each case This fixture executes once before each case. === = Executes the case: Test_02 = = = in each case after the completion of the execution of teardown [100%] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 2 passed in 0.10 s = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Process finished with exit code 0Copy the code

The result of the run shows that the effect is consistent with yield. This is a bit of a formality, but there are additional explanations in the request documentation, which will be shared later.

2. Request. Addfinalizer Registers multiple finalizer functions

The code above is a terminating function. What if you want to register more than one?

Import pytest@pytest. fixture() def demo_fixture(request): print("\n This fixture is executed once before each case ") def demo_finalizer(): Def demo_finalizer2(): print("\n teardown on each case ") def demo_finalizer2(): Print (" teardown2 executed after each case ") # register demo_finalizer as the final function request. Addfinalizer (demo_finalizer) Request. Addfinalizer (demo_finalizer2) def test_01(demo_fixture): print("\n=== case: Test_01 = = = ") def test_02 (demo_fixture) : print (" \ n = = = performed case: test_02 = = = ") if __name__ = = "__main__" : pytest.main(['-s', 'test_module.py'])Copy the code

Running results:

= = = = = = = = = = = = = = = = = = = = = = = = = = = = = test session starts = = = = = = = = = = = = = = = = = = = = = = = = = = = = = platform win32 - Python 3.6.8, Pytest-5.4.3, py-1.9.0, pluggy-0.13.1 RootDir: D:\ practice \demo_fixture plugins: Allure - pytest - 2.8.32, celery - 4.3.0, Faker - 4.14.2, base - - 1.4.2 url, HTML - 2.1.1, Metadata-1.10.0 COLLECTED 2 items test_module.py This fixture executes once before each case. === Executes the case: Test_01 === Teardown2 Teardown executed after each case This fixture executes once before each case. === = Executes the case: Test_02 = = = in each case after the completion of the execution of teardown2 in each case after the completion of the execution of teardown [100%] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 2 passed in 0.09 s ============================== Process finished with exit code 0Copy the code

Note here that in the case of multiple finalizers, the order of execution is reversed from the time of registration.

3. Difference between Yield and Addfinalizer

As far as we can see from official documentation

We have to be careful though, because pytest will run that finalizer once it’s been added, 
even if that fixture raises an exception after adding the finalizer. 
Copy the code

Once a finalizer is added, PyTest is executed.

However, when I tried throwing errors in the Setup code, the finalizer code didn’t execute. I tried to search the Internet but didn’t get effective help temporarily, so I had to propose issue to Pytest on GitHub, which is a pit to be solved later.

The pit has been filled in for the night. Click here to skip to it