1、安装

pip install pytest

在unittest中,写case必须得定义类,继承TestCase,而pytest不需要,类和函数都支持,
函数需要以test开头,类需要以Test开头

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. def test_calc():
  5. print("test_calc")
  6. ret = calc(1, 2)
  7. assert ret == 2
  8. class TestCalc:
  9. def test_calc1(self):
  10. print("test_calc1")
  11. ret = calc(3, 3)
  12. assert ret == 6
  13. if __name__ == '__main__':
  14. # pytest.main() # 默认找test开头的py文件
  15. pytest.main(["-vs",__file__])
  16. # pytest.main(["-vs",'pytest应用.py'])

从结果中可以看出:1条成功,1条失败
image.png

用例执行的优先级

实现用例优先级,需要安装pytest-ordering这个模块, 1、标记于被测试函数, @pytest.mark.run(order=x)
2、根据order传入的参数来解决运行顺序
3、order值全为正数或负数时,值越小优先级越高
4、正负数同时存在时,正数优先极高
5、已标记和未标记的函数,已标记的函数优先极高

  1. pip install pytest-ordering
  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. @pytest.mark.run(order=-1)
  5. def test_calc():
  6. print("test_calc")
  7. ret = calc(1, 2)
  8. assert ret == 2
  9. class TestCalc:
  10. @pytest.mark.run(order=1)
  11. def test_calc1(self):
  12. print("test_calc1")
  13. ret = calc(3, 3)
  14. assert ret == 6
  15. @pytest.mark.run(order=2)
  16. def test_calc2(self):
  17. print("test_calc1")
  18. ret = calc(5, 3)
  19. assert ret == 8
  20. if __name__ == '__main__':
  21. # pytest.main() # 默认找test开头的py文件
  22. pytest.main(["-vs",__file__])
  23. # pytest.main(["-vs",'pytest应用.py'])

image.png

2、前置/后置操作

用例的前置操作和后置操作

2.1函数的方式

函数的方式使用setup_module、teardown_module、setup_function、teardown_function,分别是所有case运行之前执行、之后执行,每个case运行之前执行、之后执行。

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. def test_calc():
  5. print("test_calc")
  6. ret = calc(1, 2)
  7. assert ret == 3
  8. def test_calc2():
  9. print("这是test_calc2执行的")
  10. ret = calc(1, 1)
  11. assert ret == 2 # 这是一条失败的case
  12. def setup_module():
  13. print("所有case执行之前执行setup_module")
  14. def teardown_module():
  15. print("所有case执行之后执行teardown_module")
  16. def setup_function():
  17. print("每个case执行之前执行setup_function")
  18. def teardown_function():
  19. print("每个case执行之后执行teardown_function")
  20. if __name__ == '__main__':
  21. pytest.main(['-s', __file__]) # 运行当前文件里面所有用例,
  22. # -s表示详细模式,否则不会print我们在函数里面的内容,__file__代表的是当前这个py文件,也可以直接写当前这个python文件的名字

image.png

2.2类的方式

函数的方式使用setup_classteardown_classsetup_methodteardown_method,分别是所有case运行之前执行、之后执行,每个case运行之前执行、之后执行。和unittest中的差不多,函数名不一样,不需要非得使用类方法

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. class TestCalc:
  5. def test_calc1(self):
  6. print("test_calc3")
  7. ret = calc(3, 3)
  8. assert ret == 6
  9. def test_calc2(self):
  10. print("test_calc3")
  11. ret = calc(2, 3)
  12. assert ret == 5
  13. def setup_class(self):
  14. print("所有case执行之前执行setup_class")
  15. def teardown_class(self):
  16. print("所有case执行之后执行teardown_class")
  17. def setup_method(self):
  18. print("每条case执行之前执行setup_method")
  19. def teardown_method(self):
  20. print("每条case执行之后执行setup_method")
  21. if __name__ == '__main__':
  22. pytest.main(['-s', __file__]) # 运行当前文件里面所有用例,
  23. # -s表示详细模式,否则不会print我们在函数里面的内容,__file__代表的是当前这个py文件,也可以直接写当前这个python文件的名字

image.png

3、参数化

pytest的参数化,不需要安装第三方模块,使用pytest.mark.parametrize,装饰即可,需要传入两个参数,参数名和参数值列表,参数名是一个字符串,多个参数以逗号分开,参数值传一个列表,如果多个参数需要传二维数组。

示例一:

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. @pytest.mark.parametrize("a,b,c", [
  5. [1, 2, 3],
  6. [1, 3, 4],
  7. [1, 5, 6],
  8. [1, 6, 8],
  9. ])
  10. def test_calc_p(a, b, c):
  11. ret = calc(a, b)
  12. assert ret == c
  13. if __name__ == '__main__':
  14. pytest.main(['-sv', __file__])

image.png

示例二:

它比unittest的参数化要高级一点,支持参数的穷举组合,而unittest的参数化是不支持的

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. @pytest.mark.parametrize('username', ["11123@qq.com", "sdgsdg@qq.com"])
  5. @pytest.mark.parametrize('password', ["123456","111111"])
  6. def test_login(username, password):
  7. print("=" * 30)
  8. print(username, password)
  9. if __name__ == '__main__':
  10. pytest.main(['-sv', __file__])

image.png

示例三:

  1. import pytest
  2. def calc(seq): # 被测函数
  3. try:
  4. ret = eval(seq)
  5. except:
  6. return False
  7. return ret
  8. @pytest.mark.parametrize("expression", ["1+2", "1/1", "1*1", "2-1"])
  9. def test_add_singe(expression): # 单个参数的写法
  10. ret = calc(expression)
  11. assert ret == 1
  12. @pytest.mark.parametrize("expression,except_value", [
  13. ("1+2", 3),
  14. ("1*2", 2),
  15. ("1-2", -1),
  16. ("1/2", 0.5),
  17. ])
  18. def test_add(expression, except_value): # 多个参数
  19. ret = calc(expression)
  20. assert except_value == ret
  21. @pytest.mark.parametrize("a,c", [(1, 2), (0.5, 3)])
  22. @pytest.mark.parametrize("b", ["+", "-", "*", "/"])
  23. def test_add_other(a, b, c): # 参数组合,比如要计算1和2的加减乘除,就需要各种组合
  24. expression = "{}{}{}".format(a, b, c)
  25. ret = calc(expression)
  26. if b == "+":
  27. assert ret == a + c
  28. if b == "-":
  29. assert ret == a - c
  30. if b == "*":
  31. assert ret == a * c
  32. if b == "/":
  33. assert ret == a / c
  34. if __name__ == '__main__':
  35. pytest.main(['-vs', __file__])

4、用例跳过

用例跳过基本上和unittest一样,没有特别的,使用pytest.mark.skip和pytest.mark.skipif,可以装饰函数和类

示例一:

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. @pytest.mark.skip("无条件跳过!")
  5. def test_calc1(): # 函数方式写case
  6. print("这是test_calc1执行的")
  7. ret = calc(1, 2)
  8. assert ret == 3
  9. def test_calc2():
  10. print("这是test_calc2执行的")
  11. ret = calc(1, 1)
  12. assert ret == 3 # 这是一条失败的case
  13. if __name__ == '__main__':
  14. pytest.main(["-vs",__file__])

示例2:(有条件跳过)

  1. import pytest
  2. import sys
  3. class TestCase:
  4. @pytest.mark.skip("无条件跳过")
  5. # 无条件跳过,就是不执行,就是玩,需要传一个字符串,说明跳过原因
  6. def test_print(self):
  7. print("test_print")
  8. @pytest.mark.skipif(sys.platform in ["win32", "darwin"], reason="不是在服务器上,不执行该条case")
  9. # if需要传2个参数,第一个参数是一个布尔值,true或者false,第二个参数是跳过的原因,reason这个参数是必传的
  10. # 带条件的跳过,比如说当运行在本地的时候,不执行这条case,本地电脑一般是mac或者Windows,这里判断一下
  11. def test_hello(self):
  12. print("test_hello")
  13. def test_normal(self):
  14. print("我是一个不跳过的case")
  15. @pytest.mark.skip("跳过整个类") # 也可以把装饰器加在类上面,跳过整个测试类
  16. class TestCase2:
  17. def test_mygod(self):
  18. print("我是一个不跳过的case")
  19. def test_my(self):
  20. print("我是一个不跳过的case")
  21. if __name__ == '__main__':
  22. pytest.main([__file__])

5、用例标签

实际运行case的时候,并不是所有情况下都需要运行所有的case,比如刚提测的时候需要运行冒烟case,其他的case不需要,这种情况下,就需要把case打上标签,运行的时候指定-m参数加上标签即可,一个case可以有多个标签,给case加标签使用pytest.mark.xxx ,xxx是标签的名字,标签的名字可以随便自己写,可以装饰测试类和测试函数。 如果标签比较多的情况下,也可以定义到一个py文件里面,统一管理。 在unittest中,是没有这个功能的。

不使用统一标签管理:示例一:

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. @pytest.mark.online #加了一个“online”和“smoke”的标签
  5. @pytest.mark.smoke
  6. def test_calc1(): # 函数方式写case
  7. print("这是test_calc1执行的")
  8. ret = calc(1, 2)
  9. assert ret == 3
  10. @pytest.mark.online #加了一个“online”的标签
  11. def test_calc2():
  12. print("这是test_calc2执行的")
  13. ret = calc(1, 1)
  14. assert ret == 3 # 这是一条失败的case
  15. if __name__ == '__main__':
  16. pytest.main(["-s", __file__, "-m", "smoke"]) #执行带有“smoke”标签的用例

image.png

不使用统一标签管理:示例二:

  1. import pytest
  2. def calc(a, b):
  3. return a + b
  4. @pytest.mark.online #加了一个“online”和“smoke”的标签
  5. @pytest.mark.smoke
  6. def test_calc1(): # 函数方式写case
  7. print("这是test_calc1执行的")
  8. ret = calc(1, 2)
  9. assert ret == 3
  10. @pytest.mark.online #加了一个“online”的标签
  11. def test_calc2():
  12. print("这是test_calc2执行的")
  13. ret = calc(1, 1)
  14. assert ret == 3 # 这是一条失败的case
  15. if __name__ == '__main__':
  16. pytest.main(["-s", __file__, "-m", "smoke and online"])

统一标签管理

上面的方式使用没问题,如果我们想要知道当前有多少个标签的话,就需要从所有的case里面看一遍。 多个人同时编写case的时候,就不知道当前标签是否被别人用过,运行的时候有可能会把别人的case运行了,导致结果不正确。 这种情况下就需要统一管理标签。

首先定义一个case_tags.py,用来存放我们所有的case标签

  1. import pytest
  2. smoke = pytest.mark.smoke # 冒烟测试用例
  3. online = pytest.mark.online # 线上case
  4. pre = pytest.mark.pre # 预发case
  5. local = pytest.mark.local # 本地运行
  1. import pytest
  2. import case_tags
  3. class TestCase:
  4. def test_print(self):
  5. print("test_print")
  6. @case_tags.smoke
  7. def test_hello(self):
  8. print("test_hello")
  9. def test_normal(self):
  10. print("test_normal")
  11. @case_tags.online
  12. @case_tags.pre
  13. class TestCase2:
  14. def test_mygod(self):
  15. print("test_mygod!!!")
  16. def test_my(self):
  17. print("test_my!!!!")
  18. class TestCase3:
  19. pytestmark = [case_tags.smoke, case_tags.local]
  20. def test_car(self):
  21. print("test_car!!!")
  22. def test_fly(self):
  23. print("test_fly!!!!")
  24. if __name__ == '__main__':
  25. pytest.main([__file__, '-sm', 'smoke'])

6、pytest运行参数

前面使用pytest.main来运行case,也可以通过pytest命令在命令行中运行,可以指定运行哪个文件里面的case,从哪个目录下查找case等等。

下面是几个常用的参数
-s: 显示程序中的print/logging输出
-v: 丰富信息模式, 输出更详细的用例执行信息
-q: 安静模式, 不输出环境信息
-k:关键字匹配,用and或者or区分:匹配范围(文件名、类名、函数名)
-m:mark匹配
—last-failed : 只运行上一次失败的case
—failed-first:优先运行上一次失败的case

  1. if __name__ == '__main__':
  2. # pytest.main(["-q",__file__]) #安静模式,不会显示pytest版本信息等等
  3. pytest.main(["-v",__file__]) #详细模式,会打印每条case执行结果,指定python文件
  4. # pytest.main(["-s",__file__]) #展示模式,函数里面有print或者日志输出,会展示,指定python文件
  5. # pytest.main(["-vs",__file__]) #展示模式+详细模式,指定python文件
  6. # pytest.main(["-vs","./cases"]) #展示模式+详细模式,指定目录
  7. # pytest.main(["./cases","-k","login"]) #指定运行包含login关键字的,包括 py文件、类、函数
  8. # pytest.main(["./cases","-k","login or register"]) #指定运行包含login或者register关键字的,包括 py文件、类、函数
  9. # pytest.main(["./cases","-k","login and online"]) #指定运行包含login并且包含online关键字的,包括 py文件、类、函数
  10. # pytest.main(["./cases","-m","smoke"]) #指定运行smoke标签的case
  11. # pytest.main(["./cases","-m","smoke or online"]) #指定运行smoke或者online标签的case
  12. # pytest.main(["./cases","-m","smoke and online"]) #指定运行smoke是并且是online标签的case
  13. # pytest.main(["./cases", "-sk", "user", "-m", "smoke"]) # 指定运行名称中包含user的,标签是smoke的case
  14. # pytest.main(["./cases", "-sk", "user", "-m", "smoke","--last-failed"]) # 指定运行名称中包含user的,上次失败的标签是smoke的case
  15. # pytest.main(["./cases", "-sk", "user", "-m", "smoke","--failed-first"]) # 指定运行名称中包含user的,优先运行上次失败的标签是smoke的case

pytest命令行

当然上面的参数一样可以在命令行里面使用pytest命令来运行case

  1. pytest -vs a.py #只运行a.py这个文件里面的case
  2. pytest ./cases -vs #运行case目录下的
  3. pytest ./cases -m smoke #指定运行冒烟case

7、统计case运行结果

统计case运行的结果需要使用pytest-json-report插件,需要安装

  1. pip install pytest-json-report

代码:

  1. import pytest
  2. from pytest_jsonreport.plugin import JSONReport
  3. class TestCase:
  4. def test_print(self):
  5. print("test_print")
  6. def test_hello(self):
  7. print("test_hello")
  8. def test_normal(self): # 可以加多个标签
  9. print("test_normal")
  10. 1/0
  11. class TestCase2: # 装饰类的方式
  12. def test_mygod(self):
  13. print("test_mygod!!!")
  14. assert 1==2
  15. def test_my(self):
  16. print("test_my!!!!")
  17. class TestCase3:
  18. def test_car(self):
  19. print("test_car!!!")
  20. @pytest.mark.skip("!!")
  21. def test_car1(self):
  22. print("test_car!!!")
  23. def fly(self):
  24. print("test_fly!!!!")
  25. def test_user(): # 可以加多个标签
  26. print("test_user")
  27. if __name__ == '__main__':
  28. plugin = JSONReport()
  29. pytest.main(['--json-report-file=none', __file__], plugins=[plugin])
  30. #pytest.main(["-s", __file__, "-m", "smoke and online"],plugins=[plugin])
  31. summary = plugin.report.get("summary")
  32. passed = summary.get("passed",0)
  33. failed = summary.get("failed",0)
  34. skipped = summary.get("skipped",0)
  35. total = summary.get("total",0)
  36. print("共{}条,通过{}条,失败{}条,跳过{}条".format(total,passed,failed,skipped))

如果不写:—json-report-file=none,会在当前目录下生成一个.report.json的文件
image.png

image.png

8、测试报告

pytest有好几种测试报告插件,可以自己选择哪个好看用哪个。

1、pytest-html

安装

  1. pip install pytest-html
  1. #pytest.main(["--html=./report.html","--self-contained-html"])
  2. pytest.main(["-vs", __file__,"--html=./report.html","--self-contained-html"])

image.png

2、PyTestReport

安装:

  1. pip install PyTestReport

使用:

  1. pytest.main([ __file__,"--pytest_report", "py_test_report.html", "--pytest_title", "这是测试报告的标题", "--pytest_desc", "测试报告描述"])

会在当前目录下生成一个py_test_report.html文件

image.png

3、pytest-html-reporter

安装:

  1. pip install pytest-html-reporter

使用:

  1. pytest.main(["--html-report=./report_new.html"])

9、fixture

fixture的作用和前置后置操作差不多,都是起一样的作用,是case执行之前自动运行或者case执行完之后自动运行,但是比它们使用更灵活。 teardown或者setup之类的操作,是拿不到里面的返回值的,使用fixture是可以的;teardown或者setup之类的操作,没办法指定某些case运行前置条件,某些不运行,使用fixture是可以的;teardown或者setup之类的操作,如果一些跨文件的case也都需要使用同样的前置条件,那么就需要在每个文件/每个类里面都写,而fixture可以只写一次。 fixture就是一个普通的函数,使用pytest.fixture装饰之后,就变成了一个fixture,使用yield关键字,来决定是前置条件还是后置条件。

9.1、基本使用

  1. @pytest.fixture(scope="function",autouse=True)
  2. def exe_database_sql():
  3. print("执行SQL查询") #前置操作
  4. yield
  5. print("关闭数据库连接") #后置操作

@pytest.fixture(scope=None,autouse=False,params=None,ids=None ,name=None)
fixture里面有个参数autouse,默认是Fasle没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了。

基本使用:

  1. import pytest
  2. @pytest.fixture
  3. def login(): #定义fixture
  4. print("login!")
  5. token = "admin-token"
  6. return token
  7. def test_order(login):#使用这个fixture的时候,要把这个函数名当做参数传进来
  8. print("test_order_login",login)
  9. assert 1 == 1
  10. def test_order2(login):
  11. print("test_order2_login",login)
  12. assert 1 == 1
  13. if __name__ == '__main__':
  14. pytest.main(['-vs',__file__])

image.png

9.2 scope

scope的作用是指fixture的作用范围,有4个参数可以选,function、class、module和session,默认如果不写的话是function

名称 作用范围
function 每个用了这个fixture的函数运行之前都会先运行这个fixture
class 如果函数是在类里面的话,类里面有多个函数都使用了这个fixture,那么每个类在运行之前只会运行一次,类似setup_class
module 这个python文件里面所有用到这个fixture的,这个文件里面只会运行一次
session 如果有多个python文件里面都用到了这个fixture,那么就只会运行一次

示例一:scope=class

  1. import pytest
  2. @pytest.fixture(scope="class", )
  3. def login(): # 定义fixture
  4. print("login!")
  5. token = "admin-token"
  6. return token
  7. class TestUser:
  8. def test_add_user(self, login, ):
  9. print("test_add_user", login)
  10. def test_modify_user(self,login,):
  11. print("test_modify_user",login)
  12. class TestOrder:
  13. def test_order2(self,login):
  14. print("test_order2_login", login)
  15. assert 1 == 1
  16. if __name__ == '__main__':
  17. pytest.main(['-vs', __file__])

image.png

示例二:scope=module

  1. import pytest
  2. @pytest.fixture(scope="module", )
  3. def login(): # 定义fixture
  4. print("login!")
  5. token = "admin-token"
  6. return token
  7. class TestUser:
  8. def test_add_user(self, login, ):
  9. print("test_add_user", login)
  10. def test_modify_user(self,login,):
  11. print("test_modify_user",login)
  12. class TestOrder:
  13. def test_order2(self,login):
  14. print("test_order2_login", login)
  15. assert 1 == 1
  16. if __name__ == '__main__':
  17. pytest.main(['-vs', __file__])

image.png

示例三:scope=”session”

session这个比较特殊需要单独说,需要定义在conftest.py这个文件里面
下面是目录结构,有3个case文件,一个conftest.py,conftest.py这个是固定的名字
image.png

  1. #conftest.py
  2. import pytest
  3. @pytest.fixture(scope='session')
  4. def login_session():
  5. print("login_session!!!")
  6. return "admin-token-session"
  1. # test_order.py
  2. class TestOrder:
  3. def test_order_user(self,login):
  4. print("test_order_user",login )
  5. def test_delete_order(self,login):
  6. print("test_order_user",login)
  7. def test_update_order(self,login):
  8. print("test_order_user",login)
  1. #test_pay.py
  2. class TestPay:
  3. def test_pay_success(self,login,):
  4. print("test_pay_success",login )
  5. def test_pay_error(self,login):
  6. print("test_pay_error",login)
  1. #test_user.py
  2. class TestUser:
  3. def test_add_user(self,login,):
  4. print("test_add_user",login )
  5. def test_delete_user(self,login):
  6. print("test_delete_user",login)
  7. def test_modify_user(self,login):
  8. print("test_modify_user",login)
  9. if __name__ == '__main__':
  10. import pytest
  11. pytest.main(['-vs','./'])

运行case也可以使用pytest命令执行,指定运行这3个python文件

  1. pytest -s test_order.py test_pay.py test_user.py

image.png

9.3 autouse

autouse这个参数的作用是自动使用fixture,自动使用的话,就不需要传入fixture了,case在运行之前他会自动运行fixture 使用atuouse的话,返回值就获取不到了,需要用返回值的话 ,还是需要传入fixture

  1. import pytest
  2. @pytest.fixture(scope="function",autouse=True)
  3. def login(): # 定义fixture
  4. print("login!!!!")
  5. token = "admin-token"
  6. return token
  7. def test_order():
  8. print("test_order_login")
  9. assert 1 == 1
  10. def test_order2():
  11. print("test_order2_login")
  12. assert 1 == 1
  13. if __name__ == '__main__':
  14. pytest.main(['-vs', __file__])

下面是结果,可以看到两个case并没有传入fixture,它自动调用了2次

image.png

9.4 fixture前置/后置操作

前面用的都是fixture的前置操作,如果使用后置操作的话,那就需要用到yield了,yield之前的代码是case执行之前做的,yield之后的代码是case执行做的。 如果使用了yield,那就不需要写return了,如果需要返回值,可以直接在 yield后面加返回值

  1. import pytest
  2. import os
  3. @pytest.fixture(scope="function", autouse=True)
  4. def check_dir():
  5. print("case执行之前")
  6. if not os.path.exists("imgs"):
  7. os.mkdir("imgs")
  8. yield 1111
  9. os.rmdir("imgs")
  10. print("case执行之后!!")
  11. def test_order(check_dir):
  12. print("test_order_login",check_dir)
  13. assert 1 == 1
  14. def test_order2():
  15. assert 1 == 1
  16. if __name__ == '__main__':
  17. pytest.main(['-vs', __file__])

image.png

9.5、 fixture和参数化一起使用:

  1. import pytest
  2. import os
  3. @pytest.fixture(scope="module")
  4. def db_connect():
  5. print("db_connect!!!!!!!!")
  6. return "dfddfd"
  7. @pytest.fixture(scope="module",autouse=True)
  8. def check_dir():
  9. print("用例执行之前")
  10. if not os.path.exists("imgs"):
  11. os.mkdir("imgs")
  12. yield 1111
  13. os.rmdir("imgs")
  14. print("case执行之后!!")
  15. @pytest.mark.parametrize('a,b,c',[
  16. [1,2,3],
  17. [4,5,9],
  18. [1,5,7],
  19. ])
  20. def test_add(a,b,c,db_connect,check_dir):
  21. assert a+b == c
  22. if __name__ == '__main__':
  23. pytest.main([__file__,'-vs'])

image.png

文章引用:https://www.yuque.com/nnzhp/nkqyhm/qvldtm#d3GkF