Pytest介绍
pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:简单灵活、容易上手、文档丰富;支持参数化,可以细粒度地控制被测用例;能够支持简单的单元测试和复杂的功能测试,还可以用来做Web自动化(selenium)、APP自动化(appium)以及接口自动化(pytest+requests);pytest具有很多第三方插件,并且可以自动以扩展,比较好用的如pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败用例重复执行)、pytest-xdist(多CPU分发)等;测试用例的skip和xfail处理;可以很好的和CI工具结合,如:Jenkins
Pytest安装
直接在命令窗口执行以下命令即可,更多信息可查阅官方文档(https://docs.pytest.org/en/latest/index.html)
-
pip install pytest # 安装pytest
-
pytest --version # 查看pytest版本
直接在PyCharm中安装,下面提到的使用pip安装方式都可以直接在PyCharm中安装,安装成功后点击【OK】
PyCharm中设置使用pytest
由于PyCharm默认使用Unitest,所以安装pytest后需要更改默认值,配置路径如下
配置路径:依次点击【File】→【Settings】→【Tools】→【Python Integrated Tools】,【Default test runner】选择“pytest”,然后点击【Apply】→【OK】,就可以使用Pytest啦
Pytest使用方法
Pytest规则
编写pytest测试用例默认使用以下规则,当然,规则是可以修改的
-
测试文件以
test_
开头或者以_test
结尾 -
测试类以
Test
开头,且类中不能包含__init__
方法 -
测试函数/方法以
test_
开头 -
断言使用基本的
assert
即可
修改规则需添加配置文件pytest.ini
,它是pytest的主配置文件,可以修改pytest的默认行为,此文件放在项目的根目录下才会生效(除非指定目录查找配置文件),如下配置,注意:实际配置文件中不可出现中文、冒号、引号等
-
[pytest]
-
python_files = test_*.py *_test.py case_* # 添加以case_*开头的测试模块
-
python_classes = Test* *_case* # 添加以*_case结尾的测试类
-
python_functions = test_* case_* # 添加以case_*开头的测试函数
-
testpaths = testcases # 指定测试目录,是指以testpaths所在目录为基准的相对路径
-
addopts = -vs -l # 将常用命令参数设为默认,省去重复输入的工作
-
norecursedirs = .* build dist CVS _darcs {arch} *.egg src # 指定pytest忽略查找某些目录,多个目录之间空格隔开
-
log_cli = True # 控制台实时输出日志
-
# 注册mark标记
-
markers =
-
smoke : smoke tests as smoke
-
output : print
Pytest标记
上面的配置文件中提到了标记,又如何使用呢?
默认情况下,pytest会递归查找当前目录下所有以test开头或结尾的Python脚本,并执行文件内所有以test开头的函数和方法。工作中由于功能尚未实现、挑选用例执行冒烟测试等原因,只想测试指定用例,在pytest中有几种方法可解决
-
显式指定函数名,通过
::
标记,例如:某文件内存在两个类,但一个类尚未完成,只想执行第二个类中的用例,就可指定类执行-
pytest -vs test_learn.py::Testpy2 # 只执行文件中的Testpy2类
-
pytest -vs test_learn.py::Testpy2::test_method # 只执行文件中Testpy2类下的test_method方法
-
-
使用模糊匹配,通过
-k
参数识别关键字,例如:pytest -k login test_learn.py # 只执行文件中包含关键字login的用例
-
使用pytest.mark在用例上进行标记,标记需要在
pytest.ini
文件中配置,配置方法见上文,格式一定要写对,若未配置也可执行,但是会出现警告,配置中冒号前是标记名,冒号后可以理解为标记解释,当然也可以前后一样-
@pytest.mark.check # 在函数上设置标记
-
def test_func1(self):
-
assert 1 == 1
-
@pytest.mark.output # 在类上面设置标记,对类中所有方法都有效
-
class caseCls(object):
-
def dyd_case(self):
-
@pytest.mark.smoke
-
def test_func1(self): # 在类中的方法上设置标记
-
print("这是Pytest")
执行带有标记的函数,使用
-m
参数-
pytest -m check test_learn.py # 执行带有check标记的用例,此标记在一个函数上标记了一次,所有只会执行一条用例
-
pytest -m smoke test_learn.py # 执行带有smoke标记的用例,此标记在一个方法上标记了一次,同样只会执行一条用例
-
pytest -m output test_learn.py # 执行带有output标记的用例,因为用例在类上,故类中所有的用例都会被执行
-
Pytest运行
-
通过命令行运行
-
pytest -vs /package_name # 执行包中所有模块的用例
-
pytest -vs file_name.py # 执行单独的pytest模块,file_name.py文件
-
pytest -vs file_name.py::class_name # 只执行文件中的class_name类
-
pytest -vs file_name.py::class_name::method_name # 只执行文件中class_name类下的method_name方法
-
pytest -vs file_name.py::function_name # 只执行文件中function_name函数
pytest命令参数
-
-v
展示每个测试函数的执行结果(详细信息) -
-q
只显示整体测试结果(简要信息) -
-s
展示测试函数中print()函数输出信息 -
-k
只执行包含关键字的用例 -
-m
只执行指定标记的用例 -
-x
出现失败用例则立即停止执行 -
-l
用例失败时打印相关局部变量 -
-c
从指定目录加载配置文件,而非自动查找配置文件 -
-lf
只执行上次失败的用例,若没有则执行全部用例 -
-ff
先执行完上次失败的再执行剩余的用例 -
-tb=style
用例失败时错误的详细程度(auto/long/short/line/native/no) -
--maxfail=num
用例允许失败的最大次数,超出则立即停止执行 -
--collect-only
收集但不执行用例 -
--durations=num -vv
显示设定数值内,按照耗时时长降序打印结果,通常用于调优 -
-h
,--help
帮助
-
-
通过main方法
-
if __name__ == '__main__':
-
pytest.main(["-sv", "file_name.py"]) # 通过main函数执行
-
示例:
-
# 文件名称命名规则,以test_开头或_test结尾
-
import pytest
-
def test_func(): # 通常在类外面称为函数,函数命名规则,以test_开头
-
assert 1 == 1
-
class Testpy(object): # 类命名规则,以Test开头
-
def test_method(self): # 通常在类中称为方法,方法命名规则与函数一样,以test_开头
-
assert 1 + 2 == 3
-
@pytest.mark.smoke
-
def test_func1(self):
-
print("这是Pytest")
-
@pytest.mark.smoke
-
class caseCls(object):
-
def dyd_case(self):
-
print("修改函数后的规则后")
-
if __name__ == '__main__':
-
pytest.main(["test_learn.py"]) # 通过main函数执行命令
使用命令执行示例中的用例
-
pytest ../Pytest # 执行包中所有模块的用例
-
pytest -vs test_learn.py # 执行单独的pytest模块,test_learn.py文件
-
pytest -vs test_learn.py::Testpy # 只执行文件中的Testpy类
-
pytest -vs test_learn.py::Testpy::test_method # 只执行文件中Testpy类下的test_method方法
-
pytest -vs test_learn.py::test_function # 只执行文件中test_function函数
-
pytest -m smoke test_learn.py # 执行带有smoke标记的用例
-
pytest -k dyd test_learn.py # 只执行包含关键字dyd的用例
-
pytest --maxfail=2 test_learn.py # 执行指定文件中的用例,失败用例超出2条则停止执行
-
pytest --collect-only test_learn.py # 收集指定文件中的用例,不加文件名则直接搜索当前目录下所有用例
Pytest参数化
在pytest中,可以使用参数化测试,即每组参数都独立执行一次
使用参数化装饰器pytest.mark.parametrize(argnames,argvalues)
,参数化的名字要与方法中的参数名一一对应,传递多个参数时可使用列表、元组、字典或列表嵌套等,实际工作中还是以json、yaml、excel等文件形式做参数化的较多,后续再介绍,示例如下:
-
import pytest # 导入pytest包
-
list = [1,"1+1","1+2"] # 列表
-
# ↓参数必须与值的个数相等,可使用ids参数重命名,名字方便理解其意思即可,ids的个数要等于传递的数据个数
-
@pytest.mark.parametrize("value",list,ids=["value=1","value=2","value=3"])
-
def test_list(value):
-
print(value) # 打印三个结果
-
tuple = [("abc"),("def"),["g","h","i"],[1,2,3]] # 元组
-
@pytest.mark.parametrize("x,y,z",tuple) # 参数必须与值的个数相等
-
def test_xzy(x,y,z):
-
print(x,y,z) # 打印四个结果
-
@pytest.mark.parametrize("input,expect",[("1+1",2),("2+2",4)])
-
def test_count(input,expect):
-
assert eval(input) ==expect # 估算实际结果是否与期望结果一致,打印两个结果
-
dict = ({"username":"dyd","passwd":123456},{"phone":18888888888,"age":18}) # 字典
-
@pytest.mark.parametrize("dic",dict)
-
def test_dict(dic):
-
print(dic) # 打印两个结果
-
data = [ # ↓通过pytest中的param定义参数,id是对参数的一个说明,可自定义,方便理解各用例的含义即可
-
pytest.param(100,100,200,id="a+b:pass"),
-
pytest.param("a","b","ab",id="a+b:pass"),
-
pytest.param(1,1,6,id="a+b:fail")]
-
def add(a,b): # 定义一个add相加的函数
-
return a+b
-
class TestParam(object):
-
@pytest.mark.parametrize("a,b,expect",data)
-
def test_param(self,a,b,expect):
-
assert add(a,b) == expect # 调用a+b,判断实际结果是否与期望结果一致,出现三个结果
-
@pytest.mark.parametrize("x",(1,2,3))
-
@pytest.mark.parametrize("y",(4,5,6))
-
def test_dkej(x,y):
-
print(f"打印组合方式:({x},{y})") # 通过参数化实现笛卡尔积
Setup与teardown
在执行用例时,可能某些操作只需要进行一次,比如打开浏览器,或者每次操作前都需要先执行其它操作,比如查看数据前需要连接数据库,查看后需要断开数据库等,类似的操作可以使用以下几种setup/teardown进行配置与销毁
-
模块级 setup_module/teardowm_module 开始于模块始末,全局的
-
函数级 setup_function/teardown_function 只对函数用例生效(在类外)
-
类级 setup_class/teardown_class 只在类中前后运行一次(在类中)
-
方法级 setup_method/teardown_method 类中的每个方法执行前后(在类中)
-
类中的 setup/teardown 运行在调用方法前后(常用)
下面是使用方法简单示例,setup/teardown并非必须成对使用,比如连接数据库后不想断开,就可以只使用setup
-
def setup_module():
-
print("setup_模块级")
-
def teardown_module():
-
print("teardown_模块级")
-
…… setup_function/teardown_function
-
def test_01():
-
print("测试01")
-
def test_02():
-
print("测试02")
-
class Testcase01(object):
-
@classmethod
-
def setup_class(cls):
-
print("setup_类")
-
def teardown_class(cls):
-
print("teardown_类")
-
…… setup_method/teardown_method
-
…… setup/teardown
-
def test_03(self):
-
print("测试03")
-
def test_04(self):
-
print("测试04")
下面是两个函数test_01、02和两个方法test_03、04,在配置setup~和teardown~后运行的结果,从结果中可以看出它们的层级关系
-
setear_test.py::test_01
-
setup_模块级
-
setup_函数
-
测试用例01 PASSED
-
teardown_函数
-
setear_test.py::test_02
-
setup_函数
-
测试用例02 PASSED
-
teardown_函数
-
setear_test.py::Testcase01::test_03
-
setup_类
-
setup_方法
-
setup
-
测试用例03 PASSED
-
teardown
-
teardown_方法
-
setear_test.py::Testcase01::test_04
-
setup_方法
-
setup
-
测试用例04 PASSED
-
teardown
-
teardown_方法
-
teardown_类
-
teardown_模块级
Pytest fixture
上面setup/teardown可实现的功能,fixture可以更灵活的实现
定义fixture和定义普通函数差不多,唯一区别是在函数上加上个装饰器@pytest.fixture()
为了跟用例有所区分,fixture命名时不要以test开头,fixture是有返回值的,若没有返回值则默认为None
用例调用fixture的返回值,直接把fixture的函数名当作变量名
-
import pytest
-
@pytest.fixture() # 默认范围为函数级(function)
-
def login():
-
print("请先登录")
-
return
-
def test_Search(): # 不需要登录,所以不调用fixture
-
print("搜索商品,无需登录")
-
def test_Cart(login): # 需要登录,调用fixture
-
print("加购商品,需登录")
-
@pytest.fixture(scope="class") # 设置范围为类级(class)
-
def login():
-
print("请登录后再购买")
-
return
-
class Testfix(object):
-
def test_Look(self):
-
print("查看商品,无需登录") # 不需要登录,所以不调用fixture
-
def test_Pay(self,login):
-
print("购买商品,需登录") # 需要登录,调用fixture
输出结果如下,控制范围(scope)还有module,package,session,以及其它参数如:param、ids、name等
-
test_fix.py::test_Search
-
搜索商品,无需登录 PASSED
-
test_fix.py::test_Cart
-
请登录后再购买
-
加购商品,需登录 PASSED
-
test_fix.py::Testfix::test_Look
-
查看商品,无需登录 PASSED
-
test_fix.py::Testfix::test_Pay
-
请登录后再购买
-
购买商品,需登录 PASSED
再介绍一下session级别,它是可以跨.py模块调用的,也就是说有多个.py文件的测试用例时,只需调用一次fixture,设置scope="session",并写在conftest.py文件中,conftest.py文件是固定名称,pytest会自动识别此文件,所以不可改名称,此文件放在项目根目录,则可全局调用,放在某个包中,则只对包内的有效
比如使用钩子函数实现失败用例截图,将以下代码写在conftest.py中,其它模块就可以直接调用
-
import allure
-
import pytest
-
from selenium import webdriver
-
driver = None # 自定义一个driver=None
-
@pytest.hookimpl(tryfirst=1,hookwrapper=1)
-
def pytest_runtest_makereport(item,call): # 钩子函数
-
# 用例执行完成后再执行此操作,(后置处理用yield)
-
outcome = yield # 测试用例执行完后接下来要做什么事情
-
rep = outcome.get_result() # 获取用例执行完成之后的结果
-
if rep.when == "call" and rep.failed: # 若结果正在被调用而且是失败的则进行截图
-
img = driver.get_screenshot_as_png() # 若出现异常,则进行截图操作
-
allure.attach(img, "失败截图", allure.attachment_type.PNG) # 将图片展现在Allure报告上
-
# 自定义一个Fixture,初始化driver对象
-
@pytest.fixture(scope="session",autouse=True)
-
def init_driver():
-
global driver
-
driver = webdriver.Chrome()
-
return driver # 返回初始化后的driver,就可以直接被调用啦
调用conftest.py中的函数
-
def test_baidu(init_driver):
-
init_driver.get("http://baidu.com")
-
init_driver.find_element(By.CLASS_NAME, "123").click()
-
……
logging日志
日志等级
DEBUG 最详细的日志信息,典型场景是问题诊断,代码调试
INFO 详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
WARNING 当某些不期望的事情发生时记录信息(如:CPU占用过高),但此时应用程序还是正常运行的
ERROR 由于一个更严重的问题导致某些功能不能正常运行时记录的信息
CRITICAL 当发生严重错误,导致应用程序不能继续运行时记录的信息
logging模块的使用
使用logging提供的模块级别的函数
使用logging日志系统的四大组件
Pytest Allure安装
Allure运行需要Java环境,Java安装并配置环境变量后,在命令窗口执行以下命令,更多信息可查阅官方文档(https://allurereport.org/docs/#_pytest)
pip install allure-pytest
安装完成后,下载一个allure可执行文件,点击此次(https://github.com/allure-framework/allure2/releases)进入下载列表页,下载所需版本,下载后解压文件,然后将bin目录加到PATH环境变量中,最后在命令行窗口输入allure
或者allure --version
验证是否配置成功
Allure用例描述
添加装饰器 | 参数值 | 参数说明 |
---|---|---|
@allure.epic() | epic描述 | 定义项目,往下是feature |
@allure.feature() | 模块名称 | 大的功能点描述,往下是story |
@allure.story() | 次级分类 | 用户故事,次级功能点描述,往下是title |
@allure.title() | 用例的标题 | 测试用例重命名 |
@allure.testcase() | 测试用例的链接地址 | 对应功能测试用例系统里面的用例 |
@allure.issue() | 测试用例的缺陷地址 | 对应缺陷管理系统里面的链接 |
@allure.description() | 用例描述 | 测试用例的描述 |
@allure.step() | 操作步骤 | 测试用例的步骤 |
@allure.severity() | 用例等级 | blocker,critical,normal,minor,trivial |
@allure.link() | 链接 | 定义一个链接,在测试报告展现 |
@allure.attach() | 附件 | 报告添加附件,支持html、图片、视频 |
Allure报告生成
生成测试报告必须在命令行执行
-
生成中间结果,使用下面的命令指定结果生成到的目录,生成结果中包含两个格式文件(text、json)
-
pytest --alluredir=./result --clean-alluredir # 将包中所有模块的用例生成报告并清除之前的报告
-
pytest --alluredir=./result test_Jpress.py # 将指定的文件生成报告
-
pytest --alluredir=./result test_Jpress.py::TestUser# 将指定文件中的类生成报告
-
…… # 可以指定方法或使用标记等生成报告
-
-
中间结果无法直接查看,执行下面命令启动Allure查看HTML报告
allure serve ./result
-
也可以直接生成可在线访问的HTML结果
allure generate ./result -o ./report --clean # 覆盖路径需要加--clean
示例如下:
-
from selenium import webdriver
-
from selenium.webdriver.common.by import By
-
from selenium.webdriver.support import expected_conditions as EC
-
from selenium.webdriver.support.wait import WebDriverWait
-
import pytest
-
import allure
-
from util import util # 导入自己封装的日志模块
-
loginError = [["","654321","账号不能为空"],["test","123456","用户名不正确。"],
-
["admin","","密码不能为空"],["admin","654321","用户名或密码不正确"]]
-
loginOK = [["admin","123456","用户中心"]]
-
@allure.feature("Jpress后端测试")
-
class TestUser(object):
-
def setup_class(self):
-
self.driver = webdriver.Chrome()
-
self.driver.maximize_window()
-
self.driver.get("http://192.166.66.22:8080/user/login")
-
self.logger = util.get_logger()
-
self.logger.info("登录测试")
-
@allure.story("这是登录失败的测试用例")
-
@allure.description("登录平台-错误场景")
-
@pytest.mark.parametrize("username,pwd,expected",loginError)
-
def test_user_login_Error(self,username,pwd,expected):
-
user = username
-
pwd = pwd
-
expected = expected
-
# 清空输入框后输入用户名
-
self.driver.find_element(By.NAME, "user").clear()
-
self.driver.find_element(By.NAME, "user").send_keys(user)
-
# 清空输入框后输入密码
-
self.driver.find_element(By.NAME, "pwd").clear()
-
self.driver.find_element(By.NAME, "pwd").send_keys(pwd)
-
# 点击【登录】
-
self.driver.find_element(By.CLASS_NAME, "btn").click()
-
WebDriverWait(self.driver, 3).until(EC.alert_is_present())
-
alert = self.driver.switch_to.alert
-
# 验证报错信息是否正确
-
assert alert.text == expected
-
self.logger.debug("登录失败,用户名或密码输出有误!")
-
alert.accept()
-
@allure.story("后台管理平台登录失败")
-
@allure.description("记录失败时的截图和日志")
-
def test_failcase(self):
-
self.driver = webdriver.Chrome()
-
self.driver.get("http://192.166.66.22:8080/admin/login")
-
self.driver.find_element(By.NAME, "user").send_keys("admin")
-
self.driver.find_element(By.NAME, "pwd").send_keys("123456")
-
self.driver.find_element(By.NAME, "captcha").send_keys(8888)
-
try:
-
self.driver.find_element(By.CLASS_NAME, "btn错误的登录按钮元素").click()
-
except Exception as e:
-
img = self.driver.get_screenshot_as_png() # 若出现异常,则进行截图操作
-
allure.attach(img, "失败截图", allure.attachment_type.PNG) # 将图片展示在Allure报告上
-
log = self.logger.error("报错信息:%s", "无法登录,找不到登录按钮", exc_info=1) # 记录报错日志
-
# 获取报错日志,并将日志展示在Allure报告上
-
allure.attach.file(self.driver.get_log(log), "失败日志", allure.attachment_type.TEXT)
-
WebDriverWait(self.driver, 3).until(EC.alert_is_present())
-
alert = self.driver.switch_to.alert
-
assert alert.text == "登录失败"
-
@allure.story("这是登录成功用例")
-
@allure.description("成功登录至平台")
-
@allure.severity(severity_level="Blocker")
-
@pytest.mark.parametrize("username,pwd,expected",loginOK)
-
def test_user_login_OK(self,username,pwd,expected):
-
user = username
-
pwd = pwd
-
expected = expected
-
self.driver.find_element(By.NAME, "user").clear()
-
self.driver.find_element(By.NAME, "user").send_keys(user)
-
self.driver.find_element(By.NAME, "pwd").clear()
-
self.driver.find_element(By.NAME, "pwd").send_keys(pwd)
-
self.driver.find_element(By.CLASS_NAME, "btn").click()
-
WebDriverWait(self.driver, 3).until(EC.title_is(expected))
-
assert self.driver.title == expected
-
@allure.story("文章管理测试用例")
-
@allure.title("查看文章列表")
-
@allure.step("步骤1:进入文章列表")
-
def test_article_list(self):
-
expected = "http://192.166.66.22:8080/ucenter/article"
-
self.driver.find_element(By.XPATH,'//span[contains(text(),"我的文章")]').click()
-
self.driver.find_element(By.LINK_TEXT,"文章列表").click()
-
WebDriverWait(self.driver, 2).until(EC.url_contains("/ucenter/article"))
-
assert self.driver.current_url == expected
-
@allure.story("文章管理测试用例")
-
@allure.title("查看文章详情")
-
@allure.step("步骤2:查看文章详情")
-
@allure.severity(severity_level="critical")
-
def test_Look_article(self):
-
expected = "一个小测试"
-
handle = self.driver.current_window_handle # 获取当前窗口句柄
-
self.driver.find_element(By.LINK_TEXT, "一个小测试").click()
-
handles = self.driver.window_handles # 获取所有窗口句柄
-
for newhandle in handles: # 对窗口进行遍历
-
if newhandle != handle: # 判断当前窗口是否为新窗口
-
self.driver.switch_to.window(newhandle) # 切换到新打开的窗口
-
WebDriverWait(self.driver, 2).until(EC.title_is(expected))
-
assert self.driver.title == expected
-
# 关闭浏览器
-
self.driver.quit()
-
@allure.feature("Jpress前端测试")
-
class TestBlog(object):
-
def setup_class(self):
-
self.driver = webdriver.Chrome()
-
self.driver.maximize_window()
-
self.driver.get("http://192.166.66.22:8080/")
-
self.logger = util.get_logger()
-
self.logger.info("博客访问测试")
-
@allure.title("查看博客详情")
-
@pytest.mark.skipif(reason="刻意跳过此用例")
-
def test_details(self):
-
expected = "一个小测试"
-
self.driver.find_element(By.CLASS_NAME,"bh-card-main-title").click()
-
WebDriverWait(self.driver, 2).until(EC.url_contains("/article/2"))
-
assert self.driver.title == expected
-
# 关闭浏览器
-
self.driver.quit()
生成的Allure报告见最后Jenkins集成后的报告结果,未集成时也可生成并查看报告,只是没有趋势图
数据驱动测试(DDT)
读取excel文件
需要安装xlrd模块,使用xlrd模块处理excel文件,新版xlrd不支持xlsx格式的excel,会报错xlrd.biffh.XLRDError: Excel xlsx file; not supported
,可以使用xls格式的excel,或者可以安装老版本的xlrd
pip install xlrd==1.2.0 # 此版本支持xlx格式的excel
结合pytest的参数化的方式,实现excel文件数据驱动,示例如下:
-
import pytest
-
import xlrd
-
def get_data(): # 定义一个获取数据的函数
-
filename = "my_data.xlsx" # 定义数据文件
-
wb = xlrd.open_workbook(filename) # 打开文件
-
sheet = wb.sheet_by_index(0) # 使用索引方式获得文件中的第一个sheet
-
rows = sheet.nrows # 获取行
-
cols = sheet.ncols # 获取列
-
lst = []
-
for row in range(rows): # 遍历行
-
for col in range(cols): # 遍历列
-
cell_data = sheet.cell_value(row,col) # 获取单元格中的数据
-
lst.append([cell_data]) # 将单元格中的数添加到列表中
-
return lst # 返回list列表
-
@pytest.mark.parametrize("value",get_data()) # 调用excel数据
-
def test_read(value):
-
print(value)
-
# 输出结果,2*2的表格,打印每个单元格中的数据
-
test_excel.py::test_read[value0] PASSED [ 25%]['xlsx']
-
test_excel.py::test_read[value1] PASSED [ 50%]['格式']
-
test_excel.py::test_read[value2] PASSED [ 75%]['读取文件']
-
test_excel.py::test_read[value3] PASSED [100%]['666']
读取csv文件
csv文件是使用逗号分隔的文本文件,直接使用python的csv模块处理csv文件,然后结合pytest的参数化的方式,实现csv文件数据驱动,
示例如下:
-
# 测,试
-
# ceshi,testing
-
import csv
-
import pytest
-
def get_data():
-
with open("my_data.csv","r",encoding='UTF-8') as j: # 只读模式打开csv文件
-
data = csv.reader(j) # csv.reader(f)返回一个csv_reader对象
-
lst = []
-
for row in data: # 遍历对象,每次返回一行
-
lst.extend(row) # 将数据一次性追加到lst中,将结果写到一个列表中
-
return lst # 返回lst
-
@pytest.mark.parametrize("csv_value",get_data()) # 调用csv数据
-
def test_read(csv_value):
-
print(csv_value) # 将写在列表中的数据一个一个的输出
-
# 输出结果
-
test_csv.py::test_read[\u6d4b] PASSED [ 25%]测
-
test_csv.py::test_read[\u8bd5] PASSED [ 50%]试
-
test_csv.py::test_read[ceshi] PASSED [ 75%]ceshi
-
test_csv.py::test_read[testing] PASSED [100%]testing
读取json文件
直接使用python中的json模块处理json文件,然后结合pytest的参数化方式,实现json文件数据驱动
json文件内容
-
[
-
{
-
"name":"",
-
"pwd":"654321",
-
"expected":"账号不能为空"
-
},
-
{
-
"name":"admin",
-
"pwd":"",
-
"expected":"密码不能为空"
-
},
-
{
-
"name":"admin",
-
"pwd":"654321",
-
"expected":"用户名或密码不正确"
-
}
-
]
示例如下:
-
import pytest
-
import json
-
from selenium import webdriver
-
from selenium.webdriver.common.by import By
-
from selenium.webdriver.support.wait import WebDriverWait
-
from selenium.webdriver.support import expected_conditions as EC
-
def get_data():
-
lst = []
-
with open("my_data.json","r",encoding='UTF-8') as j: # 只读模式打开json文件
-
dict_data = json.loads(j.read()) # 将json转换为字典
-
for i in dict_data: # 遍历字典
-
lst.append(tuple(i.values())) #将数据以元组形式添加到lst中
-
return lst # 返回lst
-
# 此时输出get_data()值,会得到的下面结果
-
[('', '654321', '账号不能为空'), ('admin', '', '密码不能为空'), ('admin', '654321', '用户名或密码不正确')]
-
class TestUser(object):
-
def setup_class(self):
-
self.driver = webdriver.Chrome()
-
self.driver.maximize_window()
-
self.driver.get("http://192.166.66.22:8080/user/login")
-
@pytest.mark.parametrize("username,pwd,expected",get_data()) # 调用json数据
-
def test_user_login_Error(self,username,pwd,expected):
-
user = username
-
pwd = pwd
-
expected = expected
-
# 清空输入框后输入用户名
-
self.driver.find_element(By.NAME, "user").clear()
-
self.driver.find_element(By.NAME, "user").send_keys(user)
-
# 清空输入框后输入密码
-
self.driver.find_element(By.NAME, "pwd").clear()
-
self.driver.find_element(By.NAME, "pwd").send_keys(pwd)
-
# 点击【登录】
-
self.driver.find_element(By.CLASS_NAME, "btn").click()
-
# 等待页面加载
-
WebDriverWait(self.driver, 3).until(EC.alert_is_present())
-
alert = self.driver.switch_to.alert
-
# 验证报错信息是否正确
-
assert alert.text == expected
-
alert.accept()
读取yaml文件
先安装yaml模块,然后结合pytest的参数化方式,实现yaml文件数据驱动,和json差不多
pip install pyyaml
yaml文件内容
-
---
-
name: ""
-
pwd: "123456"
-
expected: "账号不能为空"
-
---
-
name: "admin"
-
pwd: ""
-
expected: "密码不能为空"
-
---
-
name: "admin"
-
pwd: "654321"
-
expected: "用户名或密码不正确"
使用yaml验证登录,示例如下:
-
import pytest
-
import yaml
-
from selenium import webdriver
-
from selenium.webdriver.common.by import By
-
from selenium.webdriver.support.wait import WebDriverWait
-
from selenium.webdriver.support import expected_conditions as EC
-
def get_data():
-
lst = []
-
with open("my_data.yaml","r",encoding='UTF-8') as j: # 只读模式打开yaml文件
-
ym = yaml.load_all(j.read()) # 使用load_all生成迭代器
-
for i in ym: # 遍历ym中的数据
-
lst.append(tuple(i.values())) #将数据以元组形式添加到lst中
-
return lst # 返回lst
-
# 此时输出get_data()值,会得到和读取json文件一样的结果
-
[('', '123456', '账号不能为空'), ('admin', '', '密码不能为空'), ('admin', '654321', '用户名或密码不正确')]
-
class TestUser(object):
-
def setup_class(self):
-
self.driver = webdriver.Chrome()
-
self.driver.maximize_window()
-
self.driver.get("http://192.166.66.22:8080/user/login")
-
@pytest.mark.parametrize("username,pwd,expected",get_data()) # 调用yaml数据
-
def test_user_login_Error(self,username,pwd,expected):
-
user = username
-
pwd = pwd
-
expected = expected
-
# 清空输入框后输入用户名
-
self.driver.find_element(By.NAME, "user").clear()
-
self.driver.find_element(By.NAME, "user").send_keys(user)
-
# 清空输入框后输入密码
-
self.driver.find_element(By.NAME, "pwd").clear()
-
self.driver.find_element(By.NAME, "pwd").send_keys(pwd)
-
# 点击【登录】
-
self.driver.find_element(By.CLASS_NAME, "btn").click()
-
# 等待页面加载
-
WebDriverWait(self.driver, 3).until(EC.alert_is_present())
-
alert = self.driver.switch_to.alert
-
# 验证报错信息是否正确
-
assert alert.text == expected
-
alert.accept()
Jenkins集成Allure报告
本次Jenkins是安装在Windows上的,直接Jenkins官网(https://www.jenkins.io/zh/download/)下载安装包进行安装就行啦,不多做介绍
1、Jenkins安装完成后,进入插件管理,安装Allure插件
2、进入全局配置,配置Allure Commandline,填写本地Allure安装路径,别名自定义,完成后点击【应用】→【保存】
3、新建一个自由风格的项目
4、配置项目,在常规配置里点击【高级】,勾选“使用自定义的工作空间”,填写工作空间目录(通常填写项目的主目录),显示名称自定义
然后增加构建步骤,选择“执行windows批处理命令”,填写要执行的命令,命令可以指定文件或类等,具体上文有介绍
pytest -vs --alluredir=./pytest_result test_Jpress.py --clean-alluredir
最后增加构建后操作步骤,选择“Allure Report”,填写结果路径,一定要与构建时的路径相同,然后【应用】→【保存】
在Jenkins页面上会出现Allure报告标识,点击可查看具体构建报告
感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取