pytest框架

学习目标

  • 掌握pytest理论
  • 熟练编写pytest框架
  • 掌握pytest自带方法
  • 掌握pytest常用插件
  • 完成自动化代码代入框架练习

1. 什么是pytest

pytest是python自动化的一种单元测试框架

2. 特点

  • 非常简单容易上手,入门简单,文档丰富
  • 支持简单的单元测试,以及复杂的业务测试
  • 支持参数化
  • 可用指定跳过某些用例、对失败用例进行重试
  • 很多第三方插件可用

3. 安装pytest

方法一:

文件-->设置-->项目-->解释器-->[+]-->搜索pytest-->安装

方法二:

打开终端运行命令:pip install pytest

使用pip list命令查看是否安装成功

4.pytest框架编码规范

  • pytest框架规定,需要被执行的类,必须以Test开头
  • pytest框架规定,需要被执行的测试用例(方法),方法名需要以test开头

1. pytest框架demo

            
import pytest
class TestDemo:             # 需要被执行的类,需以“Test”开头,注意区分大小写

    def test_01(self):      # 需要被执行的方法(测试用例),需以“test”开头,注意区分大小写
        a = 1+1             # 编写常规的方法体代码(测试用例的功能代码)
        assert a == 2       # 断言,即该用例的预期结果

    def test_02(self):
        a = 1+1
        assert a == 3
            
        

2. pytest运行方式

  • 右键运行
  • 点击代码左侧的绿色箭头
  • 终端命令运行
  • __main__方法执行
  • pytest.ini配置文件运行

① 终端命令运行

            
语法:pytest -s -v 文件路径
案例:pytest -s -v .\pytest框架\demo.py
解释:-s 保留脚本代码中的输出print语句
      -v 打印出被执行的类名、方法名
            
        

② __main__方法执行

  • 新建一个main文件
  • 编写以下代码:
            
import pytest
if __name__ == '__main__':
    pytest.main(["-s","-v","Testdemo.py"])
            
        

③. pytest.ini运行pytest

  • 新建一个文件夹:testCase
  • 将需要执行的py文件放进testCase文件夹内
  • 新建一个文件:pytest.ini
  • 编写以下代码(不要在该文件内写注释或使用中文):
  •                 
    [pytest]
    addopts = -s -v
    testpaths = ./testCase
    python_files = Test*.py
    python_classes = Test*
    python_functions = test*
                    
                

    解释:

                    
    [pytest]                        # pytest标识
    addopts = -s -v                 # 追加配置参数
    testpaths = ./testCase          # 需要被执行的文件夹路径
    python_files = Test*.py         # 需要被执行的文件名格式
    python_classes = Test*          # 需要被执行的类名格式
    python_functions = test*        # 需要被执行的方法名格式
                    
                
  • 最后,使用【终端】执行命令:pytest

3. pytest框架自带方法

  • setup_class:类初始化方法,类被执行时,第一个被执行的方法,只会执行一次
  • teardown_class:类结束方法,类执行结束时,最后一个运行的方法,只会执行一次
  • setup:用例初始化方法,每一条用例执行前都会执行一次
  • teardown:用例结束方法,每一条用例执行后都会执行一次

将自带方法代入demo案例:

            
import pytest
class TestDemo02:

    def setup_class(self):
        print("类开始方法!")

    def setup(self):
        print("用例开始方法!")

    def test_01(self):
        print("第一条用例!")
        assert True

    def test_02(self):
        print("第二条用例!")
        assert False

    def test_03(self):
        print("第三条用例!")
        assert True

    def teardown(self):
        print("用例结束方法!")

    def teardown_class(self):
        print("类结束方法!")
            
        

4. pytest常用插件

  • pytest-html:自动生成html测试报告
  • pytest-ordering:控制用例执行顺序
  • pytest-rerunfailures:失败用例自动重试
  • @pytest.mark.skipif:跳过不执行的用例
  • @pytest.mark.parametrize:数据参数化

① pytest-html自动生成html测试报告

自动化脚本最终执行通过/不通过,都需要用测试报告来体现

  • 安装插件:pip install pytest-html
  • 新建一个文件夹:report
  • 修改pytest.ini配置文件如下:
  •                 
    [pytest]
    addopts = -s -v --html=report/report.html
    testpaths = ./testCase
    python_files = Test*.py
    python_classes = Test*
    python_functions = test*
                    
                

    运行pytest,会自动在report文件夹内生成一个report.html,使用浏览器打开页面可打开该页面

② pytest-ordering控制用例执行顺序

用例前后顺序会对用例实际执行效果产生影响

  • 安装插件:pip install pytest-ordering
  • 在需要被控制的用例前一行,使用修饰器:@pytest.mark.run(order=1)
  •                 
    @pytest.mark.run(order=1)
    def test_03(self):
        print("第三条用例!")
        assert True
                    
                
  • 注意:同区间数字的数值越小,优先级越高,建议使用连贯的正整数
  • 数字优先级:标记0-->标记正整数-->未标记-->标记负整数

③ pytest-rerunfailures失败用例自动重试

为了避免因为环境波动问题导致用例执行失败,因此提供失败用例自动重试机制,当全部重试机会均为失败,用例才算失败

  • 安装插件:pip install pytest-rerunfailures
  • 修改pytest.ini配置文件如下:
  •                 
    [pytest]
    addopts = -s -v --html=report/report.html --reruns 3
    testpaths = ./testCase
    python_files = Test*.py
    python_classes = Test*
    python_functions = test*
                    
                

    ④ @pytest.mark.skipif跳过不执行的用例

    在指定场景下,有部分用例不需要被执行,可设置为“跳过”

    • 在需要跳过的用例前一行,添加修饰器:@pytest.mark.skipif(condition=True, reason='本版本不执行!')
    •                 
      @pytest.mark.skipif(condition=True,reason='本版本不执行!')
      def test_04(self):
          print("第四条用例!")
          assert True
                      
                  

      ⑤ 数据参数化

      数据写死经常达不到测试目的,需要对数据进行参数化

      • 单一参数:@pytest.mark.parametrize('参数名', [值1, 值2, ...])
      • 多参数:@pytest.mark.parametrize(('参数名1', '参数名2', ...), [(值1, 值2, ...), (值1, 值2, ...), ...])
                      
      @pytest.mark.parametrize('a',['黄焖鸡','红烧肉'])
      def test_05(self,a):
          print("第五条用例!")
          print(a)
          assert True
                      
                  
                      
      @pytest.mark.parametrize(('num1', 'num2', 'r'),[10, 20, 30])
      def test_05(self,num1, num2, r):
          print("第六条用例!")
          sum = num1 + num2
          assert sum == r
                      
                  

项目练习: 完成“宠物医院”的登录功能自动化

① 现有功能测试用例如下:

② 新建一个用例脚本文件:TestLogin.py

③ 若不使用pytest框架,最原始的代码大致如下:

            
from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.get('http://localhost:8080/')

'''登录失败(用户名空+密码空)'''
user_name = driver.find_element_by_id('name')          # 定位用户名输入框
pwd = driver.find_element_by_id('password')            # 定位密码输入框
login = driver.find_element_by_class_name('input3')    # 定位登录按钮
user_name.send_keys('')     # 用户名输入框输入空字符
pwd.send_keys('')           # 密码输入框输入空字符
login.click()               # 点击登录按钮
time.sleep(1)
user_name_msg = driver.find_element_by_class_name('userName-msg')  # 定位用户名提示气泡
pwd_msg = driver.find_element_by_class_name('pass-msg')            # 定位密码提示气泡
assert user_name_msg.text == '用户名不能为空'      # 用户名提示气泡的内容=指定提示语
assert pwd_msg.text == '密码长度不能小于5个字符'         # 密码提示气泡的内容=指定提示语

driver.refresh()        # 刷新页面

'''登录失败(用户名空+密码正确)'''
user_name = driver.find_element_by_id('name')          # 定位用户名输入框
pwd = driver.find_element_by_id('password')            # 定位密码输入框
login = driver.find_element_by_class_name('input3')    # 定位登录按钮
user_name.send_keys('')     # 用户名输入框输入空字符
pwd.send_keys('123456')           # 密码输入框输入空字符
login.click()               # 点击登录按钮
time.sleep(1)
user_name_msg = driver.find_element_by_class_name('userName-msg')  # 定位用户名提示气泡
assert user_name_msg.text == '用户名不能为空'      # 用户名提示气泡的内容=指定提示语

driver.refresh()        # 刷新页面

'''登录失败(用户名正确+密码空)'''
user_name = driver.find_element_by_id('name')          # 定位用户名输入框
pwd = driver.find_element_by_id('password')            # 定位密码输入框
login = driver.find_element_by_class_name('input3')    # 定位登录按钮
user_name.send_keys('admin')     # 用户名输入框输入空字符
pwd.send_keys('')                # 密码输入框输入空字符
login.click()                    # 点击登录按钮
time.sleep(1)
pwd_msg = driver.find_element_by_class_name('pass-msg')            # 定位密码提示气泡
assert pwd_msg.text == '密码长度不能小于5个字符'         # 密码提示气泡的内容=指定提示语

driver.refresh()        # 刷新页面

'''登录失败(用户名正确+密码错误)'''
user_name = driver.find_element_by_id('name')          # 定位用户名输入框
pwd = driver.find_element_by_id('password')            # 定位密码输入框
login = driver.find_element_by_class_name('input3')    # 定位登录按钮
user_name.send_keys('admin')      # 用户名输入框输入空字符
pwd.send_keys('111111')           # 密码输入框输入空字符
login.click()                     # 点击登录按钮
time.sleep(1)
msg = driver.find_element_by_class_name('layui-layer-padding')  # 定位报错弹窗
assert msg.text == '用户名或密码错误'         # 密码提示气泡的内容=指定提示语

driver.refresh()        # 刷新页面

'''登录失败(用户名错误+密码正确)'''
user_name = driver.find_element_by_id('name')          # 定位用户名输入框
pwd = driver.find_element_by_id('password')            # 定位密码输入框
login = driver.find_element_by_class_name('input3')    # 定位登录按钮
user_name.send_keys('root123')      # 用户名输入框输入空字符
pwd.send_keys('123456')          # 密码输入框输入空字符
login.click()                    # 点击登录按钮
time.sleep(1)
msg = driver.find_element_by_class_name('layui-layer-padding')  # 定位报错弹窗
assert msg.text == '用户名或密码错误'         # 密码提示气泡的内容=指定提示语

driver.refresh()        # 刷新页面

'''登录失败(用户名错误+密码错误)'''
user_name = driver.find_element_by_id('name')          # 定位用户名输入框
pwd = driver.find_element_by_id('password')            # 定位密码输入框
login = driver.find_element_by_class_name('input3')    # 定位登录按钮
user_name.send_keys('root123')       # 用户名输入框输入空字符
pwd.send_keys('111111')           # 密码输入框输入空字符
login.click()                     # 点击登录按钮
time.sleep(1)
msg = driver.find_element_by_class_name('layui-layer-padding')  # 定位报错弹窗
assert msg.text == '用户名或密码错误'         # 密码提示气泡的内容=指定提示语

driver.refresh()        # 刷新页面

'''登录成功'''
user_name = driver.find_element_by_id('name')  # 定位用户名输入框
pwd = driver.find_element_by_id('password')  # 定位密码输入框
login = driver.find_element_by_class_name('input3')  # 定位登录按钮
user_name.send_keys('admin')  # 用户名输入框输入空字符
pwd.send_keys('123456')  # 密码输入框输入空字符
login.click()  # 点击登录按钮
time.sleep(2)
msg = driver.find_element_by_xpath('/html/body/table/tbody/tr[1]/td/div/table/tbody/tr/td[3]/div/span[1]')  # 定位温馨提示
assert msg.text == '你好!'  # 登录成功后的温馨提示=指定提示语
time.sleep(1)
driver.quit()
            
        

⑤ 代入pytest框架,代码大致如下:

            
import pytest
from selenium import webdriver
import time


class TestLogin:

    def setup_class(self):
        driver = webdriver.Chrome()
        driver.get('http://localhost:8080/')

    def setup(self):
        driver.refresh()

    # 用户名空+密码空
    def test_01(self):
        user_name = driver.find_element_by_id('name')          # 定位用户名输入框
        pwd = driver.find_element_by_id('password')            # 定位密码输入框
        login = driver.find_element_by_class_name('input3')    # 定位登录按钮
        user_name.send_keys('')     # 用户名输入框输入空字符
        pwd.send_keys('')           # 密码输入框输入空字符
        login.click()               # 点击登录按钮
        time.sleep(1)
        user_name_msg = driver.find_element_by_class_name('userName-msg')  # 定位用户名提示气泡
        pwd_msg = driver.find_element_by_class_name('pass-msg')            # 定位密码提示气泡
        assert user_name_msg.text == '用户名不能为空'      # 用户名提示气泡的内容=指定提示语
        assert pwd_msg.text == '密码长度不能小于5个字符'         # 密码提示气泡的内容=指定提示语

    # 用户名空+密码正确
    def test_02(self):
        user_name = driver.find_element_by_id('name')          # 定位用户名输入框
        pwd = driver.find_element_by_id('password')            # 定位密码输入框
        login = driver.find_element_by_class_name('input3')    # 定位登录按钮
        user_name.send_keys('')     # 用户名输入框输入空字符
        pwd.send_keys('123456')           # 密码输入框输入空字符
        login.click()               # 点击登录按钮
        time.sleep(1)
        user_name_msg = driver.find_element_by_class_name('userName-msg')  # 定位用户名提示气泡
        assert user_name_msg.text == '用户名不能为空'      # 用户名提示气泡的内容=指定提示语

    # 用户名正确+密码空
    def test_03(self):
        user_name = driver.find_element_by_id('name')          # 定位用户名输入框
        pwd = driver.find_element_by_id('password')            # 定位密码输入框
        login = driver.find_element_by_class_name('input3')    # 定位登录按钮
        user_name.send_keys('admin')     # 用户名输入框输入空字符
        pwd.send_keys('')           # 密码输入框输入空字符
        login.click()               # 点击登录按钮
        time.sleep(1)
        pwd_msg = driver.find_element_by_class_name('pass-msg')            # 定位密码提示气泡
        assert pwd_msg.text == '密码长度不能小于5个字符'         # 密码提示气泡的内容=指定提示语

    # 用户名正确+密码错误
    def test_04(self):
        user_name = driver.find_element_by_id('name')          # 定位用户名输入框
        pwd = driver.find_element_by_id('password')            # 定位密码输入框
        login = driver.find_element_by_class_name('input3')    # 定位登录按钮
        user_name.send_keys('admin')     # 用户名输入框输入空字符
        pwd.send_keys('111111')           # 密码输入框输入空字符
        login.click()               # 点击登录按钮
        time.sleep(1.5)
        msg = driver.find_element_by_class_name('layui-layer-padding')  # 定位报错弹窗
        assert msg.text == '用户名或密码错误'         # 报错弹窗的内容=指定提示语

    # 用户名错误+密码正确
    def test_05(self):
        user_name = driver.find_element_by_id('name')  # 定位用户名输入框
        pwd = driver.find_element_by_id('password')  # 定位密码输入框
        login = driver.find_element_by_class_name('input3')  # 定位登录按钮
        user_name.send_keys('root123')  # 用户名输入框输入空字符
        pwd.send_keys('123456')  # 密码输入框输入空字符
        login.click()  # 点击登录按钮
        time.sleep(1)
        msg = driver.find_element_by_class_name('layui-layer-padding')  # 定位报错弹窗
        assert msg.text == '用户名或密码错误'  # 报错弹窗的内容=指定提示语

    # 用户名错误+密码错误
    def test_06(self):
        user_name = driver.find_element_by_id('name')  # 定位用户名输入框
        pwd = driver.find_element_by_id('password')  # 定位密码输入框
        login = driver.find_element_by_class_name('input3')  # 定位登录按钮
        user_name.send_keys('root123')  # 用户名输入框输入空字符
        pwd.send_keys('11111')  # 密码输入框输入空字符
        login.click()  # 点击登录按钮
        time.sleep(1)
        msg = driver.find_element_by_class_name('layui-layer-padding')  # 定位报错弹窗
        assert msg.text == '用户名或密码错误'  # 报错弹窗的内容=指定提示语

    # 用户名正确+密码正确
    def test_07(self):
        user_name = driver.find_element_by_id('name')  # 定位用户名输入框
        pwd = driver.find_element_by_id('password')  # 定位密码输入框
        login = driver.find_element_by_class_name('input3')  # 定位登录按钮
        user_name.send_keys('admin')  # 用户名输入框输入空字符
        pwd.send_keys('123456')  # 密码输入框输入空字符
        login.click()  # 点击登录按钮
        time.sleep(2)
        msg = driver.find_element_by_xpath('/html/body/table/tbody/tr[1]/td/div/table/tbody/tr/td[3]/div/span[1]')  # 定位温馨提示
        assert msg.text == '你好!'  # 登录成功后的温馨提示=指定提示语

    def teardown(self):
        pass

    def teardown_class(self):
        driver.quit()