unittest框架扩展(基于代码驱动)自动化-下
一.数据驱动/代码驱动优缺点:
使用数据驱动的好处:
- 代码复用率高。同一测试逻辑编写一次,可以被多条测试数据复用,提高了测试代码的复用率,同时可以提高测试脚本的编写效率。
- 异常排查效率高。测试框架依据测试数据,每条数据生成一条测试用例,用例执行过程相互隔离,在其中一条失败的情况下,不会影响其他的测试用例。
代码驱动:
1.测试用例全是用代码实现的。
2.接口之间互相有依赖的,需要操作数据库、参数加密、操作redis的。
比如,流程:先注册-->登录-->加入购物车-->下单-->付款,整个流程每一个步骤的数据都要基于前一个接口的调用,因此利用数据驱动是不能满足需求的,需要用代码驱动来实现。
数据驱动
1.适合 有大量的接口需要测试,接口之间互相不依赖的
二.哪些业务需要做自动化:
1、重要的接口
2、主要的流程
3、优先验证正常的
三.本文将继续完善上一篇https://www.cnblogs.com/fancyl/p/9167015.html框架,基于扩展该框架既可以支持数据驱动,也可以支持代码驱动的一个框架。
实现:
1.先注册,注册完登录,登陆后抽奖
业务逻辑:
①.注册接口要返回账号,密码给登录,登录后返回userid,sign给抽奖接口;
②.注册、登录的信息存在数据库,因此要有操作数据库流程;
③.因为抽奖次数存放在Redis里,每抽奖3次需因此要有操作redis的流程;
2.以前有做过操作redis的封装接口,如下:
import redis
class MyRedis():
def __init__(self,ip,passwd,port=6379,db=0): #构造函数
try:
self.r = redis.Redis(host=ip,password=passwd,port=port,db=db)
except Exception as e:
print('redis连接失败,错误信息%s' %e)
def str_get(self,k): #获取数据要有返回值,所以要有返回值
res = self.r.get(k)
if res:
return res.decode()
return None #写不写都行
def str_set(self,k,v,time=None):
self.r.set(k,v,time)
def delete(self,k):
tag = self.r.exists(k) #判断这个key是否存在
if tag:
self.r.delete(k)
print('删除成功')
else:
print('这个key不存在')
def hash_get(self,name,k): #无论key是否存在,都不会报错,所以不用写try
res = self.r.hget(name,k)
if res:
res.decode()
def hash_set(self,name,k,v):
self.r.hset(name,k,v)
def hash_getall(self,name):
data = {}
res = self.r.hgetall(name)
if res:
for k,v in res.items:
k = k.decode()
v = v.decode()
data[k]=v
return data
def hash_del(self,name,k):
res = self.r.hdel(name,k)
if res: #因为删除成功会返回1,删除失败返回0
print('删除成功')
return 1
else:
print('删除失败,该key不存在')
return 0
@property #定义为属性方法,以后可以直接调用
def clean_redis(self):
self.r.flushdb() #清空redis
print('清空redis成功!') my = MyRedis(**REDIS) #**REDIS,把配置文件中的my_redis里的参数变成xx=xx。在这儿实例化以后,在其他页面用的之后直接导入就可以使用了
3.封装MySQL的接口:
import pymysql
from conf import setting
class MyDb(object):
def __del__(self):
#析构函数
self.cur.close()
self.coon.close()
def __init__(self,
host,user,passwd,db,
port=3306,charset='utf8'):
try:
self.coon = pymysql.connect(
host=host,user=user,passwd=passwd,port=port,charset=charset,db=db,
autocommit=True,#自动提交
)
except Exception as e:
print('数据库连接失败!%s'%e)
else:
self.cur = self.coon.cursor(cursor=pymysql.cursors.DictCursor)
def ex_sql(self,sql):
try:
self.cur.execute(sql)
except Exception as e:
print('sql语句有问题,%s'%sql)
else:
self.res = self.cur.fetchall()
return self.res my_sql = MyDb(**setting.MYSQL_INFO)
#直接在这里实例化的话,用的时候,直接导入就ok了
4.因为在接口调用过程中会多次用到发送请求的接口:get或post,再此将发送请求的方法封装后,可随时调用:
import requests
from conf.setting import log #日志模块,在setting中有实例化,因此可以直接导入使用 class MyRequest():
@staticmethod #因为不想实例化,所以用静态方法
def post(url,data=None,cookie=None,header=None,is_json=False,files=None):
data = data if data else {} #三元表达式,判断传值是否为空,为空就定义为空字典
#拆分:
# if data:
# data = data
# else:
# data = {}
cookie = cookie if cookie else {}
header = header if header else {}
files = files if files else {}
try:
if is_json: #如果是Json
res = requests.post(url,json=data,cookies= cookie,headers = header,verify=False,files=files).json()
else:
res = requests.post(url, data=data, cookies=cookie, headers=header,verify=False,files=files).json()
log.debug('【接口返回数据:%s】'% res) #打印日志,实际返回的结果
print('res...',res)
except Exception as e:
res = {'error':str(e)} #如果接口调用出错的话,那么就返回一个有错误信息的字典
log.error('异常信息:接口调用失败! url 【%s】 data 【%s】 实际结果是 【%s】'%(url,data,res))
return res #函数的默认参数。不要写成字典或者list。这样会有问题 @staticmethod
def get(url,data=None,cookie=None,header=None):
data = data if data else {}
cookie = cookie if cookie else {}
header = header if header else {}
try:
# verify=False 的意思就是https能访问,如果不加这个参数,https访问时会报错
res = requests.get(url, params=data, cookies=cookie, headers=header,verify=False).json()
log.debug('【接口返回数据:】'%res)
except Exception as e:
log.error('异常信息:接口调用失败! url 【%s】 data 【%s】'%(url,data))
res = {'error':str(e)} #如果接口调用出错的话,那么就返回一个有错误信息的字典
return res
5.其中setting.py,case_data文件,tool.py等文件是没有变化的,需在cases下新增代码用例,比如先新建Choice.py文件做抽奖接口,抽奖的业务:首先用户注册-->登录-->抽奖,而登录需要注册返回的username,passwd,抽奖需要登录返回的user_id,sign,具体实现如下:
import unittest,requests
from lib.my_redis import my
from lib.my_sql import my_sql
from conf.setting import BASE_URL
from urllib.parse import urljoin
from lib.my_request import MyRequest class TestChoiceDraw(unittest.TestCase):
def setUp(self): #每个用例执行之前执行的
self.username = 'test_lyl' #类变量,所有函数都可以使用 def tearDown(self):#每个用例执行之后,执行该函数
sql = 'delete from app_myuser where username = "%s";'% self.username #每个用例执行后执行的,将注册用户删掉,不然下次注册会报错
my_sql.ex_sql(sql) #调用my_sql函数,执行sql语句
my.delete('choujiang:%s'%self.username) #每个用例执行结束后,执行删除redis里面的数据 def register(self): #注册函数,在返回username,passwd供其他函数调用
url = '/api/user/user_reg'
real_url = urljoin(BASE_URL,url) #拼接实际Url
username = self.username
passwd = 'xxxxxxx'
data = {'username':username,'pwd':passwd,'cpwd':passwd}
res = MyRequest.post(real_url,data) #res实际调用myrequest里的post请求结果,传参url,data
self.assertEqual(1000,res.get('error_code'),msg='注册失败') #1000是预期结果,实际结果对比,不一致返回msg
return username,passwd def login(self): #登录函数,返回user_id,sign,供抽奖时使用
url = '/api/user/login'
real_url = urljoin(BASE_URL, url)
username,passwd = self.register() #获取注册函数里的username和passwd
data = {'username': username, 'passwd': passwd}
res = MyRequest.post(real_url,data) #res实际调用请求结果
self.assertEqual(0, res.get('error_code'), msg='登录失败') #预期结果:登录成功返回0,与实际返回结果比较
user_id = res.get('login_info').get('userId') #获取userId
sign = res.get('login_info').get('sign') #获取sign
return user_id,sign def test_choice(self): #函数以test开头会被首先执行该用例,然后该用例调用login函数,login函数调用register函数,而login和register函数没有以test开头,否则会被执行两次
'''正常抽奖''' #正常抽奖指的是抽奖次数不超过3次
url = '/api/product/choice'
real_url = urljoin(BASE_URL, url)
user_id,sign = self.login() #获取login()里的user_id,sign
data = {'userid':user_id,'sign':sign}
res = MyRequest.get(real_url,data)
self.assertEqual(0, res.get('error_code'), msg='抽奖接口调用失败') #抽奖成功返回0 def test_choice_fail(self):
'''测试超过抽奖次数的'''
url = '/api/product/choice'
real_url = urljoin(BASE_URL, url)
user_id,sign = self.login() #获取login()里的user_id,sign
data = {'userid':user_id,'sign':sign}
# choujiang:username
key = 'choujiang:%s'%self.username #这个redis里面的key,控制抽奖次数的
my.str_set(key,3,180) #设置抽奖次数3次,180s失效
res = MyRequest.get(real_url, data) #调用抽奖接口
self.assertEqual(1099,res.get('error_code')) #抽奖次数用尽的时候返回1099,因此作比较
6.添加商品接口,具体业务逻辑:先注册,注册完之后把注册用户改为管理员,然后登录获取到session信息,然后添加商品,添加后再清理用户信息和商品信息。否则无法再次添加。具体实现如下:
import os
import unittest,requests
from lib.my_redis import my
from lib.my_sql import my_sql
from conf.setting import BASE_URL,DATA_PATH
from urllib.parse import urljoin
from lib.my_request import MyRequest
class Product(unittest.TestCase):
def setUp(self):
self.username = 'test_lyl'
self.product_name = 'lyl_测试商品'
#在这里定义的话,每个函数都可以用了
def tearDown(self):
sql = 'delete from app_myuser where username = "%s";'% self.username #将注册用户删除
my_sql.ex_sql(sql)
sql2 = 'delete from app_product where product_name = "%s";'%self.product_name #将注册成功后添加的商品删掉,否则无法二次添加
my_sql.ex_sql(sql2)
print('数据清理完成。。。')
def register(self):
url = '/api/user/user_reg'
real_url = urljoin(BASE_URL,url)
username = self.username
passwd = 'xxxxxx'
data = {'username':username,'pwd':passwd,'cpwd':passwd}
res = MyRequest.post(real_url,data)
self.assertEqual(1000,res.get('error_code'),msg='注册失败')
sql = 'update app_myuser set is_admin = 1 where username = "%s";'%username
my_sql.ex_sql(sql)
return username,passwd def login(self):
url = '/api/user/login'
real_url = urljoin(BASE_URL, url)
username,passwd = self.register()
data = {'username': username, 'passwd': passwd}
res = MyRequest.post(real_url,data)
self.assertEqual(0, res.get('error_code'), msg='登录失败')
user_id = res.get('login_info').get('userId')
sign = res.get('login_info').get('sign')
return user_id,sign def test_add_product(self):
'''测试添加奖品信息'''
url = '/api/product/add'
real_url = urljoin(BASE_URL,url)
userid,sign = self.login() #获取登录返回的session
product_name = self.product_name #在setUp()定义,否则在删除商品时找不到product_name
file_abs_path = os.path.join(DATA_PATH,'test.jpg') #存放图片或文件的路径
#把文件的绝对路径拼好
files = {'file':open(file_abs_path, 'rb')} #这个构造好文件信息
data = {'userid':userid,'sign':sign,'name':product_name} #这个是请求数据
res = MyRequest.post(real_url,data=data,files=files) #获取请求结果
self.assertEqual(1000,res.get('error_code'),msg='添加奖品失败') #添加成功返回1000,与实际比较
如果数据需要加密或者其他操作,可以再封装方法去实现,该框架就举例到此。
unittest框架扩展(基于代码驱动)自动化-下的更多相关文章
- unittest框架扩展(自动生成用例)自动化-上
一.思想: 基于数据驱动和代码驱动结合的自动化测试框架. 二.自动化测试框架步骤: 1.获取用例,用例格式:.ymal 2.调用接口 3.校验结果 4.发送测试报告 5.异常处理 6.日志模块 三.基 ...
- 基于数据库的自动化生成工具,自动生成JavaBean、数据库文档、框架代码等(v5.8.8版)
TableGo v5.8.8版震撼发布,此次版本更新如下: 1.新增两个扩展字段,用于生成自定义模板时使用. 2.自定义模板新增模板目录,可以选择不同分类目录下的模 ...
- Selenium基于Python web自动化基础二 -- 免登录、等待及unittest单元测试框架
一.免登录在进行测试的过程中难免会遇到登录的情况,给测试工作添加了工作量,本文仅提供一些思路供参考解决方式:手动请求中添加cookies.火狐的profile文件记录信息实现.人工介入.万能验证码.去 ...
- 3分钟手把手带你搭建基于selenium的自动化框架
1 .什么是seleniumSelenium 是一个基于浏览器的自动化工具,它提供了一种跨平台.跨浏览器的端到端的web自动化解决方案.Selenium主要包括三部分:Selenium IDE.Sel ...
- python接口自动化24-有token的接口项目使用unittest框架设计
获取token 在做接口自动化的时候,经常会遇到多个用例需要用同一个参数token,并且这些测试用例跨.py脚本了. 一般token只需要获取一次就行了,然后其它使用unittest框架的测试用例全部 ...
- python接口自动化-有token的接口项目使用unittest框架设计
获取token 在做接口自动化的时候,经常会遇到多个用例需要用同一个参数token,并且这些测试用例跨.py脚本了. 一般token只需要获取一次就行了,然后其它使用unittest框架的测试用例全部 ...
- 分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载
一.分布式消息总线 在很多MIS项目之中都有这样的需求,需要一个及时.高效的的通知机制,即比如当使用者A完成了任务X,就需要立即告知使用者B任务X已经完成,在通常的情况下,开发人中都是在使用者B所使用 ...
- Selenium2+python自动化30-引入unittest框架
from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.com ...
- 搭建基于SornaQube的自动化安全代码检测平台
一.背景和目的 近年来,随着新业务.新技术的快速发展,应用软件安全缺陷层出不穷.虽然一般情况下,开发者基本都会有单元测试.每日构建.功能测试等环节来保证应用的可用性.但在安全缺陷方面,缺乏安全意识.技 ...
随机推荐
- 同步锁 synchronized
package ba; public class Tongbu implements Runnable{ int i=100; public void run(){ while(true){ sell ...
- ajax使用jsonp跨域调用webservice error错误信息"readyState":4,"status":200,"statusText":"success"
主要还是接口写有问题 至于ajax保持简洁写法即可 $.ajax({ dataType: 'jsonp', type: ‘get’, data: {}, url: '' })
- nginx配置反向代理,解决前端开发的跨域问题
适用:开发和生产环境 配置如下 server { listen 10901; server_name res.pre.ices.red; #charset koi8-r; #access_log lo ...
- JavaEE高级-SpringMVC学习笔记
*SpringMVC概述 - Spring为展示层提供的基于MVC设计理念的优秀Web框架,是目前最主流的MVC框架之一 - Spring3.0后全面超越Struts2,成为最优秀的MVC框架 - S ...
- dockerfile制作笔记
dockerfile语法格式: FROM: 基础镜像(就是在什么镜像上面做) MAINTAINER: 镜像创建者信息(作者的信息) EXPOSE: 开放的端口 ENV: 设置变量 ...
- Keycode含义
keycode 是键盘上每一个按键对应的码keycode如下 :keycode 0 = keycode 1 = keycode 2 = keycode 3 = keycode 4 = keycode ...
- L3-002 特殊堆栈 (30 分)
大家都知道“堆栈”是一种“先进后出”的线性结构,基本操作有“入栈”(将新元素插入栈顶)和“出栈”(将栈顶元素的值返回并从堆栈中将其删除).现请你实现一种特殊的堆栈,它多了一种操作叫“查中值”,即返回堆 ...
- 安装php-solr扩展
本人qq群也有许多的技术文档,希望可以为你提供一些帮助(非技术的勿加). QQ群: 281442983 (点击链接加入群:http://jq.qq.com/?_wv=1027&k=29Lo ...
- Jmeter性能测试结果分析:响应时间为什么是下降的趋势?
测试图数据库:边的插入,递增并发量,6000并发平均响应时间比7000的并发的平均响应时间还要大? 7000并发的99%用户响应时间是70.99,平均响应时间怎么就是38.59了? 一共两 ...
- MYSQL数据导出与导入,secure_file_priv参数设置
https://www.imooc.com/article/41883 MySQL 报错 [Code: 1290, SQL State: HY000] The MySQL server is run ...