接口测试是单元测试的一个子集, 但又不等同于单元测试. 从测试的角度
来看, 接口测试的价值在于其测试投入比单元测试少, 而且技术难度也比单元测
试小. 一般来说, 接口测试的粒度要比单元测试更粗, 它主要是基于子系统或者
子模块的接口层面的测试. 因此, 接口测试需要测试的接口或者函数的数量会远
远小于单元测试, 与此同时, 接口定义的稳定性会远远高于类级别的函数. 所以,
接口测试用例代码的改动量也远远小于单元测试, 代码维护成本会比单元测试少
很多, 因而测试的投入量会小很多. 从另外一个层面来看, 借助于接口测试, 可
以保证子系统或子模块在各种应用场景下接口调用的正确性, 那么子系统或子模
块的产品质量也可以得到充分的保证. 因此, 接口测试是一种适度的白盒测试技
术, 准确说它是一种灰盒测试, 投入产出是非常理想的.
总的来说, 接口测试是保证高复杂性系统质量的内在要求和低成本的经
济利益的驱动作用下的最佳解决方案. 主要体现在下面的二个方面:
首先, 接口测试节省了测试成本.
其次, 接口测试不同于传统开发的单元测试, 接口测试是站在用户的角
度对系统接口进行全面高效持续的检测.
(ps: 以上有网络搜索和自己的总结)
好了, 话不多说, 接下来我们测试接口的整体思路是这样的:
1, 把接口的相关数据保存在 json 文件中.
2, 我们通过去读这个 json 文件, 来读取接口的相关信息, 包括: 路径, 参数, 请求方式, 比对值等.
3, 基于 requests 封装一个发送请求的方法.
4, 使用装饰器来进行具体的测试.
- {
- "test_1_ip_api": {
- "url": "http://httpbin.org/ip",
- "assert": {
- "origin":"183.16.188.172"
- },
- "method": "get",
- "params": {
- "a":"b"
- },
- "case": "测试 httpbin 的 ip 接口返回正常"
- }
- }
以上就是 Json 文件的格式, 一条这样的数据, 代表一条用例:
test_1_ip_api: 算作是一个标识.
url: 接口的路径.
assert: 最后接口的比对值.
method: 接口请求方式.
params: 请求参数. ps: 如果请求方式为 post 此处为 data.
case: 此条用例的名字, 也就是测试的点.
(注: 有些可能需要添加 headers, 这个用户自己添加一下就行了, 但是后面的代码稍微修改一下.)
当然有了文件, 那么我们就需要把这个数据给读出来. 见如下代码.
- class ReadCaseData(object):
- def __init__(self,filename):
- self.filename = filename
- self.path = os.path.join(get_super_path(),'data',self.filename)
- @property
- def read_case_json(self) -> dict:
- '''
获取指定文件的 json 数据
- :return:
- '''
- try:
- my_json = open(self.path,encoding='utf-8')
- json_data = json.load(my_json)
- return json_data
- except Exception as err:
- print(str(err))
- def get_case_content(self,first_key,second_key):
- '''获取 json 相应值'''
- return self.read_case_json[first_key][second_key]
- def get_assert_keys(self,first_key):
- '''根据用例名取 assert 的 keys'''
- return self.get_case_content(first_key,'assert').keys()
- def get_assert_value(self,first_key,assert_key):
- '''取 assert 的具体值'''
- return self.read_case_json[first_key]['assert'][assert_key]
- def get_case_name(self,first_key):
- '''获取用例名'''
- return self.get_case_content(first_key,'case')
以上就是读取 json 文件的各种数据, 并根据需要封装了一些方法.(思路: 会根据不同的 json 文件数据, 给这个 json 文件取相应的文件名, 那么此处的类就是把这个文件名初始化, 然后读取此文件相关的一些数据.)
(注: 1,get_super_path() 是写的一个获取根目录的方法.)
那么数据读出来了, 要怎么运用呢, 那么我们就需要借用一下 requests(这个库应该不用说了, 您如果在找这样的文章, 那么我肯定知道这个库罗.).
- def get_base_url():
- if IniHelper('server.ini').get_value('server', 'select') == 'test':
- return IniHelper('server.ini').get_value('server', 'test')
- elif IniHelper('server.ini').get_value('server', 'select') == 'formal':
- return IniHelper('server.ini').get_value('server', 'formal')
- else:
- raise AttributeError("配制文件读取出错! 请检查!")
- class Myrequests(ReadCaseData):
- def __init__(self, filename, case_name):
- ReadCaseData.__init__(self, filename)
- self.case_name = case_name
- self.base_url = get_base_url()
- def make_requests_template(self):
- '''
根据读取到的 json 文件中的 method, 来发送不到的请求, 目前只能发送 get 和 post 请求
- :return:
- '''if self.get_case_content(self.case_name,'method').lower() =='get':
- body = {}
- body['url'] = self.base_url + self.get_case_content(self.case_name, 'url')
- body['params'] = self.get_case_content(self.case_name, 'params')
- return self.get(**body)
- elif self.get_case_content(self.case_name, 'method').lower() == 'post':
- body = {}
- body['url'] = self.base_url + self.get_case_content(self.case_name, 'url')
- body['params'] = self.get_case_content(self.case_name, 'data')
- return self.post(**body)
- else:
- raise AttributeError("错误的请求方法, 请检查配置文件中的请求方法, 目前只支持 ['GET','POST']")
- def get(self, **kw):
- '''
使用 requests 发送 get 请求
:param kw: 参数需要传一个字典表
- :return:
- '''
- return requests.get(**kw)
- def post(self, **kw):
- '''
使用 requests 发送 post 请求
:param kw: 参数需要传一个字典表
- :return:
- '''
- return requests.post(**kw)
- @property
- def get_json(self):
- '''
返回接口的 json 数据
- :return:
- '''
- try:
- return self.make_requests_template().json()
- except Exception as e:
- print('json format error' + str(e))
- @property
- def get_status_code(self):
- '''
返回接口发送后的 status_code 值
- :return:
- '''
- return self.make_requests_template().status_code
这里首先是继承了我们刚刚封装的读取的 json 文件的类, 因为继承过后就可以使用他的一些方法, 然后通过读取里面的数据来发送请求等.
注: 我这里的接口的服务器地址是通过读取配置文件里的, 这个代码就不分享了, 大家网找一下, 很多.(随意啦)
ps:IniHelper 是封装的一个读取配置文件的类.
数据也读出来了, 发送请求的方法也封装好了, 那么接下来要怎么用这些数据来测试呢?
- def test_case_run(data_file_name, test_case_key):
- def _test_case_run(func):
- def wrap(self):
- r = Myrequests(filename=data_file_name, case_name=test_case_key)
- self.r = r
- self._testMethodDoc = r.get_case_name(test_case_key)
- self.status_code = r.get_status_code
- self.json = r.get_json
- self.assert_key = r.get_assert_keys(test_case_key)
- log.get_log(test_case_key).info(f'开始测试:{test_case_key}')
- log.get_log(test_case_key).info('比对: status_code')
- self.assertEqual(r.get_status_code, 200)
- for key in r.get_assert_keys(test_case_key):
- log.get_log(test_case_key).info(f'比对:{key}')
- self.assertEqual(get_dict_value(key, **r.get_json), r.get_assert_value(test_case_key, key))
- func(self)
- return wrap
- return _test_case_run
以上就是根据读取的数据和发送请求的方法封装的一个装饰器.
其中:
log: 这个是基于 logging 封装的记录 log 的类.
get_assert_value(): 这个是写的一个根据 json 数据里读出来的 assert 的数据里面的 Key, 来获取实际接口里面返回的 value 的方法.
(注: 1,json 里面的 assert 的数据至少一条, 有可能多条. 2, 使用 Key 从实际结果中获取 value 时, 可能是多级字典, 因此此方法使用了递归)
最后就是我们使用 python 的 unittest 来进行测试了.
- from src.testcase.method.base_test import BaseTest
- from src.testcase.method import wraps
- import unittest
- class TestApi(BaseTest):
- @wraps.test_case_run('data.json', 'test_1_ip_api')
- def test_01(self):
- self._testMethodDoc = self._testMethodDoc
- @wraps.test_case_run('data.json', 'test_2_headers_api')
- def test_02(self):
- self._testMethodDoc = self._testMethodDoc
- @wraps.test_case_run('data.json', 'test_3_post_api')
- def test_03(self):
- self._testMethodDoc = self._testMethodDoc
- if __name__ == '__main__':
- unittest.main(verbosity=2)
以上就是实际具体的用例, 我们在每一条 case 上使用装饰器, 然后装饰器有二个参数, 这里说明一下: 1, 就是此 case 对应的文件名. 2, 就是此 case 在 json 文件中对应的标识.
建议:
1, 每一个接口对应一个 json 文件, 然后在这个 json 文件中为每一条 case 添一个标识.(文中的 Json 文件就是一个接口对应的一条 case.)
2, 所有的 json 文件放在同一个文件夹下.
3, 每一个接口对应一个测试文件.
4, 结果最后采用 htmltestrunner 的 html 报告输出.
5, 把报告通过 email 发送.
6, 使用 jenkins 在服务器部署.
欢迎大家指证!!!
来源: https://www.cnblogs.com/Alin-2016/p/8891471.html