pytest用例管理
1.安装
pip install pytest
2.编写
2.1 编写规则
- 用例文件:所有文件名以test_开头
- 用例类:测试文件中每个Test开头的类就是一个测试用例类
- 测试用例:测试类中每一个以test开头的方法就是一个测试用例
2.2 函数形式编写测试用例
def test_demo():
assert 100 == 100
使用pytest可以执行所有测试用例
2.3 以类的形式编写用例
class Test_demo:
def test_demo1():
assert 100 == 100
def test_demo2():
assert 100 == 200
3.执行测试
3.1 执行方式
- 命令行在文件入口执行pytest
- 代码中通过
pytest.main()
执行
3.2 执行参数
-
-v 显示测试的详细参数信息
命令行:pytest -v
代码:pytest.main("-v")
-
-s 显示测试执行的详细信息
-
-vs 展示全部信息
3.3 参数传递
-
-k 匹配用例名词(当前路径下的 文件名、类名、方法名,备注可以结合or,not 等关键字 ,不区分大小写)
pytest.main(['-vs', '-k', 'up', './test_2.py'])
-
-x 遇到错误停止
`pytest.main(['-vs','-x', './test_stop.py'])
-
--maxfail=num 失败数量达到num个时停止
pytest.main(['-vs', '--maxfail=2', './test_stop.py'])
-
-n=num 开启num个线程去执行用例 (需要安装 pytest-xdist)
pytest.main(['-vs','./test_thread.py','-n=2'])
-
--reruns=num 设置失败重跑次数 如果存在用例失败,用例失败后重跑的次数num
pytest.main(['-vs', './test_fail.py', '--reruns=2'])
3.4 指定执行的测试目录和文件
::用于指定测试类和测试用例
pytest testcase/test_demo::Test_demo::test_demo
4.前后置方法和fixture机制
4.1 函数用例的前后置方法
def setup_function(function):
print("前置方法")
def teardown_function(function):
print("后置方法")
def test_0():
print("执行")
4.2 测试类中的前后置方法
class Test_demo:
def test_0():
print("执行0")
def test_1():
print("执行1")
@classmethod
def setup_class(cls):
print("前置方法")
@classmethod
def teadown_class(cls):
print("后置方法")
4.3 测试用例前后置方法(先执行测试类方法,再执行函数测试用例)
class Test_demo:
def test_0():
print("执行0")
def test_1():
print("执行1")
@classmethod
def setup_class(cls):
print("执行测试类前置")
@clasmethod
def teadown_class(cls):
print("执行测试类后置")
def setup_method(function):
print("测试用例前置")
def teardown_method(function):
print("测试用例后置")
4.4 模块级别前后置方法
def setup_module(module):
print()
def teardewn_module(module):
print()
4.5 fixture机制
4.5.1 测试夹具级别
夹具定义可以通过参数scope指定夹具的级别,如果不指定夹具级别,scope 默认值为function(用例级别)
function | 每个函数或方法都会调用 |
---|---|
class | 每个类调用一次,一个类中存在多个方法 |
module | 每个.py文件调用一次,一个文件中存在多个class和多个function |
session | 多个文件调用一次,可以跨.py文件使用,每个文件就是一个module |
4.5.2 调用fixture
-
函数或类里面方法直接传fixture的函数名称
import pytest @pytest.fixture(scope='function') def test1(): print("前置夹具") def test_01(test1): print("测试函数") class Test_02: def test_02(self, test1): print("类方法")
-
使用装饰器@pytest.mark.usefixtures()
import pytest @pytest.fixture(scope='function') def test1(): print("前置夹具") @pytest.mark.usefixtures('test1') def test_01(): print("测试函数") @pytest.mark.usefixtures('test1') class Test_01: def test_02(self): print("类方法")
-
叠加usefixtures
import pytest @pytest.fixture(scope='function') def test1(): print("前置夹具1") @pytest.fixture(scope='function') def test2(): print("前置夹具2") @pytest.mark.usefixtures("test1") @pytest.mark.usefixtures("test2") # 按照顺序执行夹具 class Test_01: def test_01(self): print("类方法")
-
fixture自动执行 autouse=True
适用于多用例情况,直接执行
import pytest @pytest.fixture(scope='function', autore=True) def test1(): print("前置夹具1") @pytest.fixture(scope='function', autore=True) def test2(): print("前置夹具2") class TestCase: def test_01(self): print("类方法")
4.5.3 yield和终结函数
-
yield
@pytest.fixture(scope="function") def test1(): print("开始执行") yield 1 # 不需要返回值时直接 yield print("执行结束") # 起到分割作用,yield之前的代码为setup,后面为teardown # 可以实现参数传递
def test2(test1): print(test1) # 1
存在多个fixture存在yield时,先按照线性顺序执行前置,执行过后,反向执行yield后的逻辑
5. conftest.py
用于管理fixture,工程根目录下设置的conftest.py文件起到全局作用,不同子目录下只在该层级以及一下目录生效
5.1 注意事项
- pytest会默认读取conftest.py里面的所有fixture
- conftest.py文件名是固定的,不能移动
- conftest.py只对同一个package下的所有测试用例生效
- 不同目录可以有自己的conftest.py,一个项目可以有多个conftest.py
- 测试用例文件不用手动import,pytest会自动查找
5.2 举例
conftest.py
import pytest
@pytest.fixture(score="session")
def login():
print("登录")
name = "test"
token = ""
yield name, token # 起到分割作用,yield之前的代码为setup,后面为teardown
print("退出登录")
@pytest.fixture(autose=True)
def get_info(logins):
name, token = logins
print(f"打印token: {token}")
get_info(next(login()))
test1.py
def test_get_info(login):
name, token = login
print(f"用户名:{}, token{}")
run.py
import pytest
if __name__ == '__main__':
pytest.main(["-s", "目录"])
6. 参数化和数据驱动
6.1 fixture传参
@pytest.fixture(scope="function", params=[1, 2, 4])
def need_data(request):
yield request.param
def test_001(need_data):
print(need_data)# 依次输出1, 2, 4,经历三次测试
6.2 mark.parametrize数据驱动
class Test_add:
@pytest.mark.parametrize("arg", [1, 2, 3])
def test_01(self, arg):
print(arg) # 三次测试
read_yaml.py
class Read_Yaml:
def __init__(self, filename):
self.filename = filename
def read_yam(self):
with open(self.filename, "r", encoding="utf-8") as f:
context = yaml.load(f, Loader=yaml.Loader)
return context
requests_paclage.py
class PacKag:
def __init__(self, api_data):
self.api_data = api_data
def packaging(self):
if self.api_data["method"] == "get":
res = requests.request(method=self.api_data["method"], url=self.api_data["url"],
params=self.api_data["params"], headers=self.api_data["headers"])
else:
data = json.dumps(self.api_data["body"])
res = requests.request(method=self.api_data["method"], url=self.api_data["url"],
data=data, headers=self.api_data["headers"])
return res.json()
testcase.py
class Test_001:
test_data = Read_Yaml(setting.yaml_data).read_yam()
# 读取列表嵌套字典,依次取出字典,存在几个字典进行几次用例
@allure.title("用户登录")
@pytest.mark.parametrize("arg", test_data)
def test_case001(self, arg):
try:
res = PacKag(arg).packaging()
assert res["errorMessage"] == arg["msg"]
logger.info("测试通过,结果为{}".format(res["errorMessage"]))
except Exception as e:
logger.error(e)
6.3 fixture+mark.parametrize
在fixture中实现前后置方法,使用mark进行数据驱动
@pytest.fixture(scope='function')
def add_data(request):
print(request.param) # 根据传进来的参数依次执行前后置方法
yield request.param["name"]
class Test_001:
test_data = Read_Yaml(setting.authentication_yaml).read_yam()
@allure.title("用户登录")
@pytest.mark.parametrize("add_data", test_data, indirect=True)
# indirect=True,可以指定数据传入指定的前后置函数
def test_case001(self, add_data):
try:
res = PacKag(add_data).packaging()
assert res["errorMessage"] == arg["msg"]
logger.info("测试通过,结果为{}".format(res["errorMessage"]))
except Exception as e:
logger.error(e)
6.4 多个传入变量,列表嵌套元组
class Test_001:
@allure.title("用户登录")
@pytest.mark.parametrize("tt, ll", [(1, 2), (3, 4)])
# indirect=True,可以指定数据传入指定的前后置函数
def test_case001(self, tt, ll):
try:
print(tt, ll)
except Exception as e:
logger.error(e)
# 第一次输出1,2 第二次输出3,4
6.5 参数组合测试
username = ["lilei", "hanmeimei"]
password = ["123456", "888888"]
@pytest.mark.parametrize("password", password)
@pytest.mark.parametrize("username", username)
def test_login(username, password):
headers = {"Content-Type": "application/json;charset=utf8"}
url = "http://127.0.0.1:5000/login"
_data = {
"username": username,
"password": password
}
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
assert res['code'] == 1000
# 同时存在多个mark时,会采用枚举两两组合,上文存在四次测试用例
6.5 参数说明(ids)
@pytest.mark.parametrize("args, tt", [(1, 2), (3, 4)], ids=["a:{}-b:{}".format(a, b) for a, b in [(1, 2), (3, 4)]])
# 输出testcase.py::Test_002::test_case002[a:1-b:2].......
7.插件
7.1测试执行顺序:pytest-ordering
默认执行顺序从上而下,使用pytest-ordering实现自定义执行顺序
pip install pytest-ordering
import pytest
class TestOrder:
def test_e(self):
print("test_e")
def test_4(self):
print("test_4")
def test_b():
print("test_a")
@pytest.mark.run(order=2)
def test_a():
print("test_a")
@pytest.mark.run(order=1)
def test_2():
print("test_2")
def test_1():
print("test_1")
if __name__ == '__main__':
pytest.main()
7.2 失败后重新执行:pytest-rerunfailures
测试失败后要重新运行n次,间隔n秒后执行
执行命令 pytest -v --reruns 5 --reruns-delay 1
,每次等待1秒,重新执行5次
7.3 一个用例多个断言,前面失败后依然可以运行
pytest-assume
pytest.assume(1 == 1)
pytest.assume(1 == 2)
# 使用这种断言不会执行try except,详情需要实时日志
7.4 多进程并行和分布式执行
pytest-xdist
pytest -v test.py -n 5
,五个进程执行
8.标记用例执行
只执行标记的部分用例,指定执行某一类或某个场景的测试用例
8.1 pytest.ini
[pytest]
markers =
smoke: marks test as smoke
login
order: 下单场景
@pytest.mark.smoke
def test_01():
print("执行test_01")
def test_02():
print("执行test_02")
8.2 conftest.py
定义钩子函数
def pytest_configure(config):
marker_list = [
"smoke: ok",
"login",
"order: 场景"
]
for marker in marker_list:
config.addinivalue_line("markers", marker)
import pytest
# 标记测试函数
@pytest.mark.smoke
def test_01():
print("执行test_01")
def test_02():
print("执行test_02")
# 标记测试类
@pytest.mark.order
class TestOrder:
def test_order(self):
print("下单")
def test_pay(self):
print("支付")
# 多个标签
@pytest.mark.smoke
@pytest.mark.login
def test_login():
print("登录")
8.3 执行
pytest -m smoke
执行单个用例
pytest -m smoke and login
执行多个用例
pytest -m smoke or login
执行或
pytest.main(['-m', 'smoke and login'])
不能在当前模块运行,需要新建run.py,分离执行才会生效
8.4 标记跳过
import pytest
@pytest.mark.skip(reason="不需要执行test_01")
def test_01():
print("执行test_01")
@pytest.mark.skip(2>1, reason="如果2大于1则跳过不执行")
def test_02():
print("执行test_02")
if __name__ == '__main__':
pytest.main(['-s'])
8.5 标记执行失败
部分场景:对未实现的功能或者尚未修复的错误进行测试,使用@pytest.mark.xfail
可以将测试用例标记为失败
pytest.mark.xfail(condition=None, reason=None, raises=None, run=True, strict=False)
condition: 预期失败的条件
reason: 失败原因,会在控制台展示
run: 默认值为True,当run为false时,pytest不会执行该用例,直接将结果标记为xfail
strict: 当 strict=False 时,如果用例执行失败,则结果标记为xfail,表示符合预期的失败;如果用例执行成功,结果标记为XPASS,表示不符合预期的成功;
当strict=True时,如果用例执行成功,结果将标记为failed。
import pytest
# run、strict都为默认,因为用例执行是失败的,所以该用例执行结果会被标记为xfail
@pytest.mark.xfail(reason="bug待修复")
def test_01():
print("执行test_01")
a = "hello"
b = "hi"
assert a == b
# run、strict都为默认,因为用例执行是通过的,所以该用例执行结果会被标记为xpass
@pytest.mark.xfail(condition=lambda: True, reason="bug待修复")
def test_02():
print("执行test_02")
a = "hello"
b = "hi"
assert a != b
# run=False,该用例不执行,直接将结果标记为xfail
@pytest.mark.xfail(reason="功能尚未开发完成", run=False)
def test_03():
print("执行test_03")
a = "hello"
b = "hi"
assert a == b
# strict=True,因为用例执行是通过的,所以结果会被标记为failed
@pytest.mark.xfail(reason="功能尚未开发完成", strict=True)
def test_04():
print("执行test_04")
a = "hello"
b = "he"
assert b in a
if __name__ == '__main__':
pytest.main(['-s'])
9.实时日志(方便监控一个用例多个断言)
9.1 pytest.ini
[pytest]
log_cli = 1
log_cli_leavl = INFO
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
9.2 直接使用pytest- o
pytest test.py -o log_cli=true -o log_cli_level=INFO
格式: -vv -o log_cli=true -o log_cli_level=INFO --log-date-format="%Y-%m-%d %H:%M:%S" --log-format="%(filename)s:%(lineno)s %(asctime)s %(levelname)s %(message)s"
10.测试报告
10.1 pytest-html
pip install pytest-html
-
pytest.main执行
pytest.mian(["--html=测试报告.html"])
在用例文件中执行该命令不能生成测试报告,需要单独开一个run.py文件执行 -
命令行执行:
pytest test.py --html=测试报告.html
pytest test.py --html=E:测试报告.html
10.2 allue
10.2.1 安装
- 安装pytest-allure:
pip install pytest-allure
- 安装allure
- 配置环境变量:path: D:\allure\allure-2.15.0\bin
10.2.2 特性
@allure.epic() | 描述被测软件系统 |
---|---|
@allure.feature() | 描述被测软件的某个功能模块 |
@allure.story() | 描述功能模块下的功能点和测试场景 |
@allure.title() | 定义测试的标题 |
@allure.description() | 测试用例的说明描述 |
@allure.severity() | 标记测试用例级别,由高到低:blocker、critical、normal、mirnor、trivial |
@allure.step() | 标记通用函数使之成为测试步骤,测试方法中调用此函数的地方会像报告中输出步骤描述 |
allure.attach() | 插入附件 allure.attach(body, name=None, attachment_type=None, extension=None) ,内容,名称,类型 |
allure.attach.file() | 插入附件 allure.attach.file(source, name=None, attachment_type=None, extension=None) 内容,名称,类型 |
with allure.step() | 标记测试步骤 |
allure.issue("url", name='') | 跳转链接 |
allure.testcase() | 跳转链接 |
@allure.story("用户退出登录")
@allure.title("退出登录")
def test_logout(self):
'''这条测试用例仅仅只是为了举例说明allure.attach.file的使用'''
print("退出登录,并截图")
# 截图路径
testcase_path = (os.path.join(os.path.dirname(os.path.abspath(__file__))), "/screenshot/logout.jpg")
allure.attach.file(
source=testcase_path,
name="退出登录后截图",
attachment_type=allure.attachment_type.JPG # 图片类型
)
assert True
with allure.step()
with allure.step("请求登录接口"):
allure.attach(
body="用户名-{},密码-{}".format(username, password),
name="登录参数",
attachment_type=allure.attachment_type.TEXT
)
res = requests.post(url=url, headers=headers, json=_data).text
res = json.loads(res)
# 第二步,获取返回参数进行断言
with allure.step("断言"):
assert res['code'] == 1000
# with 方式一般用于截图或者文件
# with allure.step("步骤详情"):
# 具体用例
# 或者装饰器,这种可以展示步骤传参
@allure.step("step:添加购物车")
def add_shopping_cart(goods_id="10086"):
'''添加购物车'''
print("添加购物车")
10.2.4 生成测试报告
-
pytest.main()
pytest.main(['test.py', '-s', '-q', '--alluredir', './result']) # 运行test.py测试用例,将测试结果以json的格式保存在当前目录的result文件夹中 os.system('allure generate ./result -o ./report --clean') 执行json数据,并生成测试报告,保存在当前目录的report文件夹中, --clean删除之前生成的测试报告
run.py
if __name__ == '__main__': pytest.main(['testcase/test_case.py', '-s', '-q', '--alluredir', './result']) os.system('allure generate ./result -o ./report --clean')
-
命令行
pytest testcase/test_case.py --alluredir ./result
allure generate ./result -o ./report --clean
然后allure open ./report
或者allure serve ./report
10.2.5 定制部分模板
-
envionment,展示此次测试执行的环境信息
创建envionment.properties,放在--alluredor指定的文件夹
system=win python=3.7.7 version=1.0.1 host=127.0.0.1 test=孔振国
-
categories,用于定制缺陷分类,创建categories.json配置,跟上一个相同目录
[ { "name": "Passed tests", "matchedStatuses": ["passed"] }, { "name": "Ignored tests", "matchedStatuses": ["skipped"] }, { "name": "Infrastructure problems", "matchedStatuses": ["broken", "failed"], "messageRegex": ".*bye-bye.*" }, { "name": "Outdated tests", "matchedStatuses": ["broken"], "traceRegex": ".*FileNotFoundException.*" } ]
11.CI/CD jenkins
11.1 docker 安装jenkins并更新版本
docker pull jenkens/jenkins
docker run -it --name jenkins -p 8080:8080 -p 5000:5000 jenkins/jenkins
docker cp jenkins.war jenkins/jenkins: var/share/jenkins
docker restart jenkins/jenkins
11.2 安装Allure插件并进行全局工具配置
本地安装直接取消自动安装并写入allure安装目录
11.3 新建windows节点,并启动
新建本地运行环境
curl.exe -sO http://47.92.76.123:8080/jnlpJars/agent.jar
java -jar agent.jar -jnlpUrl http://47.92.76.123:8080/computer/windows/jenkins-agent.jnlp -secret 724f3a87c3cf0c7278c35d365a28c9b32345c1fccc5859dc2a3ac7a3546714a4 -workDir "E:\Jenkins"
11.4 新建任务
- 限制项目的运行节点
- 使用自定义工作空间
- 构建前命令
pytest testcase.py -vs --alluredir ./allure
- 构建后添加Allure Report 指定json数据目录,并指定生成报告目录
12.钩子函数
12.1 切换测试环境
-
Hook方法
conftest.py
注册一个自定义的命令行参数
def pytest_addoption(parser): parser.addoption("--env", action="store", default="url", help="将--env添加到pytest配置中") # action="store",允许用户存储任何类型的值 # default 在--env不填值时,默认值 # help 说明 @pytest.fixture(scope="session") def get_env(request): """从配置对象中读取自定义参数的值""" return request.config.getoption("--env") # 可以不用写 @pytest.fixture(autouse=True) def set_env(get_env): """将自定义参数的值写入全局配置文件""" with open(ENV_TXT_FILE, 'w', encoding='utf-8') as f: f.write(get_env)
test_demo.py
def test_001(get_env): print(get_env) # 传入主机名,测试用例中可以通过使用不同的主机切换测试环境
-
使用pytest-base-url插件
-
安装pytest-base-url
pip install pytest-base-url -i https://pypi.douban.com/simple
-
将base-url参数传入fixture函数中
@pytest.fixture def driver_setup(base_url): try: URL = base_url start_chrome(URL, options=browser_options(), headless=False) driver = get_driver() except Exception as e: log.error(e) else: yield driver # 如果发生异常,对异常操作,如果正常,执行eles里面的逻辑 #或者直接使用 def test_001(base_url): print base_url
-
pytest命令行传参
pytest --base-url https://www.cnblogs.com/
-