Pytest -- fixture 简单应用

Fixture

以下命令可以列出所有可用的fixture,包括内置的、插件中的、以及当前项目定义的。

pytest --fixtures
  1. fixture作为函数参数
    测试用例可以接受一个fixture函数作为参数(函数命名),fixture函数的注册通过@pytest. fixture来标记,下面看一个简单的例子:
    # test_sample.py

    import pytest
    
    
    @pytest.fixture
    def data():
        return [1, 2, 3]
    
    def test_equal(data):
        print data[0]
        assert data[0] == 1
    

    test_equal需要data的值,pytest将会查找并调用@pytest. fixture标记的data函数,运行将会看到如下结果:
    Pytest -- fixture 简单应用_第1张图片

  2. 共享fixture函数
    上面的一个例子fixture函数只运用于单个py文件,如果想要共享数据函数怎么办呢?只需要将这些fixture函数放入到 conftest.py文件中即可(名称不可变);在当前路径创建 conftest.py文件,输入以下代码:
    # conftest.py

    	import pytest
    	
    	
    	@pytest.fixture
    	def data():
    	    return [1, 2, 3]
    

    test_sample.py更新代码:

    # test_sample.py

    	import pytest
    	
    	def test_equal(data):
    	    print data[0]
    	    assert data[0] == 1
    

    运行代码将会得到同样的结果;如果此时在test_sample.py文件中也包含fixture标记过的data函数(返回数据改变),那么将会重载该数据(即会调用当前文件中的data)

  3. pytest.fixture 特性实现pytest中的setup/teardown
    经典的xunit风格的setUp、tearDown参考:上篇博客和官网介绍。
    pytest.fixture 特性实现:
    这里的scope支持 function, class, module, package or session四种,默认情况下的scope是function。

    • function作用于函数(类中或模块中)
    • class开始于类的始末
    • module开始于模块的始末
    • package/session开始于测试用例的始末(整个包)

    默认使用return的时候是没有tearDown的,但是可以使用yield 、addfinalizer函数来实现tearDown功能。
    具体请参考:

    tests
    │-- conftest.py
    │-- test_sample.py  
    │-- test_sample1.py   
    

    conftest.py代码:

    import pytest
    
    
    pytest.fixture(scope="module")
    def try_module():
        print "#######################module begin####################"
        yield [6, 7, 8]
        print "#######################module end####################"
    
    
    @pytest.fixture()  # 不加(scope="function")默认scope就是function
    def try_function():
        print "#######################function begin####################"
        yield "just a test about function scope"
        print "#######################function end####################"
    
    
    @pytest.fixture(scope="class")
    def try_class(request):
        print "#######################class begin####################"
        def end():
            print "#######################class end####################"
        request.addfinalizer(end)
        return "just a test about class scope"
    
    
    @pytest.fixture(scope="package")  # session == package
    def try_session(request):
        print "#######################session begin####################"
        def end():
            print "#######################session end####################"
        request.addfinalizer(end)
        return "just a test about session scope"
    

    test_sample.py代码:

    class TestAdd(object):
    
        def test1(self, try_session):
            print "test1 something"
            assert 4 == 4
    
        def test2(self, try_module):
            print "test2 something"
            assert 5 == 5
    
        def test3(self, try_class):
            print "test3 something"
            assert 5 == 5
    
        def test4(self, try_function):
            print "test4 something"
            assert 5 == 5
    
        def test5(self):
            print "test5 something"
            assert 5 == 5
    

    test_sample1.py代码:

    def test_file2_1(try_module):
        print "test_file2_1 something"
        assert 2 == 2
    
    
    def test_file2_2(try_function):
        print "test_file2_2 something"
        assert "a"*3 == "aaa"
    
    
    def test_file2_3(try_session):
        print "test_file2_3 something"
        assert "a"*3 == "aaa"
    
    
    def test_file2_4():
        print "test_file2_4 something"
        assert "a"*3 == "aaa"
    
    
    def test_file2_5(try_function):
        print "test_file2_5 something"
        assert "a"*3 == "aaa"
    

    命令行运行:pytest -v -s -q
    运行结果:

    ============================= test session starts =============================
    platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0
    rootdir: C:\diy\old coding\test\locust demo\pytest
    collected 10 items 
                                                                
    test_sample.py #######################session begin####################
    test1 something
    .#######################module begin####################
    test2 something
    .#######################class begin####################
    test3 something
    .#######################function begin####################
    test4 something
    .#######################function end####################
    test5 something
    .#######################class end####################
    #######################module end####################
    
    test_sample1.py #######################module begin####################
    test_file2_1 something
    .#######################function begin####################
    test_file2_2 something
    .#######################function end####################
    test_file2_3 something
    .test_file2_4 something
    .#######################function begin####################
    test_file2_2 something
    .#######################function end####################
    #######################module end####################
    #######################session end####################
    
    ========================== 10 passed in 0.16 seconds ==========================
    

    简单应用:

    import time
    import pytest
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from utils.log import logger
    from utils.config import get_url
    
    @pytest.fixture()
    def chrome_driver(scope="function"):
        print("setup() begin")
        driver = webdriver.Chrome()
        driver.get(get_url())
        print("setup() end")
        yield driver 
        #test_函数结束后,会回到这里,关闭浏览器
        print("teardown() begin")
        driver.close()
        print("teardown() end")
    
    
    class TestBaiDu(object):
    
        locator_kw = (By.ID, 'kw')
        locator_su = (By.ID, 'su')
        locator_result = (By.XPATH, '//div[contains(@class, "result")]/h3/a')
    
        def test_search_0(self, chrome_driver):
            chrome_driver.find_element(*self.locator_kw).send_keys(u'selenium 测试')
            chrome_driver.find_element(*self.locator_su).click()
            time.sleep(2)
            links = chrome_driver.find_elements(*self.locator_result)
            for link in links:
                logger.info(link.text)
    
  4. 调用fixture的三种方式

    • 通过传参的方式调用(即上边例子中的用法)
    • 通过usefixtures decorator调用(pytest.mark.usefixtures()标记用例)
    • 通过autouse调用

    usefixtures decorator调用例子:

    test_fixture
    │-- conftest.py
    │-- test_example.py   
    

    conftest.py代码:

    	import pytest
    	@pytest.fixture()  # 不加(scope="function")默认scope就是function
    	def before_function():
    	    print "#######################function begin####################"
    	    yield "just a test about function scope"
    	    print "#######################function end####################"
    
    
    	@pytest.fixture(scope="class")
    	def before_class(request):
    	    print "#######################class begin####################"
    	
    	    def end():
    	        print "#######################class end####################"
    	
    	    request.addfinalizer(end)
    	    return "just a test about class scope"
    

    test_example.py 代码:

    import pytest
    
    
    @pytest.mark.usefixtures("before_function")
    def test_1():
        print('test_1()')
    
    
    @pytest.mark.usefixtures("before_function")
    def test_2():
        print('test_2()')
    
    
    class TestExample(object):
        @pytest.mark.usefixtures("before_function")
        def test_1(self):
            print('test_1()')
    
        @pytest.mark.usefixtures("before_function")
        def test_2(self):
            print('test_2()')
    
    
    @pytest.mark.usefixtures("before_function")
    @pytest.mark.usefixtures("before_class")
    class TestExample1(object):
        # @pytest.mark.usefixtures("before_class")
        def test_1(self):
            print('test_1()')
    
        def test_2(self):
            print('test_2()')
    

    运行pytest -v -s test_example.py输出结果:

    ============================= test session starts =============================
    platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- c:\python27\python.exe
    cachedir: .pytest_cache
    rootdir: C:\diy\old coding\test\locust demo\pytest
    collected 6 items                                                              
    
    test_fixture/test_example.py::test_1 #######################function begin####################
    test_1()
    PASSED#######################function end####################
    
    test_fixture/test_example.py::test_2 #######################function begin####################
    test_2()
    PASSED#######################function end####################
    
    test_fixture/test_example.py::TestExample::test_1 #######################function begin####################
    test_1()
    PASSED#######################function end####################
    
    test_fixture/test_example.py::TestExample::test_2 #######################function begin####################
    test_2()
    PASSED#######################function end####################
    
    test_fixture/test_example.py::TestExample1::test_1 #######################class begin####################
    #######################function begin####################
    test_1()
    PASSED#######################function end####################
    
    test_fixture/test_example.py::TestExample1::test_2 #######################function begin####################
    test_2()
    PASSED#######################function end####################
    #######################class end####################
    
    
    ========================== 6 passed in 0.19 seconds ===========================
    

    autouse调用例子:
    当管理用例比较多的时候,这种方法比较方便高效,但是用该功能时也要小心,一定要注意fixture的作用范围。需要注意的是,当使用这种方式时,就不能使用返回值的功了。autouse默认设置为False。当默认为False,就可以选择用上面两种方式来试用fixture。当设置为True时,所有的test都会自动调用这个fixture。autouse遵循scope="关键字参数"规则:当scope="session"时,无论怎样定义只运行一次;当scope="module"时,每个py文件只运行一次;当scope="class"时,每个class只运行一次(但是一个文件中包括function和class时,会在每个function(不在class中)运行一次);当scope="function"时,每个function运行一次;
    目录结构:

    test_fixture
    │-- conftest.py
    │-- test_autouse.py   
    

    conftest.py代码:

    # coding=utf-8
    import pytest
    
    
    @pytest.fixture(scope="module", autouse=True)
    def before_module():
        print "#######################module begin####################"
        yield [6, 7, 8]
        print "#######################module end####################"
    
    
    @pytest.fixture(autouse=True)  # 不加(scope="function")默认scope就是function
    def before_function():
        print "#######################function begin####################"
        yield "just a test about function scope"
        print "#######################function end####################"
    
    
    @pytest.fixture(scope="class",autouse=True)
    def before_class(request):
        print "#######################class begin####################"
    
        def end():
            print "#######################class end####################"
    
        request.addfinalizer(end)
        return "just a test about class scope"
    
    
    @pytest.fixture(scope="package",autouse=True)  # session == package
    def before_package(request):
        print "#######################session begin####################"
    
        def end():
            print "#######################session end####################"
    
        request.addfinalizer(end)
        return "just a test about session scope"
    

    test_autouse.py代码:

     def test_1():
        print('test_1()')
    
    
    def test_2():
        print('test_2()')
    
    
    class TestExample(object):
        def test_1(self):
            print('test_1()')
    
        def test_2(self):
            print('test_2()')
    

    运行pytest -v -s test_autouse.py输出结果:

     ============================= test session starts =============================
    platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- c:\python27\python.exe
    cachedir: .pytest_cache
    rootdir: C:\diy\old coding\test\locust demo\pytest
    collected 4 items                                                              
    
    test_fixture/test_autouse.py::test_1 #######################session begin####################
    #######################module begin####################
    #######################class begin####################
    #######################function begin####################
    test_1()
    PASSED#######################function end####################
    #######################class end####################
    
    test_fixture/test_autouse.py::test_2 #######################class begin####################
    #######################function begin####################
    test_2()
    PASSED#######################function end####################
    #######################class end####################
    
    test_fixture/test_autouse.py::TestExample::test_1 #######################class begin####################
    #######################function begin####################
    test_1()
    PASSED#######################function end####################
    
    test_fixture/test_autouse.py::TestExample::test_2 #######################function begin####################
    test_2()
    PASSED#######################function end####################
    #######################class end####################
    #######################module end####################
    #######################session end####################
    
    
    ========================== 4 passed in 0.11 seconds ===========================
    

    fixture中使用fixture(test_something.py):

    	import pytest
    	@pytest.fixture(scope="module")
    def foo(request):
        print('\nfoo setup - module fixture')
        def fin():
            print('foo teardown - module fixture')
        request.addfinalizer(fin)
     
    @pytest.fixture()
    def bar(request, foo):
        print('bar setup - function fixture')
        def fin():
            print('bar teardown - function fixture')
        request.addfinalizer(fin)
     
    @pytest.fixture()
    def baz(request, bar):
        print('baz setup - function fixture')
        def fin():
            print('baz teardown - function fixture')
        request.addfinalizer(fin)
     
    def test_one(baz):
        print('in test_one()')
     
    def test_two(bar): # only use bar
        print('in test_two()')
    

    运行py.test -s test_something.py,输出:

    ============================= test session starts =============================
    platform win32 -- Python 2.7.2 -- pytest-2.4.2
    collected 2 items
     
    test_modular.py
    foo setup - module fixture
    bar setup - function fixture
    baz setup - function fixture
    in test_one()
    .baz teardown - function fixture
    bar teardown - function fixture
    bar setup - function fixture
    in test_two()
    .bar teardown - function fixture
    foo teardown - module fixture
     
    ========================== 2 passed in 0.02 seconds ===========================
    
  5. 参数化
    利用fixture的params参数进行参数化:

    @pytest.fixture(params=[0, 1, 3])
    def c_set(request):
        temp = request.param
        yield temp
    
    
    def test_c(c_set):
        assert c_set == 3
    

    上面例子运行了三个测试用例,运行结果:

    	============================= test session starts =============================
    platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- c:\python27\python.exe
    cachedir: .pytest_cache
    rootdir: C:\diy\old coding\test\locust demo\pytest
    collected 3 items                                                              
    
    test_parametrizing.py::test_c[0] FAILED
    test_parametrizing.py::test_c[1] FAILED
    test_parametrizing.py::test_c[3] PASSED
    
    ================================== FAILURES ===================================
    __________________________________ test_c[0] __________________________________
    
    c_set = 0
    
        def test_c(c_set):
    >       assert c_set == 3
    E       assert 0 == 3
    E         -0
    E         +3
    
    test_parametrizing.py:50: AssertionError
    __________________________________ test_c[1] __________________________________
    
    c_set = 1
    
        def test_c(c_set):
    >       assert c_set == 3
    E       assert 1 == 3
    E         -1
    E         +3
    
    test_parametrizing.py:50: AssertionError
    ===================== 2 failed, 1 passed in 0.18 seconds ======================
    
    

    也可以利用ids参数对每个测试定制一个名称标志,将上边函数加一个ids参数,可以用–collect-only参数来查看(pytest --collect-only test_parametrizing.py),添加的ids参数可以用-k来过滤运行指定参数的方法(pytest -k "apple" test_parametrizing.py只会运行参数params=[0, 1, 3]中值为1的,运行一次),代码如下:

    @pytest.fixture(params=[0, 1, 3], ids=["apple","banana","orange"])
    def c_set(request):
        temp = request.param
        yield temp
    
    
    def test_c(c_set):
        assert c_set == 3
    

    也可以为ids传递函数作为参数:

    def idfn(fixture_value): 
        if fixture_value == 0: 
            return "eggs" 
        else: 
            return None
    
    
    @pytest.fixture(params=[0, 1], ids=idfn) 
    def d(request): 
        return request.param
    
    
    def test_d(d): 
        pass
    

    使用pytest.param() ,参数值2将被跳过:

    import pytest 
    @pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)]) 
    def e_set(request): 
        return request.param
    
    
    def test_e(e_set): 
        pass
    

    利用@pytest.mark.parametrize直接对测试函数进行参数化:

    import pytest
    def add(a, b):
        return a + b
    
    @pytest.mark.parametrize("test_input, expected", [
        ([1, 1], 2),
        ([2, 2], 4),
        ([0, 1], 1),
    ])
    def test_add(test_input, expected):
        assert expected == add(test_input[0], test_input[1])
    
  6. 不同级别的重写fixture

    • 文件夹级别(conftest.py)重写fixture
    • module级别的重写fixture
    • 直接利用参数重写fixture

    文件夹级别(conftest.py)重写fixture

     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'
    

    通过上面的例子可以看到,对于特定的测试文件夹级别,可以覆盖具有相同名称的fixture。注意,重写的fixture可以轻松地访问被重写的base fixture或super fixture
    module级别的重写fixture

     tests/
    	__init__.py
    	conftest.py # content of tests/conftest.py 
    	@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'
    

    在上面的示例中,在模块级别,相同名称的fixture可以被重写
    直接利用参数重写fixture

    	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'
    

    在上面的例子中,fixture值被test参数值覆盖。请注意,即使测试没有直接使用fixture(在函数原型中没有提到),fixture的值也可以通过这种方式被覆盖

参考:
  • API:https://docs.pytest.org/en/latest/reference.html#initialization-hooks
  • skip:https://docs.pytest.org/en/latest/skipping.html
  • 样例:https://docs.pytest.org/en/latest/example/
  • 自定义测试集合:https://docs.pytest.org/en/latest/example/pythoncollection.html#customizing-test-collection

你可能感兴趣的:(Python,自动化测试,随笔,模块学习,pytest,feature,pytest传参)