[Advanced Python] pytest: building simple and scalable tests easy

Ref: https://testdriven.io/courses/tdd-flask/pytest-setup/

If this is your first time with pytest be sure to review the Installation and Getting Started guide.

 

一、使用原因

fixtureunittest是冲突的。舍弃unittest只用pytest

会遇到在很多用例当中,它的前置条件是长得一样的。用例写的越来越多的时候,肯定会遇到前置条件都差不多,大家差距不是很大。这样的话,如果每个py文件都写个setUptearDown,就重复率挺高的。要是能拎取出来做个公共的就好了。

fixture在项目当中作为公共用法来用。可以专门定义额外的setUptearDown,在单独的地方,不放在测试用例当中。测试用例将它主动引进来,作为它的前置和后置就好了。

故:

Ref: pytest的fixture功能

More details: Pytest系列(五)fixture

学习pytest就要好好的了解下它的精髓所在—fixture

就像了解unittest中的setupteardown一样。

 

 

二、fixture执行和销毁的逻辑

可见,pytest将每一个fixture的执行分成SETUP和TEARDOWN两部分。

example.py 
        SETUP    F fixtureFunc
        example.py::test_fixture (fixtures used: fixtureFunc).
        TEARDOWN F fixtureFunc

 

 

三、fixture的作用范围

  • 分组测试 和 离散参数测试

Ref: 【Python】pytest - 自动化测试工具, 帮你写出最好的程序【小马视频,讲解concise】

.ini文件。

jeffrey@unsw-ThinkPad-T490:06_example$ ls
__pycache__  pytest.ini  test_func.py

jeffrey@unsw-ThinkPad-T490:06_example$ cat pytest.ini 
[pytest]
markers = 
    g1: group1.
    g2: group2.

测试用例。

 import pytest
 
 def func(x):
     if x == 0:
         raise ValueError("value error")
     else:
         pass
 
 @pytest.mark.g1
 def test_mytest1():
     with pytest.raises(ValueError):
         func(0)
 
 @pytest.mark.g1
 def test_mytest2():
     assert func(1) == None
 
 
 @pytest.mark.g2
 def test_func1():
     pass
 
 @pytest.mark.g2
 def test_func2():
     pass
 
 #########################################
  
 def add(x, y): 
     return x+y 
 
 import pytest
 
 # how to define test inputs?
 @pytest.mark.parametrize(
     "x, y, expected",
     [   
         (1, 1, 2), 
         (2, 2, 4), 
         (10, 10, 20),
     ]   
 )
 
 def test_add(x, y, expected):
     assert add(x, y) == expected
jeffrey@unsw-ThinkPad-T490:06_example$ pytest -vv
========================================= test session starts ==========================================
platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/anaconda3/bin/python
cachedir: .pytest_cache
rootdir: /home/jeffrey/Desktop/test-driven/pytest/06_example, configfile: pytest.ini
collected 7 items                                                                                      

test_func.py::test_mytest1 PASSED                                                                [ 14%]
test_func.py::test_mytest2 PASSED                                                                [ 28%]
test_func.py::test_func1 PASSED                                                                  [ 42%]
test_func.py::test_func2 PASSED                                                                  [ 57%]
test_func.py::test_add[1-1-2] PASSED                                                             [ 71%]
test_func.py::test_add[2-2-4] PASSED                                                             [ 85%]
test_func.py::test_add[10-10-20] PASSED                                                          [100%]

========================================== 7 passed in 0.01s ===========================================
jeffrey@unsw-ThinkPad-T490:06_example$ 
jeffrey@unsw-ThinkPad-T490:06_example$ 
jeffrey@unsw-ThinkPad-T490:06_example$ 
jeffrey@unsw-ThinkPad-T490:06_example$ 
jeffrey@unsw-ThinkPad-T490:06_example$ pytest -vv -m g1
========================================= test session starts ==========================================
platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/anaconda3/bin/python
cachedir: .pytest_cache
rootdir: /home/jeffrey/Desktop/test-driven/pytest/06_example, configfile: pytest.ini
collected 7 items / 5 deselected / 2 selected                                                          

test_func.py::test_mytest1 PASSED                                                                [ 50%]
test_func.py::test_mytest2 PASSED                                                                [100%]

=================================== 2 passed, 5 deselected in 0.01s ====================================
jeffrey@unsw-ThinkPad-T490:06_example$ 
jeffrey@unsw-ThinkPad-T490:06_example$ 
jeffrey@unsw-ThinkPad-T490:06_example$ 
jeffrey@unsw-ThinkPad-T490:06_example$ pytest -vv -m g2
========================================= test session starts ==========================================
platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/anaconda3/bin/python
cachedir: .pytest_cache
rootdir: /home/jeffrey/Desktop/test-driven/pytest/06_example, configfile: pytest.ini
collected 7 items / 5 deselected / 2 selected                                                          

test_func.py::test_func1 PASSED                                                                  [ 50%]
test_func.py::test_func2 PASSED                                                                  [100%]

=================================== 2 passed, 5 deselected in 0.01s ====================================
测试结果

 

  • scope 参数

Ref: pytest的fixture功能

@pytest.fixture()的scope参数有4个值:function、class、module、session,默认为:function。

learning-pytest - 固件学习: https://learning-pytest.readthedocs.io/zh/latest/doc/fixture/intro.html

Case:最常见的可能就是数据库的初始连接和最后关闭操作。

 

更多时候,我们希望一个固件可以在更大程度上复用,这就需要对固件进行集中管理。

Pytest 使用文件 conftest.py 集中管理固件。

在复杂的项目中,可以在不同的目录层级定义 conftest.py,其作用域 为其所在的目录和子目录

Ref: pytest中fixture使用,控制使用域【例子值得借鉴】

 

代码:

 import pytest
 
 # @pytest.fixture(scope="function",autouse=True)
 # @pytest.fixture(scope="class",autouse=True)
 # @pytest.fixture(scope="module",autouse=True)
 # @pytest.fixture(scope="session", autouse=True)
 
 @pytest.fixture(scope="session", autouse=True)
 def before():
     print("before 执行啦 ... ")
 
 class Test_b():
 
     def test_a(self):
         print("test a")
 
     def test_b(self):
         print("test b")
 
     # class --> "setup" --> func
     def setup(self):
         print("setup...")
 
 
 class Test_c():
     def test_c(self):
         print("test c")

 

运行日志:

jeffrey@unsw-ThinkPad-T490:05_fixtures$ pytest func_test.py -s
======================================== test session starts =========================================
platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /home/jeffrey/Desktop/test-driven/python-pytest/05_fixtures
plugins: Faker-5.0.1
collected 3 items                                                                                    

func_test.py before执行啦。。。
setup...
test a
.before执行啦。。。
setup...
test b
.before执行啦。。。
test c
.

# 每个函数前都执行了一次。
========================================= 3 passed in 0.03s ========================================== jeffrey@unsw-ThinkPad-T490:05_fixtures$ pytest func_test.py -s ======================================== test session starts ========================================= platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 rootdir: /home/jeffrey/Desktop/test-driven/python-pytest/05_fixtures plugins: Faker-5.0.1 collected 3 items func_test.py before执行啦。。。 setup... test a .setup... test b .before执行啦。。。 test c .
# 每个类前都执行了一次。
========================================= 3 passed in 0.03s ========================================== jeffrey@unsw-ThinkPad-T490:05_fixtures$ pytest func_test.py -s ======================================== test session starts ========================================= platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 rootdir: /home/jeffrey/Desktop/test-driven/python-pytest/05_fixtures plugins: Faker-5.0.1 collected 3 items func_test.py before执行啦。。。 setup... test a .setup... test b .test c . ========================================= 3 passed in 0.03s ========================================== jeffrey@unsw-ThinkPad-T490:05_fixtures$ pytest func_test.py -s ======================================== test session starts ========================================= platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 rootdir: /home/jeffrey/Desktop/test-driven/python-pytest/05_fixtures plugins: Faker-5.0.1 collected 3 items func_test.py before执行啦。。。 setup... test a .setup... test b .test c . ========================================= 3 passed in 0.03s ==========================================

 

  • conftest.py 文件

(1) 如果你希望fixture函数的作用域仅限于某个测试文件,那么将它写在该测试文件中;

(2) 如果希望fixture被多个测试文件共享,可以在公共目录下创建一个conftest.py文件,将fixture放在其中。

import pytest

@pytest.fixture(scope='module')
def test_app():
    print("[fixture] In test_app(), before yield")
    yield 1  # testing happens here
    print("[fixture] In test_app(), after yield")


@pytest.fixture(scope='module')
def test_database():
    print("[fixture] In test_database(), before yield")
    yield 2  # testing happens here
    print("[fixture] In test_database(), after yield")

test_users.py 测试主题文件。

def test_add_user(test_app, test_database):
    i = 1
    j = 2
    rst = i + j
    print("rst = {}".format(rst))
    assert rst == 3

 

执行日志:

jeffrey@unsw-ThinkPad-T490:test_2$ ls
conftest.py  __pycache__  test_users.py

jeffrey@unsw-ThinkPad-T490:test_2$ pytest test_users.py -s ======================================== test session starts ========================================= platform linux -- Python 3.7.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 rootdir: /home/jeffrey/Desktop/test-driven/python-pytest/05_fixtures/test_2 plugins: Faker-5.0.1 collected 1 item test_users.py [fixture] In test_app(), before yield [fixture] In test_database(), before yield rst = 3 .[fixture] In test_database(), after yield [fixture] In test_app(), after yield ========================================= 1 passed in 0.03s ==========================================

 

 

四、Given-When-Then 原则

When writing tests, try to follow the Given-When-Then framework to help make the process of writing tests easier and faster. It also helps communicate the purpose of your tests better so it should be easier to read by your future self and others.

StateExplanationCode
Given the state of the application before the test runs setup code, fixtures, database state
When the behavior/logic being tested code under test
Then the expected changes based on the behavior asserts

Example:

def test_ping(client):
    # Given
    # client

    # When
    url = reverse("ping")
    response = client.get(url)
    content = json.loads(response.content)

    # Then
    assert response.status_code == 200
    assert content["ping"] == "pong!"

 

End.

posted @ 2020-10-22 18:51  郝壹贰叁  阅读(108)  评论(0编辑  收藏  举报