安装
pip install pytest
将浏览器设置为pytest运行:Pycharm-Preferences-Tools-Python intergrated Tools,
将Testing下Default test runner设为pytest
用例识别与运行:
- 文件范围
- test_*.py
- *_test.py
- 用例识别:
- Test类包含的所有test_方法
- 不在class中的所有test_*方法
- 类中不能用初始化方法
用例控制顺序:
- setup_module
- @class_method
- setup_method
- setup_function
- teardown_*
- pytest-ordering:控制用例之间的顺序
import logging
logging.basicConfig(level=logging.DEBUG)
def setup_module():
logging.info("setupmodule")
def teardown_module():
logging.info("teardownmodule")
class TestPytestObject2:
def test_three(self):
assert [1, 2] == [1, 3]
def test_four(self):
assert {"a": 1, "b": "ss"} == {"a": 2, "b": "ss"}
class TestPytestObject:
@classmethod
def setup_class(cls):
logging.info("setupclass")
def setup_method(self):
logging.info("setupmethod")
def test_two(self):
assert 1 == 1
def test_one(self):
assert True == True
def teardown_method(self):
logging.info("teardownmethod")
@classmethod
def teardown_class(cls):
logging.info("teardownclass")
输出:
INFO:root:setupmodule
.. [100%]
=========================== 4 passed in 0.07 seconds ===========================
Process finished with exit code 0
INFO:root:setupclass
INFO:root:setupmethod
.INFO:root:teardownmethod
INFO:root:setupmethod
.INFO:root:teardownmethod
INFO:root:teardownclass
INFO:root:teardownmodule
pytest-ordering:控制用例之间的顺序
pip install pytest-ordering
实例应用:
class TestPytestObject:
@classmethod
def setup_class(cls):
logging.info("setupclass")
def setup_method(self):
logging.info("setupmethod")
@pytest.mark.run(order=2)
def test_two(self):
assert 1 == 1
@pytest.mark.run(order=1)
def test_one(self):
assert True == True
def teardown_method(self):
logging.info("teardownmethod")
@classmethod
def teardown_class(cls):
logging.info("teardownclass")
pytest框架命令:
pytest --collect-only # 表示把待执行的用例全部展示出来,信息较多
pytest --collect-only -q # 获取测试名称列表
pytest --collect-only -qq # 获取测试文件列表及用例数量
参数化
官方:
import pytest
# "test_input,excepted":两个参数
@pytest.mark.parametrize("test_input,excepted", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, excepted):
assert eval(test_input) == excepted
执行结果:
import pytest
from src.calc import Calc
class TestCalc:
def setup(self) -> None:
self.calc = Calc()
@pytest.mark.parametrize("a, b, c", [
(1, 1, 2),
(1, 0, 1),
(1, -1, 0),
(1, 10000000, 10000001)
])
def test_add(self, a, b, c):
print(a, b, c)
assert self.calc.add(a, b) == c
数据驱动
文件参数:json文件
# calc.json文件内容
[
[2, 1, 3],
[2, 1, 2],
[2, 3, 6],
[3, 1, 3]
]
@pytest.mark.parametrize("a, b, c", json.load(open("calc.json")))
def test_div(self, a, b, c):
assert self.calc.div(a, b) == c
文件参数:yaml文件
# calc.yaml文件内容
- [2, 1, 2]
- [1, 2, 0.5]
- [100, 100, 1]
- [2, 0, null]
@pytest.mark.parametrize("a, b, c", yaml.load(open("calc2.yaml")))
def test_div2(self, a, b, c):
assert self.calc.div(a, b) == c
装饰器
- 将当前方法以参数形式传递给装饰器
- 增加函数的功能,又不希望修改函数的定义,这种代码运行期间动态增加功能的方法,叫“装饰器”(Decorator)
- 装饰器是一个返回函数的高阶函数
def log(func):
def wrapper(*args, **kwargs):
print("call %s():" % func.__name__)
return func(*args, **kwargs)
return wrapper
@log
def now():
print("2021-8-23")
调用now()
函数,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志:
call now():
2021-8-23
相当于执行了语句:
noe = log(now)
由于log()
是一个装饰器,返回一个函数,所以原来now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数
简单例子:
def before(func):
def new_now():
print("setup")
func()
return new_now
@before
def now():
print("2021")
now()
.setup
2021
生成junit报告
pytest --junitxml=[path] [测试用例]
python3 -m pytest --junitxml=/tmp/junitxml /testcase/
bashgems
Fixture参数
方法中有相同的依赖,可通过fixtures参数化
import requests
import pytest
@pytest.fixture()
def topics():
return requests.get("https://testerhome.com/api/v3/topics.json?limit=2").json()
def test_1(topics):
assert len(topics["topics"]) == 2
def test_2(topics):
assert topics["topics"][0]["deleted"] == False
新建网页
cd /tmp/
mkdir www
cd www
wget https://testerhome.com/api/v3/topics.json?limit=2
cp topics.json\?limit\=2 topics.json
python -m http.server
访问生成的地址
import requests
import pytest
@pytest.fixture()
def topics():
url = "http://0.0.0.0:8000/topics.json"
# url = "https://testerhome.com/api/v3/topics.json?limit=2"
return requests.get(url).json()
def test_1(topics):
assert len(topics["topics"]) == 2
def test_2(topics):
assert topics["topics"][0]["deleted"] == False
127.0.0.1 - - [08/Sep/2021 14:28:00] "GET /topics.json HTTP/1.1" 200 -
127.0.0.1 - - [08/Sep/2021 14:28:00] "GET /topics.json HTTP/1.1" 200 -
Fixture参数-scope
- 作用域:控制Fixture的作用范围
- 默认值是function
- setup/teardown
setup
@pytest.fixture(scope="module")
def topics():
url = "http://0.0.0.0:8000/topics.json"
# url = "https://testerhome.com/api/v3/topics.json?limit=2"
return requests.get(url).json()
def test_1(topics):
assert len(topics["topics"]) == 2
def test_2(topics):
assert topics["topics"][0]["deleted"] == False
结果:只发一次请求,作用于模块
127.0.0.1 - - [08/Sep/2021 14:35:18] "GET /topics.json HTTP/1.1" 200 -
@pytest.fixture(scope="session")
def topics():
url = "http://0.0.0.0:8000/topics.json"
# url = "https://testerhome.com/api/v3/topics.json?limit=2"
return requests.get(url).json()
def test_1(topics):
assert len(topics["topics"]) == 2
def test_2(topics):
assert topics["topics"][0]["deleted"] == False
结果:sesion不变只发一次请求
127.0.0.1 - - [08/Sep/2021 14:37:36] "GET /topics.json HTTP/1.1" 200 -
conftest.py
import requests
import pytest
@pytest.fixture(scope="session")
def topics():
url = "http://0.0.0.0:8000/topics.json"
# url = "https://testerhome.com/api/v3/topics.json?limit=2"
return requests.get(url).json()
test_fixtures.py
def test_1(topics):
assert len(topics["topics"]) == 2
def test_2(topics):
assert topics["topics"][0]["deleted"] == False
test_fixtures2.py
def test_1(topics):
assert len(topics["topics"]) == 2
def test_2(topics):
assert topics["topics"][0]["deleted"] == False
- scope="module"时,请求2次,两个模块
- scope="session"时,请求1次,只生成1个session,每次会话只需要运行一次,会话内所有方法及类,模块都共享这个方法
- 默认时,请求4次,4个方法
teardown
- yield:当使用yield方法时,在执行过程中,会被对应的方法调用,将调用的方法作为参数传递到引用它的地方(yield后面)
conftest.py
import requests
import pytest
import logging
logging.basicConfig(level=logging.DEBUG)
@pytest.fixture()
def topics():
url = "http://0.0.0.0:8000/topics.json"
logging.info(url)
# url = "https://testerhome.com/api/v3/topics.json?limit=2"
yield requests.get(url).json()
logging.info("after yield")
test_fixtures.py
import logging
def test_1(topics):
logging.info("start")
assert len(topics["topics"]) == 2
logging.info("end")
结果:
test_fixtures.py INFO:root:http://0.0.0.0:8000/topics.json
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 0.0.0.0:8000
DEBUG:urllib3.connectionpool:http://0.0.0.0:8000 "GET /topics.json HTTP/1.1" 200 1463
.INFO:root:start
INFO:root:end
INFO:root:after yield
conftest.py
@pytest.fixture()
def topics2(request):
url = "http://0.0.0.0:8000/topics.json"
logging.info(url)
# url = "https://testerhome.com/api/v3/topics.json?limit=2"
def fin():
logging.info("after yield teardown")
request.addfinalizer(fin)
return requests.get(url).json()
test_fixtures.py
import logging
def test_1(topics2):
logging.info("start")
assert len(topics2["topics"]) == 2
logging.info("end")
结果:
INFO:root:http://0.0.0.0:8000/topics.json
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 0.0.0.0:8000
DEBUG:urllib3.connectionpool:http://0.0.0.0:8000 "GET /topics.json HTTP/1.1" 200 1463
.INFO:root:start
INFO:root:end
INFO:root:after yield teardown
param
- 传参
conftest.py
@pytest.fixture(params=["https://testerhome.com/api/v3/topics.json?limit=2",
"http://0.0.0.0:8000/topics.json", "http://0.0.0.0:8000/topics.json"])
def topics3(request):
url = request.param
logging.info(url)
# url = "https://testerhome.com/api/v3/topics.json?limit=2"
def fin():
logging.info("after yield teardown")
request.addfinalizer(fin)
return requests.get(url).json()
test_fixtures.py
import logging
def test_1(topics2):
logging.info("start")
assert len(topics2["topics"]) == 2
logging.info("end")
def test_2(topics3):
assert topics3["topics"][0]["deleted"] == False
结果:
分组
@pyteset.mark.x
import logging
import pytest
@pytest.mark.a
def test_1(topics2):
logging.info("start")
assert len(topics2["topics"]) == 2
logging.info("end")
@pytest.mark.b
def test_2(topics3):
assert topics3["topics"][0]["deleted"] == False
@pytest.mark.b
def test_3(topics2):
assert topics2["topics"][0]["deleted"] == False
执行命令:pytest -m b
,只执行b组的用例
(venv) MacBook-Pro-68:testcase emily$ pytest -m b