pytest自动化测试框架(上)

目录

一、pytest 单元测试框架

二、单元测试框架和自动化测试框架

2.1 什么是自动化测试框架

2.2 自动化测试框架的作用

2.3 单元测试框架和自动化测试框架的关系

三、pytest 简介

四、pytest 接口自动化框架搭建

4.1 pycharm工具安装

4.2 python安装与环境配置

4.3  pycharm 安装pytest

4.4 pycharm中导入requests库

 五、基本语法

5.1 定义变量

5.2 变量类型

5.2.1 Numbers(数字)

5.2.2 String(字符串)

5.2.3 List(列表)

5.2.4 Tuple(元组)

5.2.5 Dictionary(字典)

5.3 运算符

5.3.1 算术运算符

 5.3.2 比较运算符

 5.3.3 赋值运算符

5.3.4 逻辑运算符​编辑

5.3.5 成员运算符

5.3.6 身份运算符

5.4 条件语句

5.5 循环语句

5.6 日期和时间

5.7 print打印语句

5.9 main方法

5.10 函数

5.11 面向对象

5.11.1 面向对象的调用

 5.11.2 init 函数

5.11.3 类的继承

5.11.4 类的重写

5.11.5 类属性与方法

5.11.6 单下划线、双下划线、头尾双下划线说明

六、接口测试

6.1 简单的Get 请求

6.2 简单的POST请求

七、pytest 用例开发常见问题

7.1 脚本读取配置文件

7.2 运行接口自动化用例

7.2.1 设置 pytest的测试环境

7.2.2 命名规则

7.2.3 接口入参

7.2.4 返参检验

7.2.5 运行结果

7.3 pytest参数化

7.4 文件读写

7.5 上传图片与视频

7.6 数据库参数化


一、pytest 单元测试框架

单元测试是指在软件开发当中,针对软件的最小单位(函数、方法)进行正确性的检查测试

常见的单元测试框架:

java:junit 和 testng

python:unittest 和 pytest

单元测试框架主要做什么?

  • 测试发现:从多个文件里面去找到我们的测试用例
  • 测试执行:按照一定的顺序和规则去执行,并生成报告
  • 测试判断:通过断言判断预期结果和实际结果的差异
  • 测试报告:统计测试进度,耗时,通过率,生成测试报告

二、单元测试框架和自动化测试框架

2.1 什么是自动化测试框架

自动化测试框架是为了完成一个指定的系统的自动化测试,而封装的一整套的、完善的代码框架,主要是封装一些自动化的基础模块、自动化的管理模块、自动化的统计模块等组成一个自动化框架。

2.2 自动化测试框架的作用

  • 提高测试效率,降低维护成本
  • 减少人工干预,提高测试的准确性,增加代码的重用性
  • 核心的思想是让不懂代码的人,也能够通过这个框架去实现自动化测试

2.3 单元测试框架和自动化测试框架的关系

  • 单元测试框架
  • pom设计模式
  • 数据驱动
  • 关键字驱动
  • 全局配置文件的封装
  • 日志监控
  • selenium,request 二次封装
  • 断言
  • 报告邮件
  • .....

单元测试框架只是自动化测试框架中的组成部分之一,就是由这些一个个部分,组成了一个完整的自动化测试框架。

三、pytest 简介

  • pytest 是一个非常成熟的Python单元框架,比unittest 更灵活,容易上手
  • pytest 可以和selenium,requests,appuim结合实现web自动化,接口自动化,app自动化
  • pytest 可以实现测试用例的跳过以及reruns失败用例重试
  • pytest 可以和allure生成非常美观的测试报告
  • pytest 可以和Jenkins持续集成
  • pytest 有很多非常强大的插件,并且这些插件能够实现很多的实用的操作,如:
    • pytest (本身框架)
    • pytest-html (生成html 格式的自动化测试报告)
    • pytest-xdist (测试用例分布式执行,多CPU分发)
    • pytest-ordering (用于改变测试用例的执行顺序)
    • pytest-returnfailures (用例失败后重跑)
    • allure-pytest (用于生成美观的测试报告)
  • 插件的安装方式
    • 单个安装,pip install -U pytest
    • 批量安装,将需要安装的插件放入文件中,在控制台执行文件进行批量安装即可
      • pip insatll -r xxx.txt

四、pytest 接口自动化框架搭建

4.1 pycharm工具安装

官方下载路径:Download PyCharm: Python IDE for Professional Developers by JetBrains

 下载社区版即可满足日常的接口自动化脚本开发与维护

pytest自动化测试框架(上)_第1张图片

4.2 python安装与环境配置

官方下载路径:Download Python | Python.org

配置python的环境变量,pycharm工具配置默认运行pytest框架和运行的python环境

pytest自动化测试框架(上)_第2张图片

设置默认的python运行环境:

pytest自动化测试框架(上)_第3张图片

pytest自动化测试框架(上)_第4张图片

4.3  pycharm 安装pytest

pytest自动化测试框架(上)_第5张图片

4.4 pycharm中导入requests库

pytest自动化测试框架(上)_第6张图片

pytest自动化测试框架(上)_第7张图片

 五、基本语法

5.1 定义变量

在python中,变量不需要定义,直接使用:

def Test(abs1,abs2,abs3):
    a = 10
    b = 20
    c =[1,20,40,[50,10,"where"]]
    c[0] = abs1
    c[1] = abs2
    c[2] = abs3

    print("abs1的值为:%d"%c[0])
    print("abs2的值为:%d"%c[1])
    print("abs3的值为:%d"%c[2])

Test(10,20,30)

5.2 变量类型

5.2.1 Numbers(数字)

数字数据类型用于存储数值,他们是不可改变的数据类型,这意味着改变数字数据类型会分配一个新的对象。

    a = 10
    b = 20

也可以使用del 语句删除一些对象的引用。

list1 = ['physics', 'chemistry', 1997, 2000]
list2 = [1, 2, 3, 4, 5,6,7,8 ]

print("list1[0]:",list1[0])      #输出为list1[0]: physics
print("list2[1:5]:",list2[1:5])  #输出为list2[1:5]: [2, 3, 4, 5]

del list1[2]                     #删除list[2]的对象的引用
print("list1:",list1)            #输出为list1: ['physics', 'chemistry', 2000]

5.2.2 String(字符串)

字符串或串(String)是由数字、字母、下划线组成的一串字符。

word = 'word'
sentence ="这是个句子"
paragraph = """这是一个段落
包含了多个语句"""

print(word)
print(sentence)
print(paragraph)

5.2.3 List(列表)

List(列表) 是 Python 中使用最频繁的数据类型。列表可以完成大多数集合类的数据结构实现。它支持字符,数字,字符串甚至可以包含列表(即嵌套)。列表用 [ ] 标识,是 python 最通用的复合数据类型。

列表中值的切割也可以用到变量 [头下标:尾下标] ,就可以截取相应的列表,从左到右索引默认 0 开始,从右到左索引默认 -1 开始,下标可以为空表示取到头或尾。

letters =['c','h','e','n','r','a','n','j','d','h','a','o']
a = letters[1:11:3]

print("letters:", a)    #输出结果为letters: ['h', 'r', 'j', 'a']

letters ="chenranjdhao"
a = letters[1:11:3]      #取下标为1,截止下标为11之间,每隔3个下标间距分别取值
print("letters:", a)    #输出结果为letters: hrja


letters ="chenranjdhao"
a = letters[1:5]         #取下标为1,截止下标小于5之间的值
print("letters:", a)    #输出结果为letters: henr
list = []                      #空列表
list.append('Google')          #使用 append() 添加元素
list.append('Runoob')
print("更新列表数据:",list)     #输出结果为 更新列表数据: ['Google', 'Runoob']

5.2.4 Tuple(元组)

元组是另一个数据类型,类似于 List,用 () 进行标识,内部元素用逗号隔开。

元组不能二次赋值,相当于只读列表。

tuple = ('runoob', 786, 2.23, 'john', 70.2)
tinytuple = (123, 'john')

print(tuple)  # 输出完整元组
print(tuple[0])  # 输出元组的第一个元素
print(tuple[1:3])   # 输出第二个至第四个(不包含)的元素
print(tuple[2:])   # 输出从第三个开始至列表末尾的所有元素


tup1 = (12, 34.56)
tup2 = ('abc', 'xyz')

tup3 = tup1 + tup2
print(tup3)     # 输出结果为(12, 34.56, 'abc', 'xyz')


L = ('spam', 'Spam', 'SPAM!')

print(L[1:])    # 输出结果为('Spam', 'SPAM!')
print(len(L))   # 输出结果为3

5.2.5 Dictionary(字典)

字典(dictionary)是除列表以外python之中最灵活的内置数据结构类型。列表是有序的对象集合,字典是无序的对象集合。

两者之间的区别在于:字典当中的元素是通过键来存取的,而不是通过偏移存取。

字典用"{ }"标识。字典由索引(key)和它对应的值value组成。

dict ={}
dict['one'] ="this is one"
dict[2]="this is two"
tinydict = {'name': 'runoob','code':6734, 'dept': 'sales'}

print(dict['one'])       #输出为:this is one
print(dict[2])           #输出为:this is two
print(tinydict)          #输出为:{'name': 'runoob', 'code': 6734, 'dept': 'sales'}

print(tinydict.values()) #输出为:dict_items([('name', 'runoob'), ('code', 6734), ('dept', 'sales')])
print(tinydict.keys())      #输出为:dict_keys(['name', 'code', 'dept'])
print(tinydict.values())    #输出为:dict_values(['runoob', 6734, 'sales'])
tinydict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}

tinydict['Age'] = 8  # 更新
tinydict['School'] = "RUNOOB"  # 添加

print("tinydict['School']: ", tinydict['School'])  #输出结果为RUNOOB
print("tinydict['Age']: ", tinydict['Age'])        #输出结果为8

del tinydict          # 删除字典
print(tinydict)       # 运行结果报错

tinydict.clear()      # 清空字典所有条目
print(tinydict)       # 运行结果为{}

5.3 运算符

5.3.1 算术运算符

pytest自动化测试框架(上)_第8张图片

 5.3.2 比较运算符

pytest自动化测试框架(上)_第9张图片

 5.3.3 赋值运算符

pytest自动化测试框架(上)_第10张图片

5.3.4 逻辑运算符

5.3.5 成员运算符

var1 = 'Hello \nWorld!'     #字符串换行
print("e" in var1)          #输出结果为 true
print('ela' not in var1)    #输出结果为 true
print(var1)

5.3.6 身份运算符

a = 20
b = 20

if (a is b):
    print("1 - a 和 b 有相同的标识")
else:
    print("1 - a 和 b 没有相同的标识")

if (a is not b):
    print("2 - a 和 b 没有相同的标识")
else:
    print("2 - a 和 b 有相同的标识")

5.4 条件语句

var =103
if(var ==100): print("变量var的值为100")
elif(var ==101): print("值不为100")
else:print('锤子')

count = 0
while count < 5:
   print (count, " is  less than 5")
   count = count + 1
else:
   print(count, " is not less than 5")

5.5 循环语句

for letter in 'chedganfgrhao':
    print("当前字母:%s"% letter)   #遍历字符串

fruits = ['banana','apple','mango']
for fruits in fruits:
    print('当前水果: %s'% fruits)   #遍历列表中的值
for num in range(10,20):  # 迭代 10 到 20 之间的数字
   for i in range(2,num): # 根据因子迭代
      if num%i == 0:      # 确定第一个因子
         j=num/i          # 计算第二个因子
         print ('%d 等于 %d * %d' % (num,i,j))
         break            # 跳出当前循环
   else:                  # 循环的 else 部分
      print ('%d 是一个质数' % num)

for letter in 'Python':
    if letter == 'h':
        pass
        print('这是 pass 块')
    print('当前字母 :', letter)

5.6 日期和时间

Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间。

import calendar
import time

ticks =time.time()
print("当前时间戳为:",ticks)    #当前时间戳为: 1691552283.167663


localtime = time.localtime(time.time())
print ("本地时间为 :", localtime)  #本地时间为 : time.struct_time(tm_year=2023, tm_mon=8, tm_mday=9, tm_hour=11, tm_min=38, tm_sec=3, tm_wday=2, tm_yday=221, tm_isdst=0)

# 格式化成2022-10-17 11:45:39形式
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))   #2023-08-09 11:38:03

# 格式化成Sat Mar 28 22:24:24 2022形式
print(time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()))  #Wed Aug 09 11:38:03 2023

# 将格式字符串转换为时间戳
a = "Mon Oct 28 22:24:24 2022"
print(time.mktime(time.strptime(a, "%a %b %d %H:%M:%S %Y")))  #1666967064.0

cal = calendar.month(2023, 8)
print("以下输出2023年8月份的日历:\n",cal)

5.7 print打印语句

# str + str:
print("我要打印的是字符串:"+ names[0])

# str + int 解决方案:
print("我要打印的是int: %d"%lists[0])

print("我要打印的是字符串: %s"%lists[0])

print("我要打印的是数组:{}".format(names[2][0]))

5.9 main方法

main 方法的作用只是用于调试,模拟其他模块调用本模块的场景以及自测,main语句在其他程序调用本模块时,不执行。

pytest自动化测试框架(上)_第11张图片

5.10 函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()
  • 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号起始,并且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
# 函数定义的格式
def printme(str):
    print(str)
    return

printme("自定义函数001")


# 带参数的函数
def printinfo(name, age=35):
    "打印任何传入的字符串"
    print("Name: ", name)
    print("Age ", age)
    return

printinfo(age=50, name="Miki")  # 输出结果为:Miki,50
printinfo(name="miki")          # 输出结果为:Miki,35
  

# 不定长参数,用(*变量名)进行表示
def printinfo(arg1, *vartuple):

    print("输出: \n ",arg1)

    for var in vartuple:
        print(var)
    return

printinfo(10)             # 输出结果为:10
printinfo(70, 60, 50)     # 输出结果为:70  60  50 

5.11 面向对象

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 方法:类中定义的函数。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

5.11.1 面向对象的调用

pytest自动化测试框架(上)_第12张图片

 5.11.2 init 函数

__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。类里面的方法都需要加上(self),申请这是类里面自己的函数,self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的。

pytest自动化测试框架(上)_第13张图片

类以外的函数,就不需要加self,申请该函数是类外的函数方法(不受init函数的影响)。在实际编程过程中,是使用类还是函数?如果功能存在公共的模块,就使用类;没有的话,就使用函数即可。

pytest自动化测试框架(上)_第14张图片

5.11.3 类的继承

# 定义父类
class Parent:  
    def myMethod(self):
        print( '调用父类方法')


# 定义子类
class Child(Parent):  
    def myMethod(self):
        print('调用子类方法')
    print(12)

c= Child()  # 子类实例
c.myMethod()  # 子类调用重写方法



class A:        # 定义类 A
.....

class B:         # 定义类 B
.....

class C(A, B):   # 继承类 A 和 B
.....

5.11.4 类的重写

class Parent:        # 定义父类
   def myMethod(self):
      print '调用父类方法'
 
class Child(Parent): # 定义子类
   def myMethod(self):
      print '调用子类方法'
 
c = Child()          # 子类实例
c.myMethod()         # 子类调用重写方法

5.11.5 类属性与方法

类的私有属性

__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs

类的方法

在类的内部,使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数

类的私有方法

__private_method:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用 self.__private_methods

class JustCounter:
    __secretCount = 0  # 私有变量
    publicCount = 0    # 公开变量
 
    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print self.__secretCount
 
counter = JustCounter()
counter.count()
counter.count()
print counter.publicCount
print counter.__secretCount  # 报错,实例不能访问私有变量

5.11.6 单下划线、双下划线、头尾双下划线说明

  • _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *

  • __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

  • __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的。

六、接口测试

6.1 简单的Get 请求

pytest自动化测试框架(上)_第15张图片

步骤:

  • 封装一个get接口的登录函数
  • requests模块
  • url 入参
  • res =requests.get(url)
  • res接收接口返回的内容

取响应体的内容,响应体的Contest-Type存在两种方式“text/html” 和 “application/json”,通过res.text文本或res.json()方式接收接口响应的内容。

6.2 简单的POST请求

pytest自动化测试框架(上)_第16张图片

 步骤:

  • 封装post 接口的登录函数
  • requests模块
  • 传参数据
    • data = {"key1":value1,"key2":value2}    # 表单格式(text/html)
    • json = {}       # json 格式
  • headers ={} 表明传参的格式
  • 组装post请求数据
    • res = requests.post(url=self.url,data =datas,headers=head)

常用函数:

  • res.url
  • res.request       # 获取请求方法 get/post
  • res.headers      # 取值["name"]
  • res.apparent_encoding     #utf-8
  • res.content        # 转换成中文  res.content.decode("utf-8")
  • res.cookies
  • res.text取值
  • json()取值          # json()["name"]

cookie 问题:把登录的cookie保存下来,传到下一个接口去发请求,session发起登录,通过session传递发起其他接口请求

重定向问题:禁止重定向,请求参数中传入allow_redirects=False;解决请求重定向问题,请求同一个session即可避免重定向,如 s = requests.session();login.login(s).post_login();

七、pytest 用例开发常见问题

7.1 脚本读取配置文件

以URL域名、登录账号与密码参数化为例:

pytest自动化测试框架(上)_第17张图片

将参数化文件整理到配置文件中(项目里新建file文件),以xxx.ini、xxx.cfg 或 xxx.txt格式,再编写读取配置文件类,通过函数进行配置文件的调用

from configparser import ConfigParser   # 读取配置文件模块
import os   # 读取配置文件的本地路径

class ReadFile():
    def __init__(self):
        
        # strict=False,主要解决配置文件configparser读取重复配置项的的问题
        self.conf = ConfigParser(strict=False)
        
        # 不引用os系统内置方法,第三方模块调用时,因取不到配置文件的本地路径而报错
        filepath = os.path.dirname(os.path.abspath(__file__)) + '/config.cfg'
        # print(filepath)
        # filepath = 'E:/work/GoodsApi/config/config.cfg'
        self.conf.read(filepath,encoding="utf-8")

    # 获取session字符串的URL
    def getSessionHost(self):
        session_host = self.conf.get('host','session_host')
        return session_host

    # 获取token字符串的URL
    def getTokenHost(self):
        token_host = self.conf.get('host','token_host')
        return token_host

    # 获取XX系统域名地址
    def getOaperate_host(self):
        oaperate_host = self.conf.get('host','oaperate_host')
        return oaperate_host

    # 获取用户名和密码
    def getLogin(self):
        username = self.conf.get('login', 'username')
        password = self.conf.get('login', 'password')
        return {"username":username,"password":password}

#通过main方式调试代码
if __name__ == '__main__':
    session_host = ReadFile().getSessionHost()
    token_host = ReadFile().getTokenHost()
    oaperate_host = ReadFile().getOaperate_host()
    # print(session_host)
    # print(token_host)
    print(oaperate_host)
    # username = ReadFile().getLogin()
    # print(username['username'])
    # print(username['password'])

以上读取配置类代码中,主要注意两个问题:

  • 获取到正确的配置文件路径
# 获取当前文件的路径  os.path.abspath(__file__)
# 获取当前文件的完整路径 os.path.dirname(os.path.abspath(__file__))
# 根据当前文件的完整路径,字符串拼接出来的路径 os.path.dirname(os.path.abspath(__file__)) + '\config.cfg'
# filepath = os.path.dirname(os.path.abspath(__file__)) + '\config.cfg'


# 获取文件的绝对路径
path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))

# 通过决定路径,拼接字符串的路径
filepath = os.path.join(path,"config","config.cfg")
# print("filepath:",filepath)
self.conf.read(filepath,encoding="utf-8")
  • configparser读取重复配置项的处理

config配置项已经存在了,因系统读取到2个以上,会报错。如果不希望报错,直接采用新的配置项覆盖,那么需要在configparser.ConfigParser(strict=False)方法中strict参数设置未False

        # 实例化ConfigParser方法时,需要在ConfigParser()方法中strict参数设置未False
        self.conf = ConfigParser(strict=False)

配置文件,读取配置文件,最后一步就是引用读取配置文件的函数,更便捷的维护及使用参数化的变量。

import requests
from config import readFile   # 引入读取配置文件的类文件

class LoginTest():
    def __init__(self):
        self.s = requests.session()
        self.session_host = readFile.ReadFile().getSessionHost() #实例化并赋值
        self.token_host = readFile.ReadFile().getTokenHost()
        self.getLogin = readFile.ReadFile().getLogin()
        self.getOaperate_host = readFile.ReadFile().getOaperate_host()
        # print(self.session_host)
        # print(self.token_host)

    def post_session(self,username,password):
        session_url = self.session_host + "/saas-sso/login"   # 直接使用参数化的URL
        # print("session_url:",session_url)
        data = {
            "username": self.getLogin['username'],
            "password": self.getLogin['password'],
            "verification": "",
            "loginFromSystemCode": "TAGGG_TKHH",
            "encryptType": 1
        }

        res = self.s.post(url = session_url, data = data)
        # print(res.json())
        session = res.cookies
        return session

    def get_code(self):
        session = LoginTest().post_session(username=self.getLogin['username'],password=self.getLogin['password'])
        code_url = self.session_host + "/oauth2/authorize?client_id=TTTRG_TK&responses_type=code&redirectts_uri=" + self.getOperate_host + "/"

        res = self.s.get(cookies = session,url = code_url,allow_redirects=False)
        code = res.json()["data"]["code"]
        # print("code:",code)
        return code


if __name__ == '__main__':
    # getLogin = readFile.ReadFile().getLogin()
    # LoginTest().post_session(getLogin['username'],getLogin['password'])
    # LoginTest().get_code()

总结步骤:

  • 设置xxx.cfg文件,存放配置参数
  • 引入ConfigParser 内置模块
  • conf.read(filepath,encoding="utf-8")
  • conf.get('host','ompGuard_host')
  • 封装读取配置文件模块,如类或者函数的形式

7.2 运行接口自动化用例

7.2.1 设置 pytest的测试环境

pytest自动化测试框架(上)_第18张图片

7.2.2 命名规则

用例文件名及用例中的函数或模块,以test开头或test结尾

pytest自动化测试框架(上)_第19张图片

7.2.3 接口入参

  • 手写入参
  • pytest装饰器参数化

7.2.4 返参检验

  • 使用assert关键字进行断言判断
  • 提取变量后,对变量assert
  • 对返回内容text进行关键字assert

7.2.5 运行结果

import pytest

from common.LoginTest import LoginTest
from api.Label.Lable import lable
from api.Label.LableTypes import lableTypes


# 用例1:登录
def test_Login():
    LoginTest().post_token()

# 用例2:添加标签类型
def test_addLableTypes():
    lableTypes().add_lableTypes("自动化测试新增标签类型A")

# 用例3:添加标签
def test_addLable():
    lable().add_labe("自动化测试新增标签A")

  • 对单个用例运行
    • 鼠标放在函数体内
  • 对当前模块下所有用例运行
    • 鼠标不放在任一函数体内
    • main方法调用
if __name__ == '__main__':
    pytest(['-s','testLogin.py'])

7.3 pytest参数化

单个变量参数化(全量测试):

@pytest.mark.parametrize("username",("18688276555","","1868827"))

@pytest.mark.parametrize("username",("18688276555","","1868827"))
# 对 username 进行全量测试
def test_session(username):
    res =LoginTest().post_session(username,password = readFile.ReadFile().getLogin()['password'])
    print(res)
    assert res.json()["code"] == "200"

pytest自动化测试框架(上)_第20张图片

组合交叉参数化:

pytest自动化测试框架(上)_第21张图片

pytest自动化测试框架(上)_第22张图片

多个变量参数化(入参、预期结果参数化):

# 用例:对入参username、password、出参code,进行参数化
@pytest.mark.parametrize("username,password,code",
                         [("18688276555","A","200"),
                          ("","B","100"),
                          ("1868827","C","100"),
                         ])
def test_session2(username,password,code):
    res =LoginTest().post_session(username = username,password = password)
    assert code == res['code']

7.4 文件读写

config目录下,新建desc.txt 文件,在公共目录下读取、写入、追加 desc.txt 文件

import os

path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
file =os.path.join(path,"config","desc.txt")
print(file)

# 读取desc.txt 文件('r' 也可以写成'rb',即二进制文件读取,则可不申明utf-8)
with open(file,'r',encoding = 'utf-8')as f:
    text = f.readline(7)
    text2 = f.read()
    print(text)
    print(text2)


# 完全覆盖重写desc.txt 文件('w' 也可以写成'wb',即二进制文件写入,则可不申明utf-8)
with open(file,'w',encoding = 'utf-8')as f:
    f.write("abcdefg")
    print(text)
    print(text2)


# 追加写入desc.txt 文件
with open(file,'a',encoding = 'utf-8')as f:
    f.write("abcdefg")
    print(text)
    print(text2)

7.5 上传图片与视频

def upload(self):
    path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
    file = os.path.join(path, "config", "123.jpg")

    login.Login(self).post_login()
    url = "https://xxxx.com"
    filename = file
    file = {"uploadFiles":open(filename,"rb")}

    res = self.post(url,files = file)
    print(res.txt)

    # 通过二进制流的方式进行文件的读取
    open(file,"rb")

7.6 数据库参数化

  • 数据库配置文件
[mysql]
host = rm.mysql.aliyuncs.com
port = 13308
user = qa
password = Oc0Nqir76yBWxo12311ba
# 数据库配置文件,多种类型存储数据
[dbinfo]
dbinfo ={
    "host":"rm.mysql.aliyuncs.com",
    "user":"qa",
    "password":"Oc0Nqir76yBWxo12311ba",
    "port":13308
    }
  • 安装pymysql

pytest自动化测试框架(上)_第23张图片

  • 连接数据库
  • 封装查询/修改SQL
  • 关闭释放连接
  • 返回数据

封装数据库连接,封装SQL语句运行处理:

class ReadFile():
    def __init__(self):
        self.conf = ConfigParser(strict=False)
        path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
        filepath = os.path.join(path,"config","config.cfg")
        self.conf.read(filepath,encoding="utf-8")

    # 封装读取config配置文件中的数据
    def getValue(self,section='hostname',option = 'host'):
        value = self.conf.get(section,option)
        return value

    # 获取MYSQL数据库
    def getMysql(self):
        host = self.getValue('mysql','host')
        username = self.getValue('mysql','username')
        password = self.getValue('mysql','password')
        port = self.getValue('mysql','port')
        return {"host":host,"username":username,"password":password,"port":(int)port}

    
    # 获取MYSQL数据库(读配置二)
    def getMysql1(self):
        dbinfo = self.getValue('dbinfo','dbinfo')
        return dbinfo


class Connect():
    def __init__(self,database = 'tx_product'):
        dbinfo = ReadFile().getMysql()

        # 使用配置二的函数方法
        # dbinfo = eval(ReadFile().getMysql1())
        # print(type(dbinfo))

        self.con = pymysql.Connect(database=database,
                                   cursorclass=pymysql.cursors.DictCursor,
                                   **dbinfo)
        self.curos = self.con.cursor()

   # 封装SQL查询
    def select_sql(self,sql):
        self.curos.execute(sql)
        # res = self.curos.fetchall()  # 获取所有的值
        res = self.curos.fetchone()   # 获取第一行的值
        # res = self.curos.fetchmany(size)   # 自定义返回数量
        print(res)
        self.con.close()  # 关闭SQL连接函数
        return res

    # 封装SQL更新
    def update_sql(self,sql):
        try:
            self.curos.execute(sql)
            self.con.commit()   # 执行更新语句,并提交
        except:
            self.con.rollback()  # 执行更新语句报错,则回滚
        self.con.close()

if __name__ == '__main__':
    con =Connect()
    s_sql = "select product_id from product"
    res = con.select_sql(s_sql)
    print(res[0]['product_id'])  # list列表取某组数据中具体值)

你可能感兴趣的:(自动化测试,pytest,python,自动化,单元测试,pycharm)