python pytest Advanced fixture Detailed Explanation

  • 2021-07-03 00:36:23
  • OfStack

Preface

If you learn pytest, you have to say that fixture and fixture are the essence of pytest, just like setup and teardown1 in unittest. If you don't learn fixture, there is no difference between using pytest and using unittest (personal understanding).

Use of fixture

1. Do the initialization settings before and after the test, such as test data preparation, linking database, opening browser and so on, which can be realized by fixture

2. Test case preconditions can be implemented using fixture

3. Support the classic xunit fixture, like setup and teardown used by unittest

4. fixture can achieve the function that unittest can not achieve, for example, unittest can not transfer parameters and data between test cases, but fixture can solve this problem

fixture Definition

fixture decorates a function with the @ pytest. fixture () decorator, so this function is an fixture. Look at an example


# test_fixture.py
import pytest
@pytest.fixture()
def fixtureFunc():
return 'fixtureFunc'
def test_fixture(fixtureFunc):
print(' I called {}'.format(fixtureFunc))
if __name__=='__main__':
pytest.main(['-v', 'test_fixture.py'])

Execution results


test_fixture.py . I called fixtureFunc
[100%]
========================== 1 passed in 0.02 seconds ===========================
Process finished with exit code 0

fixtureFunc This function is an fixture, fixture function inside can achieve 1 some initialization operations!

fixture uses

There are three ways to call fixture

Mode 1
The name of fixture is directly used as a parameter of the test case. The above example is in this way. Let's look at another example


# test_fixture.py
import pytest
@pytest.fixture()
def fixtureFunc():
return 'fixtureFunc'
def test_fixture(fixtureFunc):
print(' I called {}'.format(fixtureFunc))
class TestFixture(object):
def test_fixture_class(self, fixtureFunc):
print(' Use in a class fixture "{}"'.format(fixtureFunc))
if __name__=='__main__':
pytest.main(['-v', 'test_fixture.py'])

Mode 2

Decorate each function or class with the @ pytest. mark. usefixtures ('fixture') decorator

Instances


# test_fixture.py
import pytest
@pytest.fixture()
def fixtureFunc():
print('\n fixture->fixtureFunc')
@pytest.mark.usefixtures('fixtureFunc')
def test_fixture():
print('in test_fixture')
@pytest.mark.usefixtures('fixtureFunc')
class TestFixture(object):
def test_fixture_class(self):
print('in class with text_fixture_class')
if __name__=='__main__':
pytest.main(['-v', 'test_fixture.py'])

Mode 3

Specify the parameter autouse=True of fixture so that each test case will automatically call fixture (in fact, what is said here is not very accurate, because it also involves the scope of fixture, so we default to the function level here, and we will specifically talk about the scope of fixture later)

Instances


# test_fixture.py
import pytest
@pytest.fixture(autouse=True)
def fixtureFunc():
print('\n fixture->fixtureFunc')
def test_fixture():
print('in test_fixture')
class TestFixture(object):
def test_fixture_class(self):
print('in class with text_fixture_class')
if __name__=='__main__':
pytest.main(['-v', 'test_fixture.py'])

Results


fixture->fixtureFunc
.in test_fixture
fixture->fixtureFunc
.in class with text_fixture_class
[100%]
========================== 2 passed in 0.04 seconds ===========================

It can be seen from the results that fixture is automatically executed before each test case is executed

Summary

Mastering the above method, you can use fixture, so what are the differences between these methods? In fact, it can be seen from the code I wrote that if the test case needs to use the parameters returned in fixture, the returned parameters cannot be used in the latter two ways, because the data returned in fixture is stored in the name of fixture by default, so only the first way can be used to call the return value in fixture. (Theory is always a theory, read the article or try it yourself! )

fixtur Range of Action
All of the above instances are at the function level by default, so fixture is specified before the test function executes whenever the test function calls fixture. When it comes to scope, we have to say the second parameter of fixture, scope parameter.

scope parameters can be session, module, class, function; Default to function

1. The session session level (this level is usually used in conjunction with the conftest. py file, so we'll talk about it later in the conftest. py file)

2. module module level: Execute fixture at module level once before all use cases in the module are executed

3. class class level: fixture at class level is executed once before each class is executed

4. function: As mentioned in the previous example, this default is the default mode, at the function level, and fixture at the function level will be executed once before each test case is executed

Let's look at the scope of fixture under 1 through an example


# test_fixture.py
import pytest
@pytest.fixture(scope='module', autouse=True)
def module_fixture():
print('\n-----------------')
print(' I am module fixture')
print('-----------------')
@pytest.fixture(scope='class')
def class_fixture():
print('\n-----------------')
print(' I am class fixture')
print('-------------------')
@pytest.fixture(scope='function', autouse=True)
def func_fixture():
print('\n-----------------')
print(' I am function fixture')
print('-------------------')
def test_1():
print('\n  I am test1')
@pytest.mark.usefixtures('class_fixture')
class TestFixture1(object):
def test_2(self):
print('\n I am class1 Inside test2')
def test_3(self):
print('\n I am class1 Inside test3')
@pytest.mark.usefixtures('class_fixture')
class TestFixture2(object):
def test_4(self):
print('\n I am class2 Inside test4')
def test_5(self):
print('\n I am class2 Inside test5')
if __name__=='__main__':
pytest.main(['-v', '--setup-show', 'test_fixture.py'])

Running result

We use it in cdm-setup-show to see the specific order of setup and teardoen


test_fixture.py 
  SETUP  M module_fixture
    SETUP  F func_fixture
-----------------
 I am module fixture
-----------------
-----------------
 I am function fixture
-------------------
    test_fixture.py::test_1 (fixtures used: func_fixture, module_fixture).
  I am test1
    TEARDOWN F func_fixture
   SETUP  C class_fixture
    SETUP  F func_fixture
-----------------
 I am class fixture
-------------------
-----------------
 I am function fixture
-------------------
    test_fixture.py::TestFixture1::test_2 (fixtures used: class_fixture, func_fixture, module_fixture).
 I am class1 Inside test2
    TEARDOWN F func_fixture
    SETUP  F func_fixture
-----------------
 I am function fixture
-------------------
    test_fixture.py::TestFixture1::test_3 (fixtures used: class_fixture, func_fixture, module_fixture).
 I am class1 Inside test3

    TEARDOWN F func_fixture
   TEARDOWN C class_fixture
   SETUP  C class_fixture
    SETUP  F func_fixture
-----------------
 I am class fixture
-------------------

-----------------
 I am function fixture
-------------------
 test_fixture.py::TestFixture2::test_4 (fixtures used: class_fixture, func_fixture, module_fixture).
 I am class2 Inside test4

    TEARDOWN F func_fixture
    SETUP  F func_fixture
-----------------
 I am function fixture
-------------------
 test_fixture.py::TestFixture2::test_5 (fixtures used: class_fixture, func_fixture, module_fixture).
 I am class2 Inside test5

    TEARDOWN F func_fixture
   TEARDOWN C class_fixture
  TEARDOWN M module_fixture

========================== 5 passed in 0.05 seconds ===========================

We can clearly see that the whole module only executes fixture at module level once, each class executes fixture at class level once, and each function executes fixture at function level once before

Implementation of teardown by fixture

In fact, all the previous examples only do the preparation work before the test case execution, so how to clean up the environment after the use case execution? This has to say yield keyword, Compared with everyone knowing this keyword more or less, His function is actually similar to return, and it can also return data to the caller. The only difference is that the dropped function will stop executing when it encounters yield, and then execute the function at the calling place. After the called function is executed, it will continue to execute the code behind the key of yield (the specific principle can be seen in my previous article about the generator). Take a look at the following example to see how to implement teardown functionality under 1


import pytest
from selenium import webdriver
import time
@pytest.fixture()
def fixtureFunc():
    ''' Realize the opening and closing of browsers '''
driver = webdriver.Firefox()
yield driver
driver.quit()
def test_search(fixtureFunc):
''' Visit Baidu homepage and search pytest Is the string in the page source code '''
driver = fixtureFunc
driver.get('http://www.baidu.com')
driver.find_element_by_id('kw').send_keys('pytest')
driver.find_element_by_id('su').click()
time.sleep(3)
source = driver.page_source
assert 'pytest' in source
if __name__=='__main__':
pytest.main(['--setup-show', 'test_fixture.py'])

This instance opens the browser first, then executes the test case, and finally closes the browser. You can try! The teardown function after the use case is executed is realized by yield

Summarize

1. How is fixture defined

2. How to use fixture

3. Range of action of 3. fixture

4. fixture realizes teardown function with yield

Finally, I would like to mention one sentence: In practical work, the parameter auto=True may be used as little as possible, which may lead to unexpected results! The most commonly used is to pass parameters!


Related articles: