
编辑ZYL_Project:
├── config (配置文件)
├── data (数据文件)
├── logs (日志文件)
├── report (报告)
├── test (测试用例)
├── utils (公共方法)
├── main.py (入口函数)
└── ReadMe.md (说明性的文件,告诉团队成员框架需要的环境以及用法)

编辑
编辑 # 测试用例模板一
tests:
- case: 验证功能xxx
request:
method: POST
path: /api/test01
headers:
Content-Type: application/x-www-form-urlencoded
params:
a: 1111
b: 2222
expected:
data:
statue: 成功
- case: 验证功能yyy
request:
method: POST
path: /api/test01
headers:
Content-Type: application/x-www-form-urlencoded
params:
a: 3333
b: 4444
expected:
data:
statue: 成功
1.1.1、模板说明tests: 是固定模板名case: 用例名称,用于说明验证了什么功能request: 请求数据的集合method: 请求方法path: 请求路径headers: 请求头params: 请求参数expected: 期望值,数据结构可以根据实际项目自定义1.1.2、模板优缺点优:一条用例对应对应一个case架构,结构清晰。缺:对于请求方法和请求路径没必要每次都加载,有点冗余。# 测试用例模板2
tests:
request:
method: POST
path: /api/test01
headers:
token: $token
Content-Type: application/x-www-form-urlencoded
params:
a:
- 1111
- 2222
b:
- 3333
- 4444
expected:
- data:
job: 测试工程师
statue: 成功
- data:
statue: 失败
1.2.1、模板说明tests: 是固定模板名case: 用例名称,用于说明验证了什么功能request: 请求数据的集合method: 请求方法path: 请求路径headers: 请求头params: 请求参数expected: 期望值,数据结构可以根据实际项目自定义1.2.2、模板优缺点优:请求方法、请求头、请求路径用一个节点并以数组的形式进行存储,避免多模板子集,请求参数和期望数据也用数组存储,案例按照数组下标一一对应。缺:对于不同条件情况的请求头,无法区分;功能验证说明体现在测试用例代码中,在用例中体现不出;对于请求参数在测试用例设计模板中体现不出用例的颗粒度。# 测试用例模板三
tests:
common: #公共部分
method: POST
path: /api/test01
testcase:
- case: 验证正常登录后请求数据
request:
headers:
Authorization: $Authorization
Content-Type: application/x-www-form-urlencoded
params:
a: 1111
b: 2222
expected:
data:
job: 测试工程师
statue: 成功
- case: 验证登录后请求数据类型为字母
request:
headers:
Authorization: $Authorization
Content-Type: application/x-www-form-urlencoded
params:
a: aaa
b: bbb
expected:
data:
statue: 失败
1.3.1、模板说明tests: 是固定模板名case: 用例名称,用于说明验证了什么功能request: 请求数据的集合method: 请求方法path: 请求路径headers: 请求头params: 请求参数expected: 期望值,数据结构可以根据实际项目自定义1.3.2、模板优缺点优:结构清晰,一条用例一个case,提取了公共部分的请求路径和请求方法,重点放在请求头,请求参数和期望值的组合用例化。缺:对于请求路径和请求方法的错误没法单独验证,但是对于请求路径的错误可以自己模拟,体现在用例中的作用意义不大。
编辑1.4.1、用例模板说明用例相关字段的说明和格式要求,请参考下表规范去编写:
编辑1.4.2、sheet表维度设计针对sheet表维度设计有两种方案:方案一、sheet名为模块名,则sheet表中的内容为这个模块的所有接口的所有情况的校验,测试代码名为test_模块名;方案二、sheet名为接口名,则sheet表中的内容为对这一个接口的所有情况的校验,测试代码名为test_接口名;综合sheet表的有效利用率和字段的避重复性原则,这边我比较推崇方案一,可以使得测试用例模块化,测试接口多元化。1.4.3、模板优缺点优:按excel给出的接口测试用例编写模板,可以对应表头字段去对应swagger接口文档填写对应内容,可以运用等价类、边界值、场景设计法、判定表、因果图、正交法、错误猜测法等方法对用例的参数进行随机组合场景。不需要会代码就可以做自动化,代码都封装好了,只需要复制模块代码,传入对应的sheet名就可以了。缺:目前这个模板暂不支持多接口的开发。# 测试接口集合用例模板
tests:
- case: 使用正确的数据新增api密钥
request:
method: POST
path: /api/test01
headers:
Content-Type: application/x-www-form-urlencoded
params:
a: aaaa
b: 2222
expected:
data:
apiKey: 222222
statue: 成功
- case: 删除已存在的api密钥
request:
method: POST
path: /api/mockman
headers:
Content-Type: application/x-www-form-urlencoded
params:
name: li
id: $id
expected:
data:
statue: 成功
# 实例化JMESPath抽取器
j = JMESPathExtractor()
# 实例化TEMPLATE模板解析
T = TEMPLATEExtractor()
# 实例化CASE模板解析器
c = CASEExtractor()
# 测试用例的文件路径
data_path = SINGLE_DATA + 'testName.yml'
# 参数关联-获取登录令牌(需要登录接口的处理,若不需要登录,则不需要这部分代码,获取模板数据直接为T.extract(data_path)
Authorization = {'Authorization': str(Config(config=TOKEN_PATH).get('token'))}
# 获取模板数据
t = T.extract(data_path, Authorization)
class Test_03(unittest.TestCase):
''' 测试三用例模板 '''
@classmethod
def setUpClass(cls):
warnings.simplefilter('ignore', ResourceWarning)
print("------------------------------------ 开始执行用例 ------------------------------------")
# 获取请求头
cls.headers = c.get_headers(t, n)
# 获取请求的url
cls.url = T.getUrl3(t)
# 获取请求方法
cls.method = c.get_method(t)
# 请求
cls.client = HTTPClient(cls.url, cls.method, cls.headers)
@classmethod
def tearDownClass(cls):
print("------------------------------------ 用例执行结束 ------------------------------------")
@verificating_case(t, n)
def test_01(self):
""" {0} """
print("\n请求地址:%s \n请求方法:%s \n请求头:%s" % (self.url, self.method, self.headers))
# 测试需要
response = self.client.send(data=c.get_params(t, n))
# 请求成功断言
assertHTTPCode(response, [200])
# 获取返回结果
statue = j.extract(query='data.statue', body=response.text)
# 期望的结果
expect = c.get_expected(t, n)['statue']
# 比较返回结果和期望结果
self.assertEqual(statue, expect)
代码模版讲解:# 实例化JMESPath抽取器
j = JMESPathExtractor()
# 实例化TEMPLATE模板解析
T = TEMPLATEExtractor()
# 实例化CASE模板解析器
c = CASEExtractor()
t = T.extract(data_path)
情况二、需要登录的接口测试代码的处理:植入tokenAuthorization = {'Authorization': str(Config(config=TOKEN_PATH).get('token'))}
t = T.extract(data_path, Authorization)
测试用例模板的处理:request:
headers:
Authorization: $Authorization
就是以$key值的形式去引入变量值T.getUrl3(t)
请求头:c.get_headers(t, n)
请求方法:c.get_method(t)
请求数据:c.get_params(t, n)
期望数据:c.get_expected(t, n)
@verificating_case(t, n)
def test_01(self):
""" {0} """
案例名称:@verificating_case(t, n)
{0}默认都取一个值
j.extract(query='data.node', body=response.text)
这个解析器也是封装好的,具体要取哪一层的数据用,对应数组下脚标去取就可以了。self.assertEqual({实际的结果}, {期望的结果})
当然这只是一种断言的函数,还有很多种函数
这边有一些关于断言的介绍和总结:
可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。
self.assertEqual(a,b,msg=msg) #判断a与.b是否一致,msg类似备注,可以为空
self.assertNotEqual(a,b,msg=msg) #判断a与b是否不一致
self.assertTrue(a,msg=none) #判断a是否为True
self.assertFalse(b,msg=none) #判断b是否为false
self.assertAlmostEqual(a,b,places=none,msg=none,delta=none) #该判断过程有点复杂,判断过程如下
注:places与delta不能同时存在,否则出异常
#若a==b,则直接输入正确,不判断下面的过程
#若delta有数,places为空,判断a与b的差的绝对值是否<=delta,满足则正确,否则错误
#若delta为空,places有数,判断b与a的差的绝对值,取小数places位,等于0则正确,否则错误
#若delta为空,places为空,默认赋值places=7判断
例 assertAlmostEqual(2,2) 正确,
assertAlmostEqual(5,2,delta=4) 正确
assertAlmostEqual(5,2,delta=2) 错误
assertAlmostEqual(2,2.005,places=1) 正确
assertAlmostEqual(2,2.05,places=3) 错误
self.assertNotAlmostEqual(a,b,places=none,msg=none,delta=none) 同上,但判断相反
注,delta与places不能同时存在,否则抛出异常
例 assertNotAlmostEqual(2,2) 错误
assertNotAlmostEqual(a,b,delta=c) #a不等于b 同时 a-b>c 则正确,否则错误
assertNotAlmostEqual(a,b,places=2)
#a不等于b 同时|b-a|*0,01 不等于0 则正确,否则错误
self.assertSequenceEqual #有空在研究,源码很长
self.assertListEqual #有空研究
self.assertTupleEqual #有空在研究
self.assertSetEqual #有空在研究
self.assertIn(a,b) 判断a in b是否成立,正确则True,否则为False
例: assertIn(“2” in “23”) 成功
assertIn(“1” in “23”) 失败
self.assertNotIn(a,b) 判断a in b是否成立,不成立则True 否则 False
例:assertIn(“2” in “23”) 失败
assertIn(“1” in “23”) 成功
self.assertIs(a,b) 判断a 与b的对象是否相同,成立则True,否则False
注,判断是否同一对象 id(a) 若id相同,则为同一对象
例 a,b=1,1.0
assertls(a,b) 失败
a,b=1,1
assertls(a,b) 成功
self.assertIsNot(a,b) 判断a 与b的对象是否相同,不成立True,否则False
self.assertDictEqual(a,b) #判断字典a和字典b是否相等,a,b为字典
self.assertDictContainsSubset
self.assertItemsEqual(a,b) #比较两字符串是否一致,同sorted(a)==sorted(b)
注:sorted排序,方法内部为,将a,b分别list,生成各自列表,在sorted排序在比对
self.assertMultiLineEqual(a,b) #比较a文本与b文本是否一致,即便多了个换行,也会区分
self.assertLess(a,b) #判断a
self.assertLessEqual #判断a<=b 成立则通过,否则失败
self.assertGreater #判断a>b 成立则通过,否则失败
self.assertGreaterEqual #判断a>=b 成立则通过,否则失败
self.assertIsNone(obj=””) #判断obj=None 成立则通过,否则失败
self.assertIsNotNone #判断obj=None 成立则失败,否则通过
self.assertIsInstance(a,b) #判断a的数据类型是否为b,isinstance(a,b) 成立则通过,否则失败
self.assertNotIsInstance #判断同上相反
self.assertRaisesRegexp #正则判断匹配,没仔细看,过程复杂
self.assertRegexpMatches(a,b) #正则匹配 同re.search(b,a)匹配有则成功,否则失败
注:a为匹配的正则表达式,必须字符型,b 为要匹配的内容
self.assertNotRegexpMatches #同上,判断相反
# 实例化JMESPath抽取器
j = JMESPathExtractor()
# 实例化TEMPLATE模板解析器
T = TEMPLATEExtractor()
# 实例化组合模板解析器
S = SUITEExtractor()
# 测试用例的文件路径
data_path = COMBIN_DATA + 'testName.yml'
# 获取模板数据
t = T.extract(data_path)
# 用例名
case = S.get_suite(t)
class Test_01c(unittest.TestCase):
''' 测试接口集合用例模板 '''
@classmethod
def setUpClass(cls):
warnings.simplefilter('ignore', ResourceWarning)
print("------------------------------------开始执行用例:%s ------------------------------------ " % case)
@classmethod
def tearDownClass(cls):
print("------------------------------------用例执行结束:%s ------------------------------------" % case)
@verificating_suite(case)
def test(self):
""" {0} """
client = HTTPClient(T.getUrl(x, t), S.get_method(t, n), S.get_headers(t, n))
response = client.send(data=S.get_params(t, n))
statue = j.extract(query='data.statue', body=response.text)
self.assertEqual(statue, S.get_expected(t, n)['statue'])
apiKey = j.extract(query='data.apiKey', body=response.text)
t1 = T.extract(data_path, {'id': apiKey})
client1 = HTTPClient(T.getUrl(x, t1), S.get_method(t1, n), S.get_headers(t1, n))
response1 = client1.send(data=S.get_params(t1, n))
statue = j.extract(query='data.statue', body=response1.text)
self.assertEqual(statue, S.get_expected(t1, n)['statue'])
# 实例化JMESPath抽取器
j = JMESPathExtractor()
# 实例化TEMPLATE模板解析器
T = TEMPLATEExtractor()
# 实例化组合模板解析器
S = SUITEExtractor()
t = T.extract(data_path)
情况二、需要登录的接口测试代码的处理:植入tokenAuthorization = {'Authorization': str(Config(config=TOKEN_PATH).get('token'))}
t = T.extract(data_path, Authorization)
测试用例模板的处理:request:
headers:
Authorization: $Authorization
就是以$key值的形式去引入变量值T.getUrl(x, t)
请求头:S.get_headers(t, n)
请求方法:S.get_method(t, n)
请求数据:S.get_params(t, n)
期望数据:S.get_expected(t, n)
@verificating_suite(case)
def test(self):
""" {0} """
案例名称:@verificating_suite(case)
case名可以通过case = S.get_suite(t)来提取组合名称
{0}默认都取一个值
apiKey = j.extract(query='data.apiKey', body=response.text)
t1 = T.extract(data_path, {'id': apiKey})
j.extract(query='data.node', body=response.text)
这个解析器也是封装好的,具体要取哪一层的数据用取数组下脚标的方式去取就可以了。self.assertEqual({实际的结果}, {期望的结果})
当然这只是一种断言的函数,还有很多种函数,可以参考上面的总结。SHEET_NAME = "登录"
@docstring_parameter(SHEET_NAME)
class TestCase(unittest.TestCase):
'''{0}'''
@parameterized.expand(getExcelData(TEST_CASE_PATH, SHEET_NAME))
def test_login(self, rowNumber, caseRowNumber, testCaseName, priority, apiName, path, method, parmsType,
data, checkPoint, isRun, result, bugUrl):
if isRun == "Y" or isRun == "y":
headers = {"Content-Type": "application/json;charset=UTF-8"}
# 选择环境拼接url
url = get_url(path)
logger.warning("[开始执行测试用例:{}]".format(testCaseName))
print(
"测试用例编号:%s \n测试用例名称:%s \n优先级:%s \n接口名:%s \n请求方法:%s \n请求数据类型:%s \n请求url:%s \n请求参数:%s" % (
caseRowNumber, testCaseName, priority, apiName, method, parmsType, url, data))
# 对解析出来的excel的用例数据处理
flag = data_deal(SHEET_NAME, data, rowNumber, headers, url, method, checkPoint, result, bugUrl)
# 断言
self.assertTrue(flag, msg="检查点数据与实际返回数据不一致")
else:
unittest.skip("不执行")
@docstring_parameter(SHEET_NAME)
class TestCase(unittest.TestCase):
'''{0}'''
模块名称:@verificating_suite(SHEET_NAME)
用SHEET_NAME = "登录"来直接指定
# 环境
host: http://localhost:3000 # 测试环境
# http://localhost:3001 # 开发环境
# http://localhost:3002 # 预发布环境
# 日志
log:
file_name: test.log # 文件名
backup: 5 # 保留的日志数量
console_level: INFO # 日志输出级别
file_level: DEBUG # 日志输出级别
pattern: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # 日志输出格式
# 用例
case_control:
rule: test_*.py # 用例文件名的匹配原则,此处匹配文件名以“test”开头的“.py”类型的文件,星号“*”表示任意多个字符
# 测试用例的代码的存放路径,不填或路径不存在默认加载全部用例
path:
#/singlecase # 单接口用例存放路径
#/combincase # 联合接口用例存放路径
# 报告
report:
verbosity: # 执行用例的详细程度,不填或者填写的值大于2都为默认值2
# 0 # (静默模式): 你只能获得总的测试用例数和总的结果。
# 1 # (默认模式): 非常类似静默模式 只是在每个成功的用例前面有个“.” 每个失败的用例前面有个 “E”
2 # (详细模式):测试结果会显示每个测试用例的所有相关的信息 并且 你在命令行里加入不同的参数可以起到一样的效果
title: xxx有限公司自动化测试报告 # 报告标题,如XXX自动化测试报告
description: 操作系统:macOS 版本11.6,Google Chrome:95.0.4638.69 (正式版本) (x86_64) # 说明;比如操作系统、浏览器等版本
# 邮件
mail:
title: xxxx有限公司测试报告 # 邮件标题
message: 这是今天的测试报告,请查收! # 消息
receiver: xxxx@zyyy.com # 接受者邮箱,多个邮箱接收用;隔开,例如:xxx@zyyyy.com;yyyy@zuuu.com
server: smtp.qq.com # 服务
sender: xxxxxxxx@qq.com # 发送者邮箱
password: nyyyyyyyyyyy # 密码
path: [ "report.html", "xxx.html" ] # 报告附件路径,可传入list(多附件)或str(单个附件)
# socket 接口
socket:
ip: 127.0.0.1
port: 8080
# 测试环境数据库
test_mysql:
host: xxx.xxx.xxx.xxx # 地址
port: 3306 # 端口号
user: root # 账号
passwd: "12344x" #密码
db: xxxx_dev #连接的库名
# 本地环境数据库
local_mysql:
host: 127.0.0.1
port: 3306
user: root
passwd: "xxxxx"
db: xxxx_project
# redis数据库
redis:
#host, port, password, db, list_name
host: # 地址
port: # 端口号
password: #密码
db: # 连接的库名
list_name: # 列表名
# 通用参数
general_parameters:
charset: # 字符集类型
- utf8
- utf_8_sig
default_file_name: export #默认文件名

编辑
编辑模板4提取excel表中内容进行解析的数据详情:用例Pass后的详情:
编辑用例Fail后的详情:除了前面的基本接口信息外,若某一条接口测试用例执行失败后,会在excel测试用例模板[执行结果]的字段回写结果[Fail],测试人员会在禅道提bug,并在用例模板[对应禅道bug-id]中手动填写bugID与用例做关联,直到用例被验证通过,且脚本自动将[执行结果]的字段回写为[Pass]状态,脚本才会自动清除bugID。关于用例Fail部分的详情除了接口基本信息外,错误的追踪信息包括:A、对应禅道bugIdB、断言失败的完全路径及断言异常的具体行数C、返回断言失败原因
编辑
编辑
编辑很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel