一.数据驱动/代码驱动优缺点:

使用数据驱动的好处:
- 代码复用率高。同一测试逻辑编写一次,可以被多条测试数据复用,提高了测试代码的复用率,同时可以提高测试脚本的编写效率。
- 异常排查效率高。测试框架依据测试数据,每条数据生成一条测试用例,用例执行过程相互隔离,在其中一条失败的情况下,不会影响其他的测试用例。
代码驱动:
  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框架扩展(基于代码驱动)自动化-下的更多相关文章

  1. unittest框架扩展(自动生成用例)自动化-上

    一.思想: 基于数据驱动和代码驱动结合的自动化测试框架. 二.自动化测试框架步骤: 1.获取用例,用例格式:.ymal 2.调用接口 3.校验结果 4.发送测试报告 5.异常处理 6.日志模块 三.基 ...

  2. 基于数据库的自动化生成工具,自动生成JavaBean、数据库文档、框架代码等(v5.8.8版)

    TableGo v5.8.8版震撼发布,此次版本更新如下:          1.新增两个扩展字段,用于生成自定义模板时使用.          2.自定义模板新增模板目录,可以选择不同分类目录下的模 ...

  3. Selenium基于Python web自动化基础二 -- 免登录、等待及unittest单元测试框架

    一.免登录在进行测试的过程中难免会遇到登录的情况,给测试工作添加了工作量,本文仅提供一些思路供参考解决方式:手动请求中添加cookies.火狐的profile文件记录信息实现.人工介入.万能验证码.去 ...

  4. 3分钟手把手带你搭建基于selenium的自动化框架

    1 .什么是seleniumSelenium 是一个基于浏览器的自动化工具,它提供了一种跨平台.跨浏览器的端到端的web自动化解决方案.Selenium主要包括三部分:Selenium IDE.Sel ...

  5. python接口自动化24-有token的接口项目使用unittest框架设计

    获取token 在做接口自动化的时候,经常会遇到多个用例需要用同一个参数token,并且这些测试用例跨.py脚本了. 一般token只需要获取一次就行了,然后其它使用unittest框架的测试用例全部 ...

  6. python接口自动化-有token的接口项目使用unittest框架设计

    获取token 在做接口自动化的时候,经常会遇到多个用例需要用同一个参数token,并且这些测试用例跨.py脚本了. 一般token只需要获取一次就行了,然后其它使用unittest框架的测试用例全部 ...

  7. 分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载

    一.分布式消息总线 在很多MIS项目之中都有这样的需求,需要一个及时.高效的的通知机制,即比如当使用者A完成了任务X,就需要立即告知使用者B任务X已经完成,在通常的情况下,开发人中都是在使用者B所使用 ...

  8. Selenium2+python自动化30-引入unittest框架

    from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.com ...

  9. 搭建基于SornaQube的自动化安全代码检测平台

    一.背景和目的 近年来,随着新业务.新技术的快速发展,应用软件安全缺陷层出不穷.虽然一般情况下,开发者基本都会有单元测试.每日构建.功能测试等环节来保证应用的可用性.但在安全缺陷方面,缺乏安全意识.技 ...

随机推荐

  1. 分布式之redis(转发)

    为什么写这篇文章? 博主的<分布式之消息队列复习精讲>得到了大家的好评,内心诚惶诚恐,想着再出一篇关于复习精讲的文章.但是还是要说明一下,复习精讲的文章偏面试准备,真正在开发过程中,还是脚 ...

  2. Chrome开发者工具详解(二)之使用断点调试代码下

    JS调试技巧 技巧一:格式化压缩代码 技巧二:快速跳转到某个断点的位置 右侧的Breakpoints会汇总你在JS文件所有打过的断点,点击跟checkbox同一行的会暂时取消这个断点,若是点击chec ...

  3. 对Xcode菜单选项的详细探索(来自董铂然的微博http://www.cnblogs.com/dsxniubility/p/4983614.html)

    本文调研Xcode的版本是 7.1,基本是探索了菜单的每一个按钮.虽然从xcode4一直用到了xcode7,但是一般都只是用了一些基础的功能,说来也惭愧.在一次偶然的机遇突然发现了“显示调用层级”的选 ...

  4. VB Open 函数详解 打开、关闭、读、写文件

    (一)打开和关闭文件      1.顺序文件     打开顺序文件,我们可以使用Open语句.它的格式如下:Open pathname For [Input |Output |Append] As [ ...

  5. CTF各种资源:题目、工具、资料

    目录 题目汇总 Reverse 签到题 Web Web中等难度 Crypto 基础网站 各类工具 综合 Web Payloads 逆向 Pwn 取证 题目汇总 这里收集了我做过的CTF题目 Rever ...

  6. [效率神技]Intellij 的快捷键和效率技巧|系列一|常用快捷键

    Intellij 是个功能强大的IDE,这里只讲window下社区版的Intellij. 1. 常用快捷: Alt+回车 导入包,自动修正Ctrl+N   查找类Ctrl+Shift+N 查找文件Ct ...

  7. 日记(OI 无关,文化课无关)

    2019.11.13 今天在研究 wss 的代码为什么比我快那么多. 看见他定义了一个结构体叫 thxorz,一定是因为 orz 了 thx 得到了信仰加成了. 然后刚说完这句话就看见 thx 走了进 ...

  8. Django【第17篇】:Django之信号

    django中的信号 Django中的信号及其用法 Django中提供了"信号调度",用于在框架执行操作时解耦. 一些动作发生的时候,系统会根据信号定义的函数执行相应的操作 Dja ...

  9. 【leetcode】540. Single Element in a Sorted Array

    题目如下: 解题思路:题目要求时间复杂度是O(logN),可以尝试使用二分查找法.首先数组是有序的,而且仅有一个元素出现一次,其余均为两次.我们可以先找到数组最中间的元素,记为mid.如果mid和mi ...

  10. 【JavaScript】对象 obj.name 语法与 obj[name]语法

    obj.name ==> obj["name"]  底层的自动转化,所以直接写 obj["name"] 效率会高一些 var obj = { name: ...