高效Web测试:构建Pytest、Allure和Jenkins的自动化测试生态

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)

 
  1. pip install pytest # 安装pytest

  2. 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的默认行为,此文件放在项目的根目录下才会生效(除非指定目录查找配置文件),如下配置,注意:实际配置文件中不可出现中文、冒号、引号等

 
  1. [pytest]

  2. python_files = test_*.py *_test.py case_* # 添加以case_*开头的测试模块

  3. python_classes = Test* *_case*     # 添加以*_case结尾的测试类

  4. python_functions = test_* case_*    # 添加以case_*开头的测试函数

  5. testpaths = testcases # 指定测试目录,是指以testpaths所在目录为基准的相对路径

  6. addopts = -vs -l  # 将常用命令参数设为默认,省去重复输入的工作

  7. norecursedirs = .* build dist CVS _darcs {arch} *.egg src # 指定pytest忽略查找某些目录,多个目录之间空格隔开

  8. log_cli = True   # 控制台实时输出日志

  9. # 注册mark标记

  10. markers =

  11.  smoke : smoke tests as smoke

  12.     output : print

Pytest标记

上面的配置文件中提到了标记,又如何使用呢?

默认情况下,pytest会递归查找当前目录下所有以test开头或结尾的Python脚本,并执行文件内所有以test开头的函数和方法。工作中由于功能尚未实现、挑选用例执行冒烟测试等原因,只想测试指定用例,在pytest中有几种方法可解决

  1. 显式指定函数名,通过::标记,例如:某文件内存在两个类,但一个类尚未完成,只想执行第二个类中的用例,就可指定类执行

     
    1. pytest -vs test_learn.py::Testpy2     # 只执行文件中的Testpy2类

    2. pytest -vs test_learn.py::Testpy2::test_method  # 只执行文件中Testpy2类下的test_method方法

  2. 使用模糊匹配,通过-k参数识别关键字,例如:

    pytest -k login test_learn.py      # 只执行文件中包含关键字login的用例
    
  3. 使用pytest.mark在用例上进行标记,标记需要在pytest.ini文件中配置,配置方法见上文,格式一定要写对,若未配置也可执行,但是会出现警告,配置中冒号前是标记名,冒号后可以理解为标记解释,当然也可以前后一样

     
    1. @pytest.mark.check    # 在函数上设置标记

    2. def test_func1(self):

    3.     assert 1 == 1

    4. @pytest.mark.output    # 在类上面设置标记,对类中所有方法都有效

    5. class caseCls(object):

    6.     def dyd_case(self):

    7.        @pytest.mark.smoke

    8.     def test_func1(self):  # 在类中的方法上设置标记

    9.         print("这是Pytest")

    执行带有标记的函数,使用-m参数

     
    1. pytest -m check test_learn.py # 执行带有check标记的用例,此标记在一个函数上标记了一次,所有只会执行一条用例

    2. pytest -m smoke test_learn.py # 执行带有smoke标记的用例,此标记在一个方法上标记了一次,同样只会执行一条用例

    3. pytest -m output test_learn.py # 执行带有output标记的用例,因为用例在类上,故类中所有的用例都会被执行

Pytest运行
  1. 通过命令行运行

     
    1. pytest -vs /package_name                           # 执行包中所有模块的用例

    2. pytest -vs file_name.py                            # 执行单独的pytest模块,file_name.py文件

    3. pytest -vs file_name.py::class_name                # 只执行文件中的class_name类

    4. pytest -vs file_name.py::class_name::method_name   # 只执行文件中class_name类下的method_name方法

    5. 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 帮助

  2. 通过main方法

     
    1. if __name__ == '__main__':

    2.     pytest.main(["-sv", "file_name.py"])  # 通过main函数执行

示例:

 
  1. # 文件名称命名规则,以test_开头或_test结尾

  2. import pytest

  3. def test_func(): # 通常在类外面称为函数,函数命名规则,以test_开头

  4.     assert 1 == 1

  5. class Testpy(object):  # 类命名规则,以Test开头

  6.     def test_method(self): # 通常在类中称为方法,方法命名规则与函数一样,以test_开头

  7.         assert 1 + 2 == 3

  8.     @pytest.mark.smoke

  9.     def test_func1(self):

  10.         print("这是Pytest")

  11. @pytest.mark.smoke

  12. class caseCls(object):

  13.     def dyd_case(self):

  14.         print("修改函数后的规则后")

  15. if __name__ == '__main__':

  16.     pytest.main(["test_learn.py"]) # 通过main函数执行命令

使用命令执行示例中的用例

 
  1. pytest ../Pytest        # 执行包中所有模块的用例

  2. pytest -vs test_learn.py      # 执行单独的pytest模块,test_learn.py文件

  3. pytest -vs test_learn.py::Testpy    # 只执行文件中的Testpy类

  4. pytest -vs test_learn.py::Testpy::test_method # 只执行文件中Testpy类下的test_method方法

  5. pytest -vs test_learn.py::test_function   # 只执行文件中test_function函数

  6. pytest -m smoke test_learn.py     # 执行带有smoke标记的用例

  7. pytest -k dyd test_learn.py      # 只执行包含关键字dyd的用例

  8. pytest --maxfail=2 test_learn.py    # 执行指定文件中的用例,失败用例超出2条则停止执行

  9. pytest --collect-only test_learn.py    # 收集指定文件中的用例,不加文件名则直接搜索当前目录下所有用例

Pytest参数化

在pytest中,可以使用参数化测试,即每组参数都独立执行一次

使用参数化装饰器pytest.mark.parametrize(argnames,argvalues),参数化的名字要与方法中的参数名一一对应,传递多个参数时可使用列表、元组、字典或列表嵌套等,实际工作中还是以json、yaml、excel等文件形式做参数化的较多,后续再介绍,示例如下:

 
  1. import pytest # 导入pytest包

  2. list = [1,"1+1","1+2"] # 列表

  3. # ↓参数必须与值的个数相等,可使用ids参数重命名,名字方便理解其意思即可,ids的个数要等于传递的数据个数

  4. @pytest.mark.parametrize("value",list,ids=["value=1","value=2","value=3"])

  5. def test_list(value):

  6.     print(value) # 打印三个结果

  7. tuple = [("abc"),("def"),["g","h","i"],[1,2,3]] # 元组

  8. @pytest.mark.parametrize("x,y,z",tuple)  # 参数必须与值的个数相等

  9. def test_xzy(x,y,z):

  10.     print(x,y,z) # 打印四个结果

  11. @pytest.mark.parametrize("input,expect",[("1+1",2),("2+2",4)]) 

  12. def test_count(input,expect):

  13.     assert eval(input) ==expect # 估算实际结果是否与期望结果一致,打印两个结果

  14. dict = ({"username":"dyd","passwd":123456},{"phone":18888888888,"age":18}) # 字典

  15. @pytest.mark.parametrize("dic",dict)

  16. def test_dict(dic):

  17.     print(dic) # 打印两个结果

  18. data = [ # ↓通过pytest中的param定义参数,id是对参数的一个说明,可自定义,方便理解各用例的含义即可

  19.     pytest.param(100,100,200,id="a+b:pass"),

  20.     pytest.param("a","b","ab",id="a+b:pass"),

  21.     pytest.param(1,1,6,id="a+b:fail")]

  22. def add(a,b): # 定义一个add相加的函数

  23.     return a+b

  24. class TestParam(object):

  25.     @pytest.mark.parametrize("a,b,expect",data)

  26.     def test_param(self,a,b,expect):

  27.         assert add(a,b) == expect # 调用a+b,判断实际结果是否与期望结果一致,出现三个结果

  28. @pytest.mark.parametrize("x",(1,2,3))

  29. @pytest.mark.parametrize("y",(4,5,6))

  30. def test_dkej(x,y):

  31.     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

 
  1. def setup_module():

  2.     print("setup_模块级")

  3. def teardown_module():

  4.     print("teardown_模块级")

  5. …… setup_function/teardown_function

  6. def test_01():

  7.     print("测试01")

  8. def test_02():

  9.     print("测试02")

  10. class Testcase01(object):

  11.     @classmethod

  12.     def setup_class(cls):

  13.         print("setup_类")

  14.     def teardown_class(cls):

  15.         print("teardown_类")

  16.         …… setup_method/teardown_method

  17.         …… setup/teardown

  18.     def test_03(self):

  19.         print("测试03")

  20.     def test_04(self):

  21.         print("测试04")

下面是两个函数test_01、02和两个方法test_03、04,在配置setup~和teardown~后运行的结果,从结果中可以看出它们的层级关系

 
  1. setear_test.py::test_01 

  2. setup_模块级

  3. setup_函数

  4. 测试用例01 PASSED

  5. teardown_函数

  6. setear_test.py::test_02 

  7. setup_函数

  8. 测试用例02 PASSED

  9. teardown_函数

  10. setear_test.py::Testcase01::test_03 

  11. setup_类

  12. setup_方法

  13. setup

  14. 测试用例03 PASSED

  15. teardown

  16. teardown_方法

  17. setear_test.py::Testcase01::test_04 

  18. setup_方法

  19. setup

  20. 测试用例04 PASSED

  21. teardown

  22. teardown_方法

  23. teardown_类

  24. teardown_模块级

Pytest fixture

上面setup/teardown可实现的功能,fixture可以更灵活的实现

定义fixture和定义普通函数差不多,唯一区别是在函数上加上个装饰器@pytest.fixture()

为了跟用例有所区分,fixture命名时不要以test开头,fixture是有返回值的,若没有返回值则默认为None

用例调用fixture的返回值,直接把fixture的函数名当作变量名

 
  1. import pytest

  2. @pytest.fixture()    # 默认范围为函数级(function)

  3. def login():

  4.     print("请先登录")

  5.     return

  6. def test_Search():    # 不需要登录,所以不调用fixture

  7.     print("搜索商品,无需登录")

  8. def test_Cart(login):   # 需要登录,调用fixture

  9.     print("加购商品,需登录")

  10. @pytest.fixture(scope="class") # 设置范围为类级(class)

  11. def login():

  12.     print("请登录后再购买")

  13.     return

  14. class Testfix(object):

  15.     def test_Look(self):

  16.         print("查看商品,无需登录") # 不需要登录,所以不调用fixture

  17.     def test_Pay(self,login):

  18.         print("购买商品,需登录") # 需要登录,调用fixture

输出结果如下,控制范围(scope)还有module,package,session,以及其它参数如:param、ids、name等

 
  1. test_fix.py::test_Search 

  2. 搜索商品,无需登录 PASSED

  3. test_fix.py::test_Cart

  4. 请登录后再购买

  5. 加购商品,需登录 PASSED

  6. test_fix.py::Testfix::test_Look

  7. 查看商品,无需登录 PASSED

  8. test_fix.py::Testfix::test_Pay 

  9. 请登录后再购买

  10. 购买商品,需登录 PASSED

再介绍一下session级别,它是可以跨.py模块调用的,也就是说有多个.py文件的测试用例时,只需调用一次fixture,设置scope="session",并写在conftest.py文件中,conftest.py文件是固定名称,pytest会自动识别此文件,所以不可改名称,此文件放在项目根目录,则可全局调用,放在某个包中,则只对包内的有效

比如使用钩子函数实现失败用例截图,将以下代码写在conftest.py中,其它模块就可以直接调用

 
  1. import allure

  2. import pytest

  3. from selenium import webdriver

  4. driver = None # 自定义一个driver=None

  5. @pytest.hookimpl(tryfirst=1,hookwrapper=1)

  6. def pytest_runtest_makereport(item,call):   # 钩子函数

  7.     # 用例执行完成后再执行此操作,(后置处理用yield)

  8.     outcome = yield # 测试用例执行完后接下来要做什么事情

  9.     rep = outcome.get_result() # 获取用例执行完成之后的结果

  10.     if rep.when == "call" and rep.failed:   # 若结果正在被调用而且是失败的则进行截图

  11.         img = driver.get_screenshot_as_png()  # 若出现异常,则进行截图操作

  12.         allure.attach(img, "失败截图", allure.attachment_type.PNG)  # 将图片展现在Allure报告上

  13. # 自定义一个Fixture,初始化driver对象

  14. @pytest.fixture(scope="session",autouse=True)

  15. def init_driver():

  16.     global driver

  17.     driver = webdriver.Chrome()

  18.     return driver  # 返回初始化后的driver,就可以直接被调用啦

调用conftest.py中的函数

 
  1. def test_baidu(init_driver):

  2.     init_driver.get("http://baidu.com")

  3.     init_driver.find_element(By.CLASS_NAME, "123").click()

  4.     ……

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报告生成

生成测试报告必须在命令行执行

  1. 生成中间结果,使用下面的命令指定结果生成到的目录,生成结果中包含两个格式文件(text、json)

     
    1. pytest --alluredir=./result --clean-alluredir  # 将包中所有模块的用例生成报告并清除之前的报告

    2. pytest --alluredir=./result test_Jpress.py   # 将指定的文件生成报告

    3. pytest --alluredir=./result test_Jpress.py::TestUser# 将指定文件中的类生成报告

    4. …… # 可以指定方法或使用标记等生成报告

  2. 中间结果无法直接查看,执行下面命令启动Allure查看HTML报告

    allure serve ./result
    
  3. 也可以直接生成可在线访问的HTML结果

    allure generate ./result -o ./report --clean # 覆盖路径需要加--clean
    

示例如下:

 
  1. from selenium import webdriver

  2. from selenium.webdriver.common.by import By

  3. from selenium.webdriver.support import expected_conditions as EC

  4. from selenium.webdriver.support.wait import WebDriverWait

  5. import pytest

  6. import allure

  7. from util import util   # 导入自己封装的日志模块

  8. loginError = [["","654321","账号不能为空"],["test","123456","用户名不正确。"],

  9.               ["admin","","密码不能为空"],["admin","654321","用户名或密码不正确"]]

  10. loginOK = [["admin","123456","用户中心"]]

  11. @allure.feature("Jpress后端测试")

  12. class TestUser(object):

  13.     def setup_class(self):

  14.         self.driver = webdriver.Chrome()

  15.         self.driver.maximize_window()

  16.         self.driver.get("http://192.166.66.22:8080/user/login")

  17.         self.logger = util.get_logger()

  18.         self.logger.info("登录测试")

  19.     @allure.story("这是登录失败的测试用例")

  20.     @allure.description("登录平台-错误场景")

  21.     @pytest.mark.parametrize("username,pwd,expected",loginError)

  22.     def test_user_login_Error(self,username,pwd,expected):

  23.         user = username

  24.         pwd = pwd

  25.         expected = expected

  26.         # 清空输入框后输入用户名

  27.         self.driver.find_element(By.NAME, "user").clear()

  28.         self.driver.find_element(By.NAME, "user").send_keys(user)

  29.         # 清空输入框后输入密码

  30.         self.driver.find_element(By.NAME, "pwd").clear()

  31.         self.driver.find_element(By.NAME, "pwd").send_keys(pwd)

  32.         # 点击【登录】

  33.         self.driver.find_element(By.CLASS_NAME, "btn").click()

  34.         WebDriverWait(self.driver, 3).until(EC.alert_is_present())

  35.         alert = self.driver.switch_to.alert

  36.         # 验证报错信息是否正确

  37.         assert alert.text == expected

  38.         self.logger.debug("登录失败,用户名或密码输出有误!")

  39.         alert.accept()

  40.     @allure.story("后台管理平台登录失败")

  41.     @allure.description("记录失败时的截图和日志")

  42.     def test_failcase(self):

  43.         self.driver = webdriver.Chrome()

  44.         self.driver.get("http://192.166.66.22:8080/admin/login")

  45.         self.driver.find_element(By.NAME, "user").send_keys("admin")

  46.         self.driver.find_element(By.NAME, "pwd").send_keys("123456")

  47.         self.driver.find_element(By.NAME, "captcha").send_keys(8888)

  48.         try:

  49.             self.driver.find_element(By.CLASS_NAME, "btn错误的登录按钮元素").click()

  50.         except Exception as e:

  51.             img = self.driver.get_screenshot_as_png()   # 若出现异常,则进行截图操作

  52.             allure.attach(img, "失败截图", allure.attachment_type.PNG)  # 将图片展示在Allure报告上

  53.             log = self.logger.error("报错信息:%s", "无法登录,找不到登录按钮", exc_info=1)  # 记录报错日志

  54.             # 获取报错日志,并将日志展示在Allure报告上

  55.             allure.attach.file(self.driver.get_log(log), "失败日志", allure.attachment_type.TEXT)

  56.         WebDriverWait(self.driver, 3).until(EC.alert_is_present())

  57.         alert = self.driver.switch_to.alert

  58.         assert alert.text == "登录失败"

  59.     @allure.story("这是登录成功用例")

  60.     @allure.description("成功登录至平台")

  61.     @allure.severity(severity_level="Blocker")

  62.     @pytest.mark.parametrize("username,pwd,expected",loginOK)

  63.     def test_user_login_OK(self,username,pwd,expected):

  64.         user = username

  65.         pwd = pwd

  66.         expected = expected

  67.         self.driver.find_element(By.NAME, "user").clear()

  68.         self.driver.find_element(By.NAME, "user").send_keys(user)

  69.         self.driver.find_element(By.NAME, "pwd").clear()

  70.         self.driver.find_element(By.NAME, "pwd").send_keys(pwd)

  71.         self.driver.find_element(By.CLASS_NAME, "btn").click()

  72.         WebDriverWait(self.driver, 3).until(EC.title_is(expected))

  73.         assert self.driver.title == expected

  74.     @allure.story("文章管理测试用例")

  75.     @allure.title("查看文章列表")

  76.     @allure.step("步骤1:进入文章列表")

  77.     def test_article_list(self):

  78.         expected = "http://192.166.66.22:8080/ucenter/article"

  79.         self.driver.find_element(By.XPATH,'//span[contains(text(),"我的文章")]').click()

  80.         self.driver.find_element(By.LINK_TEXT,"文章列表").click()

  81.         WebDriverWait(self.driver, 2).until(EC.url_contains("/ucenter/article"))

  82.         assert self.driver.current_url == expected

  83.     @allure.story("文章管理测试用例")

  84.     @allure.title("查看文章详情")

  85.     @allure.step("步骤2:查看文章详情")

  86.     @allure.severity(severity_level="critical")

  87.     def test_Look_article(self):

  88.         expected = "一个小测试"

  89.         handle = self.driver.current_window_handle  # 获取当前窗口句柄

  90.         self.driver.find_element(By.LINK_TEXT, "一个小测试").click()

  91.         handles = self.driver.window_handles  # 获取所有窗口句柄

  92.         for newhandle in handles:  # 对窗口进行遍历

  93.             if newhandle != handle:  # 判断当前窗口是否为新窗口

  94.                 self.driver.switch_to.window(newhandle)  # 切换到新打开的窗口

  95.         WebDriverWait(self.driver, 2).until(EC.title_is(expected))

  96.         assert self.driver.title == expected

  97.         # 关闭浏览器

  98.         self.driver.quit()

  99. @allure.feature("Jpress前端测试")

  100. class TestBlog(object):

  101.     def setup_class(self):

  102.         self.driver = webdriver.Chrome()

  103.         self.driver.maximize_window()

  104.         self.driver.get("http://192.166.66.22:8080/")

  105.         self.logger = util.get_logger()

  106.         self.logger.info("博客访问测试")

  107.     @allure.title("查看博客详情")

  108.     @pytest.mark.skipif(reason="刻意跳过此用例")

  109.     def test_details(self):

  110.         expected = "一个小测试"

  111.         self.driver.find_element(By.CLASS_NAME,"bh-card-main-title").click()

  112.         WebDriverWait(self.driver, 2).until(EC.url_contains("/article/2"))

  113.         assert self.driver.title == expected

  114.         # 关闭浏览器

  115.         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文件数据驱动,示例如下:

 
  1. import pytest

  2. import xlrd

  3. def get_data(): # 定义一个获取数据的函数

  4.     filename = "my_data.xlsx" # 定义数据文件

  5.     wb = xlrd.open_workbook(filename)   # 打开文件

  6.     sheet = wb.sheet_by_index(0)    # 使用索引方式获得文件中的第一个sheet

  7.     rows = sheet.nrows  # 获取行

  8.     cols = sheet.ncols  # 获取列

  9.     lst = []

  10.     for row in range(rows): # 遍历行

  11.         for col in range(cols): # 遍历列

  12.             cell_data = sheet.cell_value(row,col)   # 获取单元格中的数据

  13.             lst.append([cell_data])   # 将单元格中的数添加到列表中

  14.     return lst  # 返回list列表

  15. @pytest.mark.parametrize("value",get_data())    # 调用excel数据

  16. def test_read(value):

  17.     print(value)

  18.     # 输出结果,2*2的表格,打印每个单元格中的数据

  19. test_excel.py::test_read[value0] PASSED                                  [ 25%]['xlsx']

  20. test_excel.py::test_read[value1] PASSED                                  [ 50%]['格式']

  21. test_excel.py::test_read[value2] PASSED                                  [ 75%]['读取文件']

  22. test_excel.py::test_read[value3] PASSED                                  [100%]['666']

读取csv文件

csv文件是使用逗号分隔的文本文件,直接使用python的csv模块处理csv文件,然后结合pytest的参数化的方式,实现csv文件数据驱动,

示例如下:

 
  1. # 测,试

  2. # ceshi,testing

  3. import csv

  4. import pytest

  5. def get_data():

  6.     with open("my_data.csv","r",encoding='UTF-8') as j:    # 只读模式打开csv文件

  7.         data = csv.reader(j)    # csv.reader(f)返回一个csv_reader对象

  8.         lst = []

  9.         for row in data: # 遍历对象,每次返回一行

  10.             lst.extend(row)   # 将数据一次性追加到lst中,将结果写到一个列表中

  11.         return lst  # 返回lst

  12. @pytest.mark.parametrize("csv_value",get_data())    # 调用csv数据

  13. def test_read(csv_value):

  14.     print(csv_value)    # 将写在列表中的数据一个一个的输出

  15.     # 输出结果

  16. test_csv.py::test_read[\u6d4b] PASSED                                    [ 25%]测

  17. test_csv.py::test_read[\u8bd5] PASSED                                    [ 50%]试

  18. test_csv.py::test_read[ceshi] PASSED                                     [ 75%]ceshi

  19. test_csv.py::test_read[testing] PASSED                                   [100%]testing

读取json文件

直接使用python中的json模块处理json文件,然后结合pytest的参数化方式,实现json文件数据驱动

json文件内容

 
  1. [

  2.     {

  3.         "name":"",

  4.         "pwd":"654321",

  5.         "expected":"账号不能为空"

  6.     },

  7.     {

  8.         "name":"admin",

  9.         "pwd":"",

  10.         "expected":"密码不能为空"

  11.     },

  12.     {

  13.         "name":"admin",

  14.         "pwd":"654321",

  15.         "expected":"用户名或密码不正确"

  16.     }

  17. ]

示例如下:

 
  1. import pytest

  2. import json

  3. from selenium import webdriver

  4. from selenium.webdriver.common.by import By

  5. from selenium.webdriver.support.wait import WebDriverWait

  6. from selenium.webdriver.support import expected_conditions as EC

  7. def get_data():

  8.     lst = []

  9.     with open("my_data.json","r",encoding='UTF-8') as j:    # 只读模式打开json文件

  10.         dict_data = json.loads(j.read())    # 将json转换为字典

  11.         for i in dict_data: # 遍历字典

  12.             lst.append(tuple(i.values()))   #将数据以元组形式添加到lst中

  13.     return lst  # 返回lst

  14. # 此时输出get_data()值,会得到的下面结果

  15. [('', '654321', '账号不能为空'), ('admin', '', '密码不能为空'), ('admin', '654321', '用户名或密码不正确')]

  16. class TestUser(object):

  17.     def setup_class(self):

  18.         self.driver = webdriver.Chrome()

  19.         self.driver.maximize_window()

  20.         self.driver.get("http://192.166.66.22:8080/user/login")

  21.     @pytest.mark.parametrize("username,pwd,expected",get_data())    # 调用json数据

  22.     def test_user_login_Error(self,username,pwd,expected):

  23.         user = username

  24.         pwd = pwd

  25.         expected = expected

  26.         # 清空输入框后输入用户名

  27.         self.driver.find_element(By.NAME, "user").clear()

  28.         self.driver.find_element(By.NAME, "user").send_keys(user)

  29.         # 清空输入框后输入密码

  30.         self.driver.find_element(By.NAME, "pwd").clear()

  31.         self.driver.find_element(By.NAME, "pwd").send_keys(pwd)

  32.         # 点击【登录】

  33.         self.driver.find_element(By.CLASS_NAME, "btn").click()

  34.         # 等待页面加载

  35.         WebDriverWait(self.driver, 3).until(EC.alert_is_present())

  36.         alert = self.driver.switch_to.alert

  37.         # 验证报错信息是否正确

  38.         assert alert.text == expected

  39.         alert.accept()

读取yaml文件

先安装yaml模块,然后结合pytest的参数化方式,实现yaml文件数据驱动,和json差不多

pip install pyyaml

yaml文件内容

 
  1. ---

  2. name: ""

  3. pwd: "123456"

  4. expected: "账号不能为空"

  5. ---

  6. name: "admin"

  7. pwd: ""

  8. expected: "密码不能为空"

  9. ---

  10. name: "admin"

  11. pwd: "654321"

  12. expected: "用户名或密码不正确"

使用yaml验证登录,示例如下:

 
  1. import pytest

  2. import yaml

  3. from selenium import webdriver

  4. from selenium.webdriver.common.by import By

  5. from selenium.webdriver.support.wait import WebDriverWait

  6. from selenium.webdriver.support import expected_conditions as EC

  7. def get_data():

  8.     lst = []

  9.     with open("my_data.yaml","r",encoding='UTF-8') as j:    # 只读模式打开yaml文件

  10.         ym = yaml.load_all(j.read()) # 使用load_all生成迭代器

  11.         for i in ym: # 遍历ym中的数据

  12.             lst.append(tuple(i.values()))   #将数据以元组形式添加到lst中

  13.     return lst  # 返回lst

  14. # 此时输出get_data()值,会得到和读取json文件一样的结果

  15. [('', '123456', '账号不能为空'), ('admin', '', '密码不能为空'), ('admin', '654321', '用户名或密码不正确')]

  16. class TestUser(object):

  17.     def setup_class(self):

  18.         self.driver = webdriver.Chrome()

  19.         self.driver.maximize_window()

  20.         self.driver.get("http://192.166.66.22:8080/user/login")

  21.     @pytest.mark.parametrize("username,pwd,expected",get_data())    # 调用yaml数据

  22.     def test_user_login_Error(self,username,pwd,expected):

  23.         user = username

  24.         pwd = pwd

  25.         expected = expected

  26.         # 清空输入框后输入用户名

  27.         self.driver.find_element(By.NAME, "user").clear()

  28.         self.driver.find_element(By.NAME, "user").send_keys(user)

  29.         # 清空输入框后输入密码

  30.         self.driver.find_element(By.NAME, "pwd").clear()

  31.         self.driver.find_element(By.NAME, "pwd").send_keys(pwd)

  32.         # 点击【登录】

  33.         self.driver.find_element(By.CLASS_NAME, "btn").click()

  34.         # 等待页面加载

  35.         WebDriverWait(self.driver, 3).until(EC.alert_is_present())

  36.         alert = self.driver.switch_to.alert

  37.         # 验证报错信息是否正确

  38.         assert alert.text == expected

  39.         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报告标识,点击可查看具体构建报告

 

感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/8613.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

ros入门:topic话题通信(c++)

准备工作 创建工作空间 mkdir -p demo02_pub/src/ 生成依赖文件 cd demo02_pub/ catkin_make 进入src目录执行 catkin_create_pkg ros_pub_sub/ roscpp rospy std__msgs 发布者实现 消息发布代码编写 cd demo02_pub/src/ros_pub_sub/src 创建代码文件demo01_pub.cc …

重建大师7.0 | 质效全面提升,塑造更优质的实景三维重建

在大势智慧“AI智算、国产信创”2024秋季新品发布会上,重建大师7.0版以其卓越性能惊艳登场。这一新版本不仅引入了创新的倾斜高斯泼溅方法(OPGS),实现城市级场景的高效三维重建。 针对传统倾斜建模方法,重建大师7.0同…

Unity性能优化5【物理篇】

1.刚体的碰撞检测属性首选离散型 离散碰撞的缺点是小物体快速移动时,有丢失碰撞的风险。此下拉菜单中,越下面的选项碰撞检测频率越高,性能消耗也显著增加。因此在选择碰撞检测类型时尽量选择离散型。 2.优化碰撞矩阵 合理标记碰撞矩阵可以减…

【threejs】创建及管理场景内的后期处理效果(以bloom为例,开箱即用)

场景内使用 //创建后期通道this.effectManager new EffectManager({ renderer, camera, scene, dom })//循环渲染// 动画----------effect为我控制后期特效的开关animate() {requestAnimationFrame(this.animate);let { camera, controls, effectManager, effect } thisif (!…

建立用邻接表表示的无向图

创建一个建立用邻接表表示的无向图 #include<stdio.h> #include<stdlib.h> typedef struct node {int adjvex;struct node *next; }Anode; typedef struct {char vertex;Anode *link; }Unode; typedef struct {Unode adjlist[100];int vexnum,arcnum; }Adjgraph; …

芯片需要按一下keyup或者复位按键虚拟或者下载之后芯片能下载却运行不了或者需要额外供电。

这些问题很有可能是因为外围电路器件幅值与设计不同的存在&#xff0c;导致你需要外部供电才能实现一个正常运行&#xff0c;可以检查一下外围电路在供电区域的电流区&#xff0c;电阻幅值是否和原理图设计时看的一模一样或者直接更换 因为按键会失灵&#xff0c;首先检查复位按…

Java直播系统视频聊天系统小程序源码

直播视频聊天系统✨&#xff1a;打造你的专属互动空间 &#x1f680; 引言&#xff1a;直播视频聊天系统的兴起 在这个快节奏的数字时代&#xff0c;直播和视频聊天已成为我们日常沟通的重要工具。从游戏直播到在线教育&#xff0c;从远程办公到家庭聚会&#xff0c;直播视频…

云轴科技ZStack助力新远科技开启化工行业智能制造新篇章

新远科技基于云轴科技ZStack Cube超融合和ZStack Zaku容器云平台打造了灵活高效的IT基础设施&#xff0c;实现了IaaS和PaaS层的全面覆盖&#xff0c;优化了资源利用率&#xff0c;降低了硬件成本和运维复杂性&#xff0c;同时强化了数据安全和业务连续性。 化工行业的数字化先…

软件测试PO模式

V1&#xff1a;不使用任何设计模式和单元测试框架 V2&#xff1a;使用UnitTest管理用例 V3&#xff1a;使用方法封装的思想&#xff0c;对代码进行优化 V4&#xff1a;采用PO模式的分层思想对代码进行拆分 V5&#xff1a;对PO分层之后的代码继续优化 V6&#xff1a;PO模式深入封…

网页版五子棋——匹配模块(客户端开发)

前一篇文章&#xff1a;网页版五子棋——用户模块&#xff08;客户端开发&#xff09;-CSDN博客 目录 前言 一、前后端交互接口设计 二、游戏大厅页面 1.页面代码编写 2.前后端交互代码编写 3.测试获取用户信息功能 结尾 前言 前面文章介绍完了五子棋项目用户模块的代码…

Spring设计模式

设计模式 是一种软件开发中的解决方案&#xff0c;设计原则。目的是使代码具有扩展性&#xff0c;可维护性&#xff0c;可读性&#xff0c;如&#xff1a; 单例模式&#xff08;Singleton Pattern&#xff09; Spring IoC 容器默认会将 Bean 创建为单例&#xff0c;保证一个类…

【设计模式】结构型模式(一):适配器模式、装饰器模式

结构型模式&#xff08;一&#xff09;&#xff1a;适配器模式、装饰器模式 1.适配器模式&#xff08;Adapter&#xff09;2.装饰器模式&#xff08;Decorator&#xff09;2.1 主要特点2.2 组成部分2.3 示例代码2.3.1 Component 组件2.3.2 ConcreteComponent 具体组件2.3.3 Dec…

Go Energy 跨平台(GUI)应用编译和安装包制作

构建打包 energy cli 平台介绍描述windowNSIS安装包制作工具可通过 energy cli 安装linuxdpkg 命令系统自带macosenergy 仅生成 xxx.app系统自带 安装包制作 config/energy_[os].json是初始化应用时自动生成的应用配置文件&#xff0c;在编译和制作应用安装包时使用 Windows…

【Linux】进程信号全攻略(二)

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; 再谈信号的捕捉 &#x1f98b; 关于信号捕捉的细节部分&#xff08;sigaction函数&#xff09; 二&#xff1a;&#x1f525; 穿插话题 - 操作系统是怎么运…

鸿蒙的进击之路

1. 题记&#xff1a; 为什么要写鸿蒙&#xff0c;因为她是华为的&#xff0c;为什么是华为就要写&#xff0c;因为华为背负了国人太多太多的包袱&#xff0c;或点赞或抨击。 我是强烈支持华为的&#xff0c;但我会客观公正地去评价华为的产品&#xff0c;就比如这篇博文&#…

Swagger的介绍和使用方式+常用注解

介绍: 使用Swagger你只需要按照它的规范去定义接口及接口相关的信息&#xff0c;就可以做到生成接口文档&#xff0c;以及在线接口调试页面.简单来说就是我们只需要知道使用Swagger可以帮助我们后端生成接口文档 Swagger官网:https://swagger.io/ 因为单独使用Swagger会有些…

FFmpeg 4.3 音视频-多路H265监控录放C++开发十三:将AVFrame转换成AVPacket。视频编码,AVPacket 重要函数,结构体成员学习

前提&#xff1a; 从前面的学习我们知道 AVFrame中是最原始的 视频数据&#xff0c;这一节开始我们需要将这个最原始的视频数据 压缩成 AVPacket数据&#xff0c; 我们前面&#xff0c;将YUV数据或者 RGBA 数据装进入了 AVFrame里面&#xff0c;并且在SDL中显示。 也就是说&…

QinQ VLAN技术

QinQ VLAN技术的主要作用包括扩展VLAN数量、实现私网VLAN透传、提供二层隔离和多租户环境等。以下是对这些作用的详细介绍&#xff1a; 扩展VLAN数量 解决VLAN ID不足问题&#xff1a;QinQ技术通过在原有的802.1Q标签基础上再增加一层802.1Q标签&#xff0c;从而将VLAN数量从40…

【机器学习】24. 聚类-层次式 Hierarchical Clustering

1. 优势和缺点 优点&#xff1a; 无需提前指定集群的数量 通过对树状图进行不同层次的切割&#xff0c;可以得到所需数量的簇。树状图提供了一个有用的可视化-集群过程的可解释的描述树状图可能揭示一个有意义的分类 缺点&#xff1a; 计算复杂度较大, 限制了其在大规模数据…

分析报告、调研报告、工作方案等的提示词

什么是提示词&#xff1f; 提示词的英文是Prompt&#xff0c;是你与人工智能&#xff08;AI&#xff09;进行交流的方式。简单来说&#xff0c;提示词就是你给AI的一段文字或问题&#xff0c;AI根据这段文字或问题来生成回应或完成任务。 举个例子&#xff1a;假设你在使用一…