文章目录
文章很长,请合理利用目录功能
httprunner介绍
一、前言
httprunner 3.x 支持3种格式的用例:YAML/JSON/pytest 代码,3.x版本主推的是pytest测试用例。
httprunner是采用yaml/json文件编写测试用例,然后运行,但是3.x后引入了pytest框架,我们即便使用yaml和json文件,最后的执行也是转成.py文件执行。
二、什么是Httprunner
面向http协议的测试框架。只需维护一份yaml/json文件。 实现接口和UI自动化,结合locust可做性能测试,线上性能监控,持续集成等。
核心特点
- 继承 Requests 的全部特性,轻松实现 HTTP(S) 的各种测试需求
- 以YAML或JSON格式定义testcase,使用pytest运行,更加简洁优雅
- 有了pytest,成百上千的插件随时可用
- 支持variables/extract/validate/hooks机制来创建极其复杂的测试场景
- 借助辅助函数(debugtalk.py),任何函数都可以在测试用例的任何部分中使用
- 支持完善的测试用例分层机制,充分实现测试用例的复用
- 使用python的第三方库jmespath,让提取和验证json响应更简单
- 基于 HAR 实现接口录制和用例生成功能(har2case)
- 结合 Locust 框架,无需额外的工作即可实现分布式性能测试
- 执行方式采用 CLI 调用,可与 Jenkins 等持续集成工具完美结合
- 集成了Allure,让测试报告更加漂亮,内容更丰富。
- 极强的可扩展性,轻松实现二次开发和 Web 平台化
三、Httprunner2.x和3.x区别
- 执行用例框架:3.x 是pytest;2.x是unitest
- 用例格式:3.x支持YAML/JSON/pytest;2.x支持YAML/JSON
httprunner 3.x 版本弱化了api层的概念,直接在 testcase 中写request 请求,如果是单个请求,也可以直接写成一个 testcase ,所以本文主要学习httprunner3,即用py格式文件集成pytest执行用例。
四、环境搭建
- 支持Python2.7,推荐使用3.6及以上
- 直接安装
> pip install httprunner
> pip install har2case
- 验证环境
> hrun -V
> har2case -V
#-V大写
- Httprunner五个命令
> httprunner 主命令
> hrun httpruner的别名,用于运行yaml\json\pytest测试用例
> hmake httprunner make的别名,将yaml\json文件转换成pytest文件
> har2case httprunner har2case的别名,用于将har文件转化成yaml\json文件
> locust 用于性能测试
五、HttpRunner快速上手
5.1、测试用例结构
- har:存放抓包工具生成的har文件
- reports:存放报告
- testcases:存放自动化案例脚本
- .env 可存放环境变量,
- .gitignore:提交git忽略的文件
- debugtalk.py:案例中可调用的python方法,如查询数据库,等待,获取随机数等,注意 文件名一定不要写错,且目录层级不能改变,调用里面函数可以用${xxx(a,b)}
5.2 脚本录制
Charles / Fiddler /Chrome抓包生成HAR文件
5.2、生成和转化测试用例
可以借助Charles或fiddler 抓包工具生成.har格式的文件。har2case可以将.har文件转化成yaml格式或者json格式的httprunner的脚本
- .har文件不加后缀,可转换为py的文件:如>har2case work.har 会生成 work_test.py
- .har文件转化为.json格式的脚本文件,加"-2j"参数即可
- .har文件转化为.yml格式的脚本文件,加"-2y"参数即可
也可以用make把YAML/JSON转pytest用例
- make 是把 YAML/JSON 测试用例转成 pytest用例, 需指定 yaml 文件路径或文件夹路径
httprunner make testcase
# 等价于
hmake testcase
5.3、run 运行用例
生成的*_test.py文件放到testcases目录中就可以运行脚本了
httprunner run testcases
# 等价于
hrun testcase
#run 命令实际上有 2 个动作,一个是把 YAML/JSON 测试用例转成 pytest 用例,
#同上一步 make 的功能一样;
#第二个动作是用 pytest 执行测试用例
hrun xxx.yml
hrun xxx.json
等价于
hmake xxx.yml
pytest xxx_test.py
5.4、生成测试报告
hrun testcases/**_test.py --html=reports/mubu_login.html
打开测试报告
默认安装了 pytest-html 插件可以直接运行
open reports/mubu_login.html
–html=report.html可生成默认报告地址(pytest-html的),结尾加上–self-contained-html可生成更多信息
想看allure report,需要安装allure-pytest插件,后面pytest再详细介绍
六、py格式测试用例详解
httprunner 3.x 版本弱化了api层的概念,直接在 testcase 中写request 请求,如果是单个请求,也可以直接写成一个 testcase 。
每个 testcase 必须具有两个类属性:config 和 teststeps。
6.1 config
- 每个测试用例都应该有一个config部分,您可以在其中配置测试用例级别的设置,有以下属性
属性名称 | 是否必填 | 作用 |
---|---|---|
name | 必填 | 指定测试用例名称。这将显示在执行日志和测试报告中。 |
base_url | 可选 | 如果base_url指定,则 teststep 中的 url 可以设置相对路径部分 |
verify | 可选 | https请求时,是否校验证书,默认True,忽略证书校验可以设置为False |
variables | 可选 | 指定测试用例的公共变量,案例中需要用到的变量维护在这,方便以后维护。 |
export | 可选 | 指定导出的测试用例会话变量,把变量暴露出来,设置为全局变量 |
- name(必填)
即用例名称,这是一个必填参数。测试用例名称,将显示在执行日志和测试报告中。比如,我在之前的百度搜索的case里,加入name。
-
运行后,在debug日志里,可以看的用例名称被展示出来。
- base_url(选填)
其实这个配置一般在多环境切换中最常用。
比如你的这套测试用例在qa环境,uat环境都要使用,那么就可以把基础地址(举例http://demo.qa.com),设置进去。在后面的teststep中,只需要填上接口的相对路径就好了(举例 /get)。
这样的话,切换环境运行,只需要修改base_url即可。
- variables(选填)
变量,这里可以存放一些公共的变量,可以在测试用例里引用。这里大家可以记住这个“公共”的词眼,因为在后面的Step中,还会有步骤变量。
比如说,我的接口有个传参是不变的,比如用户名username,而且后面的没个Step都会用到这个传参,那么username就可以放在config的公共变量里。
另外,Step里的变量优先级是比config里的变量要高的,如果有2个同名的变量的话,那么引用的时候,是优先引用步骤里的变量的。
- verify(选填)
用来决定是否验证服务器TLS证书的开关。
通常设置为False,当请求https请求时,就会跳过验证。如果你运行时候发现抛错SSLError,可以检查一下是不是verify没传,或者设置了True。
- export(选填)
导出的变量,主要是用于Step之间参数的传递。还是以上面的官方代码为例:
在config中配置export“foo3”这个变量。
在第一个Step中,.extract() 提取了"body.args.foo2"给变量“foo3”。
在第二个Step中,引用变量"foo3"。
导出的变量可以通过以下方式获取变量值
TestCaseRequestWithTestCaseReference().test_start().get_export_variables()
# 在其他文件中取出TestCaseRequestWithTestCaseReference的response中到处的值foo3
6.2 测试用例分层模型
一个testcase里(就是一个pytest格式的Python文件)可以有一个或者多个测试步骤,就是teststeps[]列表里的Step。
我的理解每一个Step就可以类比成pytest框架下的def test_xxx()的用例函数,在Step里通常都会要请求API完成测试,也可以调用其他测试用例来完成更多的需求。
可以看到,testsuite包含了testcase,testcase1需要依赖testcase2才可以完成,那么就可以在teststep12对其进行引用;而testcase2又依赖于testcase3,那么也可以在teststep22进行引用。
但是在整个testsuite下,这3个testcase都是相互独立的,可以独自运行。如果需要相互调用,则是在testcase内部去完成处理。
可能看起来有点绕,其实官方想表达的就是测试用例分层的一个思想:
- 测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
- 测试用例是测试步骤(teststep)的有序集合
- 测试用例集(testsuite)是测试用例的无序集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
其实这一点,在我们自己使用pytest框架编写测试用例的时候同样贯彻到了。为了自动化测试的稳定性和可维护性,每个测试用例之间相互独立是非常有必要的。
6.3 teststeps -RunRequest
- teststeps 可以有多个步骤,每个步骤对应一个接口的请求,也就是 RunRequest
属性名称 | 是否必填 | 作用 |
---|---|---|
name | 必填 | 指定测试步骤名称 |
method(url) | 必填 | 。如果在Config中设置了baseurl,method中只能设置相对路径,可选参数为get/post/put/delete/等 |
with_params | 可选 | 对应于的params参数 |
with_headers | 可选 | 对应于的headers参数 |
with_cookies | 可选 | cookies参数 |
with_data | 可选 | cookies参数 |
with_cookies | 可选 | cookies参数 |
with_data | 可选 | 对应于的data参数 |
with_json | 可选 | 对应于的json参数 |
with_variables | 可选 | 指定测试步骤变量。每个步骤的变量都是独立的,参数引用使用" 变量名 " ,如果是函数引用使用 " 变量名",如果是函数引用使用" 变量名",如果是函数引用使用"{函数名()}" |
先上一段Step的代码,结合下面的点对照着看:
teststeps = [
Step(
RunRequest("get with params")
.with_variables(
**{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"}
)
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
.with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
.extract()
.with_jmespath("body.args.foo2", "foo3")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.foo1", "bar11")
.assert_equal("body.args.sum_v", "3")
.assert_equal("body.args.foo2", "bar21")
),
- RunRequest
从上面的代码可以看出,RunRequest的作用就是在测试步骤中请求API,并且可以对于API的响应结果进行提取、断言。
RunRequest的参数名用于指定teststep名称,它将显示在执行日志和测试报告中。
- with_variables
用于存放变量,但是这里的是Step步骤里的变量,不同的Step的变量是相互独立的。所以对于多个Step都要使用的变量,我们可以放到config的变量里去。
另外,如果config和Step里有重名的变量,那么当你引用这个变量的时候,Step变量会覆盖config变量。
- method(url)
这里就是指定请求API的方法了,常用的get、post等等。如图所示,就是用的get方法,括号里的url就是要请求的地址了。
这里要注意的是,如果在config里设置了基础url,那么步骤里的url就只能设置相对路径了。
- with_params
这个就简单了,测接口不都得要传参么,对于params类型的传参,就放这就行了,key-value键值对的形式。对于body形式的传参,看后面。
- with_headers
同样,有header要带上的就放这里。
- with_cookies
需要带cookie的,可以用.with_cookies方法。
- with_data
对于body类型的传参,可以用.with_data。
-
with_json
如果是json类型的body请求体,可以用.with_json。 -
extract 数据提取
with_jmespath(jmes_path:文字,var_name:文字)
mes_path:jmespath表达式,有关更多详细信息,请参考JMESPath教程/tutorial.html
var_name:存储提取值的变量名,可以在后续测试步骤中引用它
- validate 校验结果
使用jmespath提取 JSON 响应正文并使用预期值进行验证。
assert_XXX(jmes_path: Text, expected_value: Any, message: Text = "")
jmes_path:jmespath 表达式,更多细节参考JMESPath 教程
预期值:这里也可以使用指定的预期值、变量或函数引用
消息(可选):用于指示断言错误原因
6.4 teststeps -RunTestCase
以前我在写接口自动化用例的时候,为了保证用例的独立性,需要在setUp里调用各种满足用例的一些前置条件,其中就不乏调用了其他测试用例中的方法。
而httprunner也是支持了这一项很重要的特性,通过RunTestCase对其他测试用例进行调用,并且还可以导出用例中你所需要的变量,来满足后续用例的的运行。
首先还是来看下RunTestCase的用法,然后再用实例去实践。
teststeps = [
Step(
RunTestCase("request with functions")
.with_variables(
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
)
.call(TestCaseRequestWithTestCaseReference)
.export(*["foo3"])
),
Step(
RunRequest("post form data")
.with_variables(**{"foo1": "bar1"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo3")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "bar1")
.assert_equal("body.form.foo2", "bar21")
)
- RunTestCase(name)
这个参数呢还是一个名称,毕竟RunTestCase还是一个Step,这个名称同样会在日志和报告中显示。
- with_variables
这个变量跟RunRequest里的用法一样。
- export
可以指定要导出的变量,以供后续Step引用。
可以看的.export()内部是一个列表[],这里可以用来导出多个变量。
6.5 hook机制
对于使用过 Python结合Unittest 框架来做自动化测试的同学,应该知道在 Unittest 中,有这样2个方法:setUp() 和 tearDown() ,即前置和后置操作。通常 setUp() 主要用于测试准备工作,而 tearDown() 主要用于 测试后的数据清理。
在 HttpRunner 中,我们可以通过 hook机制 来实现 setUp() 和 tearDown()。
- setup_hook :主要用于处理接口的前置的准备工作
- teardown_hook:主要用于后置清理工作
6.6用例间调用call
指定你要引用的testcase类名称了。
七、testcase引用&变量传递
变量优先级:原则上config 变量和 step 变量名称尽量不要重复,当config和step中都用同一个变量时,step变量优先级大于config变量
- 首先,先修改/getUserName接口的case:
from httprunner import HttpRunner, Config, Step, RunRequest
class TestCaseRequestWithGetUserName(HttpRunner):
config = (
Config("test /getUserName")
.base_url("http://localhost:5000")
.verify(False)
.export(*["username"])#这里定义出要导出的变量
)
teststeps = [
Step(
RunRequest("getUserName")
.get("/getUserName")
.extract()
.with_jmespath("body.username", "username")#提取出目标值,赋值给username变量
.validate()
.assert_equal("body.username", "wesson")
),
]
if __name__ == "__main__":
TestCaseRequestWithGetUserName().test_start()
关注注释部分的文字,一个是config里定义了这个要导出的变量,另一个是在Step中,讲目标值提取出来,赋值给这个变量。
- 接下来,修改/joinStr接口的测试用例:
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from .get_user_name_test import TestCaseRequestWithGetUserName #记得要导入引用的类
class TestCaseRequestWithJoinStr(HttpRunner):
config = (
Config("test /joinStr")
.base_url("http://localhost:5000")
.verify(False)
)
teststeps = [
Step(
RunTestCase("setUp getUserName")
.call(TestCaseRequestWithGetUserName)#导入后就可以调用了
.export(*["username"])#在RunTestCase步骤中定义这个变量的导出
),
Step(
RunRequest("joinStr")
.get("/joinStr")
.with_params(**{"str1": "hello", "str2": "$username"})#在第二个传参中引用导出的变量
.validate()
.assert_equal("body.result", "hello $username")#断言的预期值也引用变量
),
]
if __name__ == "__main__":
TestCaseRequestWithJoinStr().test_start()
八、用例的参数化
HttpRunner3.x支持3中参数化规则
- 自己定义的变量数组
- debugtalk.py函数返回值中
- 外部文件中
先从官网介绍来一段代码:
# NOTE: Generated By HttpRunner v3.1.4
# FROM: request_methods/request_with_parameters.yml
import pytest
from httprunner import Parameters
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithParameters(HttpRunner):
@pytest.mark.parametrize(
"param",
Parameters(
{
"user_agent": ["iOS/10.1", "iOS/10.2"],
"username-password": "${parameterize(request_methods/account.csv)}",
"app_version": "${get_app_version()}",
}
),
)
def test_start(self, param):
super().test_start(param)
config = (
Config("request methods testcase: validate with parameters")
.variables(**{"app_version": "f1"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
Step(
RunRequest("get with params")
.with_variables(
**{
"foo1": "$username",
"foo2": "$password",
"sum_v": "${sum_two(1, $app_version)}",
}
)
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
.with_headers(**{"User-Agent": "$user_agent,$app_version"})
.extract()
.with_jmespath("body.args.foo2", "session_foo2")
.validate()
.assert_equal("status_code", 200)
.assert_string_equals("body.args.sum_v", "${sum_two(1, $app_version)}")
),
]
if __name__ == "__main__":
TestCaseRequestWithParameters().test_start()
文件中,先在需要参数化测试类名下定义参数化字段
@pytest.mark.parametrize(
"param",
Parameters(
{
"user_agent": ["iOS/10.1", "iOS/10.2"],
"username-password": "${parameterize(request_methods/account.csv)}",
"app_version": "${get_app_version()}",
}
),
)
重写父类的方法test_start,将参数化变量传入
def test_start(self, param):
super().test_start(param)
- 自定义数组变量
"user_agent": ["iOS/10.1", "iOS/10.2"],
将数组内的两个值分别付给了变量user_agent,此时用例引用变量时$user_agent,就会循环带入两个值执行两个测试用例
- debugtalk.py函数的返回值
"app_version": "${get_app_version()}",
和自定义变量类似,把函数get_app_version()的返回值赋值给了变量app_version,用例执行时,就会带入函数返回值,返回值一般是列表或者元组类型的。容器内有几个值,就会执行几次用例,用函数返回值作用参数化变量的好处是,可以动态计算参数,留给使用者更多的可能性
- 外部文件
"username-password": "${parameterize(request_methods/account.csv)}"
外部文件中定义的参数通过${parameterize(file/path)}固定写法读取。以上的例子中每一次要参数化的值实际上是多个,将每一行读取出来的变量分别赋值给了username和password,文件有多少行,就会执行几遍用例
- 独立参数和组合参数
在上面的例子中user_agent和app_version都是独立参数,每个遍历出来值只有一个,而username-password则是组合参数,每次遍历出来是一组值,分别赋值给对应变量。
如果参数在用例中是单独定义的,没有出现组合的情况,那么遍历出来几个值,用例就会执行几次。
而如果不同的参数需要组合使用,但是又各自定义了参数化,那么此时用例执行的次数呈现笛卡尔积的,假设上面的例子中user_agent有2个值,app_version 有3个值,username-password也3组值,组合在一起,用例就会累计执行** 2x3x3 **次,也许这并不是我们想要的。所以设计好我们的参数化组合在HttpRunner接口参数化实践中是非常重要的。
九、运行testcase的几种方式
9.1、运行testcase的几种场景
- 运行单个case
通常单个case的话我会在编辑器里用main方法运行,不过也可以用命令行运行,看你喜欢。
main方法里在类的后面调用test_start()方法即可。
if __name__ == "__main__":
TestCaseRequestWithGetUserName().test_start()#这里
命令行的话,就是直接在hrun后面加上case的路径,就可以运行了。
hrun httprunner_demo\testcases\get_user_name_test.py
如果是用命令行的run命令,那么就是通过pytest来调用的;如果是用代码里的test_start()方法,那么就是调requests作者自创的。
test_start()是主程序入口,符合pytest规则,会被pytest发现。所以单个执行能使用pytest的conftest文件里的公共方法以及使用pytest的fixture。
- 运行多个case
也可以选择运行多个case,hrun后面多个路径之间用空格隔开。
- 运行整个case文件夹
通常在testcases这个目录下会存放我们的测试用例,那么也可以直接运行这个目录的路径,来运行下面所有的case。
hrun httprunner_demo\testcases\
9.2、运行YAML/JSON文件格式的case
当你转换har文件时候,同级文件夹下,就会生成对应的pytest的文件,文件名称的末尾会有_test。
如果运行YAML/JSON文件,其实httprunner会先把它们转换为pytest格式的,再去运行。
所以,用httprunner 3.x版本的话,写case的话还是直接用pytest格式吧。
9.3、 pytest 脚本
对应pytest文件来说,用hrun或者pytest命令运行都是可以的。
因为hrun封装了pytest,所以pytest运行的所有参数,同样可以在hrun后面加。
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseTestLogin(HttpRunner):
config = Config("logincase").base_url("http://127.0.0.1:8000").export(*["token"])
teststeps = [
Step(
RunRequest("steplogin")
.with_variables(**{"user": "test1", "psw": "123456"})
.post("/api/v1/login")
.with_json({"username": "$user", "password": "$psw"})
.extract()
.with_jmespath("body.token", "token")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.code", 0)
.assert_equal("body.msg", "login success!")
.assert_length_equal("body.token", 40)
),
]
if __name__ == "__main__":
TestCaseTestLogin().test_start()
命令行使用pytest -s运行
pytest介绍
一、前言
大家都知道Python有自带的单元测试框架unittest,那为什么还要学习Pytest呢?先了解下Pytest优点
1.1pytest优点:
pytest是一个非常成熟的全功能的Python测试框架,是unittest框架的扩展,主要特点有以下几点:
-
简单灵活,非常方便的组织自动化测试用例;
-
支持参数化,可以细粒度地控制要测试的测试用例;
-
能够支持简单的单元测试和复杂的功能测试,比如web端selenium/移动端appnium等自动化测试、request接口自动化测试
-
pytest具有很多第三方插件,并且可以自定义扩展,比如测试报告生成,失败重运行机制
-
测试用例的skip和fail处理;
-
结合业界最美的测试报告allure+Jenkins,持续集成
1.2编写规则
编写pytest测试样例非常简单,只需要按照下面的规则:
- 测试文件以test_开头(以_test结尾也可以)
- 测试类以Test开头,并且不能带有 init 方法
- 测试函数以test_开头
- 断言使用基本的assert即可
1.3、pytest插件
pytest有很多插件,在实际使用中很实用,常用的如下:
1)pytest-html:可以用于生成html格式的自动化报告
2)pytest-xdist:用于测试用例分布式执行,当有很多用例时,可以分布式协调测试资源,这需要要求用例之间独立没有依赖关系,pytest -n 2相当于用2个cpu执行
3)pytest-ordering:可以用于改变测试用例执行顺序,用法@pytest.mark.run(order=1)
4)pytest-rerunfailures:用于用例失败后重新执行,安装直接命令行pip install pytest-rerunfailures,使用如,pytest test_01.py --reruns 10 --reruns-delay 1 -vs (即失败后执行10次,每次间隔1秒)
5)aliiure-pytest:用于生成测试报告
6)pytest-assume:当一条用例有多个断言时,使用这个插件可以在前面断言失败后,可以继续执行下面的断言,安装(pip install pytest-assume)
7)pytest-dependency:用例依赖,用于标记一个用例作为其他用例的依赖,当这个用例执行失败时,其他用例也会跳过不执行
1.4、环境搭建
pip install -U pytest #U表示升级
pip install pytest-xdist #多线程
pip install -U pytest-rerunfailures #重试运行cases
pip install pytest-html #生成测试报告
pip install pytest-ordering
二、Pytest 运行参数
2.1结果展示
-
-s, --capture=method显示标准输出
默认情况下你在用例中写的print或者log输出,都不会在测试结果中展示。如果你想看到用例中的标准输出,那么需要加上-s参数。 -
-v, --verbose更详细的结果
使用-v会让输出结果更详细,不用的时候一个文件占一行,而用例之后一个用例占结果的一行。并且结果中的用例名称和结果都会展示,而不仅仅是一个.或字符。 -
-q, --quiet简略的结果
与-v相反,-q会输出更简化的信息。 -
-l, --showlocals显示变量
使用-l会展示运行过程中的全局变量和局部变量。
2.2查找用例
- 模块或目录
pytest 除了参数以外,在 pytest 后面没有跟任何参数的字符串都会认为是文件或者目录,会去这些指定的目录或文件中查找用例。
pytest testcases test_login.py ../flow/test_flow.py
这里 pytest 会去查找当前目录下的 testcases 子目录,当前目录下的test_login.py,上级目录下的 flow 目录中的 test_flow.py。会找到所有这些目录或文件中符合条件的测试用例。你可以使用下面的–collect-only来检验一下。
- collect-only
此参数展示当前 pytest 能找到的用例,也就是说直接根据当前参数配置,能找到的用例列表。用于检验运行的用例是否符合你的预期。
注意,这里只是列出会运行的用例,但是不会实际运行。
- -k 筛选用例
-k可以让你使用表达式来指定希望运行的测试用例。可以用 and,or,not 连接符,模糊匹配文件名、类名和函数名。
pytest -k "str or Add and not list" --collect-only
匹配名称包含 str 或者 Add 且 不包含 list 的文件、类、及函数。如果用上面的例子,这里就只会找到两个函数:
- -m 标记
标记markers用于标记测试并分组,以便快速选中某些需要的用例并运行。比如我们在冒烟测试时运行其中某一部分用例,bvt 测试时运行另一部分用例。可以通过@pytest.mark.标记名来标记这些需要的用例,运行时用-m 标记就能快速选出这部分用例运行。
@pytest.mark.bvt # => 给函数加上标记
def test_int():
"""测试整型"""
a, b = 1, 2
assert add(a, b) == 3
运行:
pytest -m bvt
标记名可以自己随意加,但是加上后会有警告信息。可以使用 pytest 的配置文件配置这些标记。在测试目录下新建一个pytest.ini,这是一个文本文件,并加入如下内容,可以避免告警。
[pytest]
markers=smoke:冒烟测试用例
bvt: bvt 测试用例
online: 正式环境运行的用例
pytest.ini的内容构成:
- 配置项markers:它的作用是做注册标记,防止拼写错误。比如把@pytest.mark.smoke拼成@pytest.mark.somke,默认情况下。这不会引起程序错误。pytest会以为这是你创建的另一个标记。为了避免拼写错误。可以在pytest.ini文件里注册标记
- 配置项testpaths:estpaths指示pytest去哪里访问。testpaths是一系列相对于根目录的路径,用于限定测试用例的搜索范围。只有在pytest未指定文件目录参数或测试用例标识符时,该选项才有作用
- 配置项addopts:pytest用命令行运行时,有时候需要经常要用到某些参数,又不想重复输入,这时可以使用pytest.ini文件里的addopts设置
2.3 控制运行过程
Pytest 自带的这些控制运行命令可能并不常用,而最常用的应该是由失败重试插件提供的遇到用例失败重试的命令。
失败重试需要插件: pip install pytest-rerunfailures
pytest --reruns=3 # 用例失败重新运行3次
以下是 pytest 自带的运行控制命令:
-
-x, --exitfirst 遇到失败停止
正常情况下,一个用例失败后,会继续运行后续用例 ,直到所有用例运行完为止。-x 参数会让整个测试在遇到第一次失败就停止。这取决于你有没有这样的需要,一般来说可能用不上。 -
–maxfail=num 遇到多少次失败就停止
与上面-x一样,不过-x是遇到第一次失败就停止。而–maxfail可以指定遇到几个测试用例失败就停止。
pytest --maxfail=3 # 遇到三次用例失败就停止
-
-lf, --last-failed
当一个或多个用例失败后,定位到最后一个失败的用例重新运行,后续用例会停止运行。 -
-ff, --failed-first
与-lf查不多,失败后会定位最后一个失败的用例重新弄运行,但是会运行完剩余的用例。
2.4测试报告
需要直观的 html 测试报告,那么需要使用插件pytest-html,你可以通过 pip install pytest-html安装 。
然后在命令中加上–html=path:
pytest --html=report.html # 在当前目录生成名为report.html的测试报告
此报告和allure报告不是同一个,
三、pytest用例的运行方式
主要三种:主函数模式、命令行模式、读取pytest.ini配置文件
3.1主函数模式
运行所有模块:pytest.main()
运行指定模块:pytest.main([‘-vs’,‘./test_001.py’])
运行指定目录:pytest.main([‘-vs’, ‘./test’])
3.2命令行模式:即在命令行中运行
运行所有模块:直接在命令行窗口输入pytest
运行指定模块:pytest -vs test_001.py
运行指定目录:pytest -vs test(需要cd切换至当前目录下)
3.3读取pytest.ini配置文件:
放在项目根目录里面,编码格式是ANSI
pytest执行用例的顺序:
unittes框架 :是根据ASCII的大小来决定执行的顺序
pytest框架:默认从上到下
运行:
pytest -m bvt
四、mark标记
4.1mark中的skip(跳过)
4.2mark中的xfail(失败)
pytest.xfail()
咱们已经掌握了若是跳过执行测试用例,其中有一种方法是在测试函数中用pytest.skip()方法。咱们如今要学的pytest.xfail()和pytest.skip()有些类似,只不过他的含义是:将该用例标记成xfail失败,而且该用例中的后续代码不会执行。github
老规矩,上荔枝:咱们在测试用例中调用pytes.xfail()方法,能够选择传入reason参数表示缘由。
#test_Pytest.py文件
#coding=utf-8
import pytest
class Test_Pytest():
def test_one(self,):
print("----start------")
pytest.xfail(reason='该功能还没有完成')
print("test_one方法执行" )
assert 1==1
def test_two(self):
print("test_two方法执行" )
assert "o" in "love"
def test_three(self):
print("test_three方法执行" )
assert 3-2==1
if __name__=="__main__":
pytest.main(['-s','-r','test_Pytest.py','test_Pytest.py'])
运行结果以下图:咱们能够看到该用例中pytest.xfail()方法以前的代码运行了,以后的再也不运行;结果中有一条用例被标记为xfail。
@pytest.mark.xfail
除了上面学习的pytest.xfail(),xfai还有一种使用方法。就是@pytest.mark.xfail标签,他的含义是指望测试用例是失败的,可是不会影响测试用例的的执行。若是测试用例执行失败的则结果是xfail(不会额外显示出错误信息);若是测试用例执行成功的,则结果是xpassed。
吃个荔枝:咱们直接在测试用例上加上@pytest.mark.xfail标签
#test_Pytest.py文件
#coding=utf-8
import pytest
class Test_Pytest():
@pytest.mark.xfail
def test_one(self):
print("test_one方法执行" )
assert 1==2
def test_two(self):
print("test_two方法执行" )
assert "o" in "love"
def test_three(self):
print("test_three方法执行" )
assert 3-2==1
if __name__=="__main__":
pytest.main(['-s','test_Pytest.py'])
运行结果以下图:能够看到咱们标记的用例确实运行了;由于断言失败因此结果是xfailed,也没有像正常同那样显示出错误用例及具体信息。
咱们把断言改为正确的,再运行一次,结果以下图:尽管咱们的用例正常运行经过,可是仍被标记为xpassed,而不是passed。
4.3使用自定义标记mark只执行部分用例
import pytest
@pytest.mark.webtest
def test_send_http():
pass # perform some webtest test for your app
def test_something_quick():
pass
def test_another():
pass
class TestClass:
def test_method(self):
pass
if __name__ == "__main__":
pytest.main(["-s", "test_server.py", "-m=webtest"])
#只运行用webtest标记的测试,cmd运行的时候,加个-m 参数,指定参数值webtest
#pytest -v -m webtest
#若是不想执行标记webtest的用例,那就用”not webtest”
#pytest -v -m “not webtest”
五、第三方插件
5.1调整测试用例的执行顺序
pytest默认按字母顺序去执行的
5.2 执行用例遇到错误中止
5.3执行用例失败后从新运行
5.4多条断言前面报错后面依然执行
5.5多线程并行与分布式执行
5.6pytest-html生成报告
- 报告独立显示
1.上面方法生成的报告,css是独立的,分享报告的时候样式会丢失,为了更好的分享发邮件展现报告,能够把css样式合并到html里
$ pytest --html=report.html --self-contained-html
六、conftest配置
当自动化用例越来越庞大的时候,很多用例的数据可以共享,复用,让用例脚本可读性,维护性更高,比如登录等
conftest.py 配置里可以实现数据共享,比如py跨文件共享前置
conftest.py配置脚本名称是固定的,不能改名称
不需要import导入 conftest.py,pytest用例会自动查找
七、Pytest -fixture
7.1fixture前言
首先咱们看一下框架中对于fixture函数的定义:
scope即是定义用例域的范围:
function:默认范围,每个函数或方法都会调用,不填写时即是它
class:每个类调用一次
module: 每个.py文件调用一次,文件中能够有多个function和class
session:多个文件调用一次,能够跨文件,如在.py文件中,每个.py文件就是module
范围:
session > module > class > function
因此在调用时各个fixture之间并不会相互冲突。
7.2 实例
pytest 相较于 unittest 最为跳跃的一点应该就是 fixture 机制
对于unittest来讲,每一个用例的类中都须要去写入setUp和tearDown。也就是咱们所说的前置和后置,
而不可避免的,不少用例的前置和后置都是同样(例如不少用例都须要前置登陆,后置退出),因而咱们须要重复的复制粘贴,这样致使工做量增长,代码量也增长,界面也显得冗杂。
因此此时pytest中fixture机制便要闪亮登场了。
通俗的讲: fixture = 前置+后置
而方便的是:若是不少用例都有一样的前置和后置,那么我就只实现一个,而后须要的用例就去调用就行了。
- 1.机制:与测试用例同级,或者是测试用例的父级,建立一个conftest.py文件。
- 2.conftest.py文件里:放全部的前置和后置。 不须要用例.py文件主动引入conftest文件。
- 3.定义一个函数:包含前置操做+后置操做。
- 4.把函数声明为fixture :在函数前面加上 @pytest.fixture(做用级别=默认为function)
- 5.fixture的定义。
若是有返回值,那么写在yield后面。(yield的做用就至关于return)
在测试用例当中,调用有返回值的fixture函数时,函数名称就是表明返回值。
在测试用例当中,函数名称做为用例的参数便可。
实例如下:
1.定义一个函数名叫open_url的fixture先后置,前置为打开连接,后置为退出浏览器
@pytest.fixture(scope=“class”) #定义scope的范围
def open_url():
# 前置
driver = webdriver.Chrome()
driver.get(url) #url为连接地址
yield driver #yield以前代码是前置,以后的代码就是后置。
# 后置
driver.quit()
这样咱们就定义了一个叫作 open_url 的 fixture
2.在咱们要用这个后置的类前面 咱们用@pytest.mark.usefixtures(fixture函数名)
就能够直接调用上面定义好的这个
能够看到 在TestLogin 这个类中 咱们再也不去编写setup 和 teardown. 直接写咱们的中间过程就能够了
7.3fixture的自动应用autouse
日常写自动化用例会写一些前置的fixture操做,用例须要用到就直接传该函数的参数名称就好了。当用例不少的时候,每次都传这个参数,会比较麻烦。
fixture里面有个参数autouse,默认是Fasle没开启的,能够设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了
设置autouse=True,autouse设置为True,自动调用fixture功能,autouse遵循scope="关键字参数"规则:当scope="session"时,不管怎样定义只运行一次;当scope="module"时,每一个py文件只运行一次;当scope="class"时,每一个class只运行一次(可是一个文件中包括function和class时,会在每一个function(不在class中)运行一次);当scope="function"时,每一个function运行一次;
import pytest
@pytest.fixture(scope="module",autouse=True)
def start(request):
print("\n----开始执行module------")
print('module : %s'% request.module.__name__)
print('------启动浏览器-------')
yield
print("------结束测试 end!----------")
@pytest.fixture(scope="function",autouse=True)
def open_home(request):
print("function:%s \n--回到首页--"% request.function.__name__)
def test_01():
print('----用例01-----')
def test_02():
print('----用例02-----')
if __name__ == '__main__':
pytest.main(["-s","autouse.py"])
执行结果
----开始执行module------
module : autouse
------启动浏览器-------
function:test_01
--回到首页--
.----用例01-----
function:test_02
--回到首页--
.----用例02-----
------结束测试 end!----------
八、pytest(8)-参数化
什么是参数化,通俗点理解就是,定义一个测试类或测试函数,可以传入不同测试用例对应的参数,从而执行多个测试用例。
例如对登录接口进行测试,假设有3条用例:正确账号正确密码登录、正确账号错误密码登录、错误账号正确密码登录,那么我们只需要定义一个登陆测试函数test_login(),然后使用这3条用例对应的参数去调用test_login()即可。
在unittest中可以使用ddt进行参数化,而pytest中也提供非常方便的参数化方式,即使用装饰器@pytest.mark.parametrize()。
一般写为pytest.mark.parametrize(“argnames”, argvalues),其中:
- argnames为参数名称,可以是单个或多个,多个写法为"argname1, argname2, …"
- argvalues为参数值,类型必须为list(单个参数时可以为元组,多个参数时必须为list,所以最好统一)
8.1单个参数
只需要传入一个参数时,示例如下:
# 待测试函数
def sum(a):
return a+1
# 单个参数
data = [1, 2, 3, 4]
@pytest.mark.parametrize("item", data)
def test_add(item):
actual = sum(item)
print("\n{}".format(actual))
# assert actual == 3
if __name__ == '__main__':
pytest.main()
注意,@pytest.mark.parametrize()中的第一个参数,必须以字符串的形式来标识测试函数的入参,如上述示例中,定义的测试函数test_login()中传入的参数名为item,那么@pytest.mark.parametrize()的第一个参数则为"item"。
运行结果如下:
从结果我们可以看到,测试函数分别传入了data中的参数,总共执行了4次。
8.2多个参数
测试用例需传入多个参数时,@pytest.mark.parametrize()的第一个参数同样是字符串, 对应用例的多个参数用逗号分隔。
import pytest
import requests
import json
# 列表嵌套元组
data = [("lilei", "123456"), ("hanmeimei", "888888")]
# 列表嵌套列表
# data = [["lilei", "123456"], ["hanmeimei", "888888"]]
@pytest.mark.parametrize("username, password", data)
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
if __name__ == '__main__':
pytest.main()
这里需要注意:
- 代码中data的格式,可以是列表嵌套列表,也可以是列表嵌套元组,列表中的每个列表或元组代表一组独立的请求参数。
- "username, password"不能写成 “username”, “password”。
运行结果如下:
从结果中我们还可以看到每次执行传入的参数,如下划线所示部分。
这里所举示例是2个参数,传入3个或更多参数时,写法也同样如此,一定要注意它们之间一一对应的关系,如下图:
九、多线程和多进程
9.1前⾔
1、单CPU中进程只能是并发,多CPU计算机中进程可以并行。
2、单CPU单核中线程只能并发,单CPU多核中线程可以并行。
3、无论是并发还是并行,使用者来看,看到的是多进程,多线程。
平常我们功能测试⽤例⾮常多时,⽐如有1千条⽤例,假设每个⽤例执⾏需要1分钟,如果单个测试⼈员执⾏需要1000分钟才能跑完
当项⽬⾮常紧急时,会需要协调多个测试资源来把任务分成两部分,于是执⾏时间缩短⼀半,如果有10个⼩伙伴,那么执⾏时间就会变成⼗分之⼀,⼤⼤节省了测试时间
为了节省项⽬测试时间,10个测试同时并⾏测试,这就是⼀种分布式场景
分布式执⾏⽤例的原则:
- ⽤例之间是独⽴的,没有依赖关系,完全可以独⽴运⾏
- ⽤例执⾏没有顺序要求,随机顺序都能正常执⾏
- 每个⽤例都能重复运⾏,运⾏结果不会影响其他⽤例
为了解决这个问题,我们采⽤pytest的插件pytest-xdist来进⾏多进程的并发执⾏测试⽤例,⼤⼤的缩短测试⽤例的执⾏时间,提⾼效率。
并发运⾏测试⽤例:
- 1、安装pytest-xdist
pip install pytest-xdist
- 2、多进程并发执⾏测试⽤例:不⽀持多线程,且一个CPU核只能一个进程,超过核数实际是串行
pytest test_add.py -n NUM # NUM表⽰并发的进程数
参数配置:
-n=* :*代表进程数
- ①多cpu并⾏执⾏⽤例,直接加个-n参数即可,后⾯num参数就是并⾏数量,⽐如num设置为3
- ② -n auto :⾃动侦测系统⾥的CPU数⽬
- ③ -n num :指定运⾏测试的处理器进程数
3、举例:
项⽬结构如下:
# file_name: test_a.py
import pytest
import time
def test_a_01():
print("----------------->>> test_a_01")
time.sleep(1)
assert 1
def test_a_02():
print("----------------->>> test_a_02")
time.sleep(1)
assert 1
def test_a_03():
print("----------------->>> test_a_03")
time.sleep(1)
assert 1
def test_a_04():
print("----------------->>> test_a_04")
time.sleep(1)
assert 1
if__name__ == '__main__':
pytest.main(["-s", "test_a.py"])
# file_name: test_b.py
import pytest
import time
def test_b_01():
print("----------------->>> test_b_01")
time.sleep(1)
assert 1
def test_b_02():
print("----------------->>> test_b_02")
time.sleep(1)
assert 1
def test_b_03():
print("----------------->>> test_b_03")
time.sleep(1)
assert 1
def test_b_04():
print("----------------->>> test_b_04")
time.sleep(1)
assert 1
if__name__ == '__main__':
pytest.main(["-s", "test_b.py"])
①正常运⾏以上代码,耗时:8.09s
②设置并⾏运⾏数量为4,耗时:3.48s,⼤⼤的缩短了测试⽤例的执⾏时间。
9.2pytest-xdist分布式测试的原理
前⾔
1、xdist的分布式类似于⼀主多从的结构,master机负责下发命令,控制slave机;slave机根据master机的命令执⾏特定测试任务。
2、在xdist中,主是master,从是workers。
⼤致原理
1、xdist会产⽣⼀个或多个workers,workers都通过master来控制。
2、每个worker负责执⾏完整的测试⽤例集,然后按照master的要求运⾏测试,⽽master机不执⾏测试任务。
9.3pytest实现多线程运⾏测试⽤例(pytest-parallel)
- 安装
pip install pytest-parallel
常⽤参数配置
- ① --workers=n :多进程运⾏需要加此参数,n是进程数。默认为1
- ② --tests-per-worker=n :多线程需要添加此参数,n是线程数
如果两个参数都配置了,就是进程并⾏;每个进程最多n个线程,总线程数:进程数*线程数
【注意】 - ①在windows上进程数永远为1。
- ②需要使⽤if name == “main” :,在dos中运⾏会报错(即在命令⾏窗⼝运⾏测试⽤例会报错)
⽰例:
pytest test.py --workers 3 :3个进程运⾏
pytest test.py --tests-per-worker 4 :4个线程运⾏
pytest test.py --workers 2 --tests-per-worker 4 :2个进程并⾏,且每个进程最多4个线程运⾏,即总共最多8个线程运⾏。
import pytest
def test_03():
print('测试⽤例3操作')
def test_04():
print('测试⽤例4操作')
if__name__ == "__main__":
pytest.main(["-s", "test_b.py", '--workers=2', '--tests-per-worker=4'])
9.4pytest-parallel与pytest-xdist对⽐说明
- ① pytest-parallel ⽐ pytst-xdist 相对好⽤,功能⽀持多。
- ② pytst-xdist 不⽀持多线程;
- ③pytest-parallel ⽀持python3.6及以上版本,所以如果想做多进程并发在linux或者mac上做,在Windows上不起作⽤(Workers=1),如果做多线程linux/mac/windows平台都⽀
持,进程数为workers的值。
allure
一、环境准备
首先是要安装好jdk的电脑上,运行java、javac这些命令都没有问题,要不安装allure时会报错
二、下载allure
如果直接用Jenkins上的插件,并不需要下载安装
如果本地运行,需要下载才能使用allure,要不然会生产乱码,也不会打开浏览器
下载地址:https://github.com/allure-framework/allure2/releases/tag/2.13.7
三、添加path环境变量
打开\allure-2.8.0\bin文件夹,会看到allure.bat文件,讲此路径设置为系统环境变量path下,这样cmd任意目录都能执行了
- 校验是否安装成功
在cmd中运行 allure命令,
allure -version
四、与pytest的集成
需要安装一个叫做 pytest-allure的插件库
pip install allure-pytest
该命令将安装 allure-pytest 包与 allure-python-commons 两个第三方包。
4.1常用命令:
- 用以生成json或xml的Allure报告
pytest执行测试用例并将执行后的每个测试用例的结果存放于指定的文件夹中,生成一个个的json或者xml文件。【其中,一个文件代表一个测试用例执行结果】
pytest --alluredir=./report/tmp # --alluredir表示指定测试报告数据的生成路径
执行完成上述命令后,会在当前目录下,report目录下生成一个result目录文件;
result下只是一些测试报告的原始数据,还不能作为html报告打开。
- allure将测试报告的原始数据生成测试报告。
allure generate report/tmp -o report/allure-report -c report/allure-report
report/tmp:每个用例的执行结果生成的每个json文件存放的位置【allure最终会将这些json文件渲染成网页结果】
-o report/allure-report:allure报告生成的位置【指定目录生成测试报告】
-c report/allure-report:新的allure报告生成之前先把先前的allure报告清理掉
【注意:这里只是在指定的文件夹生成allure测试报告,并未在本机开启一个allure服务】
- 打开生成的报告,本地查看。【打开allure报告生成的位置本地查看allure报告】
allure open report/allure-report
参数:-h, (–host):指定域名地址;
参数:-p, (–port):指定端口号;
- 打开生成的报告,可对外提供在线展示。【在本机开启一个allure服务可以远程查看allure报告】
allure serve report/allure-report
参数:-h, (–host):指定域名地址;
参数:-p, (–port):指定端口号;
五、allure标记描述
报告如图:
代码实例:
# coding:utf-8
import pytest
import allure
# 测试函数
@allure.step("字符串相加:{0},{1}") # 测试步骤,可通过format机制自动获取函数参数
def str_add(str1, str2):
print("hello")
if not isinstance(str1, str):
return "%s is not a string" % str1
if not isinstance(str2, str):
return "%s is not a string" % str2
return str1 + str2
# @llure.environment(host="172.6.12.27", test_vars=paras)
@allure.severity("critical") # 优先级,包含blocker, critical, normal, minor, trivial 几个不同的等级
@allure.feature("测试模块_demo1") # 功能块,feature功能分块时比story大,即同时存在feature和story时,feature为父节点
@allure.story("测试模块_demo2") # 功能块,具有相同feature或story的用例将规整到相同模块下,执行时可用于筛选
@allure.issue("BUG号:123") # 问题表识,关联标识已有的问题,可为一个url链接地址
@allure.testcase("用例名:测试字符串相等") # 用例标识,关联标识用例,可为一个url链接地址
@pytest.mark.parametrize("para_one, para_two", # 用例参数
[("hello world", "hello world"), # 用例参数的参数化数据
(4, 4),
("中文", "中文")],
ids=["test ASCII string", # 对应用例参数化数据的用例名
"test digital string",
"test unicode string"])
def test_case_example(para_one, para_two):
"""用例描述:测试字符串相等
:param para_one: 参数1
:param para_two: 参数2
"""
# 获取参数
paras = vars()
# 报告中的环境参数,可用于必要环境参数的说明,相同的参数以后者为准
allure.environment(host="172.6.12.27", test_vars=paras)
# 关联的资料信息, 可在报告中记录保存必要的相关信息
allure.attach("用例参数", "{0}".format(paras))
# 调用测试函数
res = str_add(para_one, para_two)
# 对必要的测试中间结果数据做备份
allure.attach("str_add返回结果", "{0}".format(res))
# 测试步骤,对必要的测试过程加以说明
with pytest.allure.step("测试步骤2,结果校验 {0} == {1}".format(res, para_one + para_two)):
assert res == para_one + para_two, res
if __name__ == '__main__':
# 执行,指定执行测试模块_demo1, 测试模块_demo2两个模块,同时指定执行的用例优先级为critical,blocker
pytest.main(['--allure_stories=测试模块_demo1, 测试模块_demo2', '--allure_severities=critical, blocker'])
六、与jenkins集成
https://www.cnblogs.com/Simple-Small/p/11512337.html
整理了一天,时间有点长,大家觉得可以的话记得收藏。