接口测试 接口自动化测试,完整入门篇

Joo · 2017年12月01日 · 最后由 Luafair 回复于 2020年05月28日 · 15942 次阅读
本帖已被设为精华帖!

1. 什么是接口测试

顾名思义,接口测试是对系统或组件之间的接口进行测试,主要是校验数据的交换,传递和控制管理过程,以及相互逻辑依赖关系。其中接口协议分为 HTTP,WebService,Dubbo,Thrift,Socket 等类型,测试类型又主要分为功能测试,性能测试,稳定性测试,安全性测试等。
在分层测试的 “金字塔” 模型中,接口测试属于第二层服务集成测试范畴。相比 UI 层(主要是 WEB 或 APP)自动化测试而言,接口自动化测试收益更大,且容易实现,维护成本低,有着更高的投入产出比,是每个公司开展自动化测试的首选。

下面我们以一个 HTTP 接口为例,完整的介绍接口自动化测试流程:从需求分析到用例设计,从脚本编写、测试执行到结果分析,并提供完整的用例设计及测试脚本。

2. 基本流程

基本的接口功能自动化测试流程如下:
需求分析 -> 用例设计 -> 脚本开发 -> 测试执行 -> 结果分析

2.1 示例接口

接口名称:豆瓣电影搜索
接口文档地址:https://developers.douban.com/wiki/?title=movie_v2#search
接口调用示例:
1) 按演职人员搜索:https://api.douban.com/v2/movie/search?q=张艺谋
2) 按片名搜索:https://api.douban.com/v2/movie/search?q=大话西游
3) 按类型搜索:https://api.douban.com/v2/movie/search?tag=喜剧

3. 需求分析

需求分析是参考需求、设计等文档,在了解需求的基础上还需清楚内部的实现逻辑,并且可以在这一阶段提出需求、设计存在的不合理或遗漏之处。
如:豆瓣电影搜索接口,我理解的需求即是支持对片名,演职人员及标签的搜索,并分页返回搜索结果。

4. 用例设计

用例设计是在理解接口测试需求的基础上,使用 MindManager 或 XMind 等思维导图软件编写测试用例设计,主要内容包括参数校验,功能校验、业务场景校验、安全性及性能校验等,常用的用例设计方法有等价类划分法,边界值分析法,场景分析法,因果图,正交表等。
针对豆瓣电影搜索接口功能测试部分,我们主要从参数校验,功能校验,业务场景校验三方面,设计测试用例如下:

5. 脚本开发

依据上面编写的测试用例设计,我们使用 python+nosetests 框架编写了相关自动化测试脚本。可以完整实现接口自动化测试、自动执行及邮件发送测试报告功能。

5.1 相关 lib 安装

必要的 lib 库如下,使用 pip 命令安装即可:

pip install nose
pip install nose-htmloutput
pip install requests

5.2 接口调用

使用 requests 库,我们可以很方便的编写上述接口调用方法(如搜索 q=刘德华,示例代码如下):

#coding=utf-8
import requests
import json

url = 'https://api.douban.com/v2/movie/search'
params=dict(q=u'刘德华')
r = requests.get(url, params=params)
print 'Search Params:\n', json.dumps(params, ensure_ascii=False)
print 'Search Response:\n', json.dumps(r.json(), ensure_ascii=False, indent=4)

在实际编写自动化测试脚本时,我们需要进行一些封装。如下代码中我们对豆瓣电影搜索接口进行了封装,test_q 方法只需使用 nosetests 提供的 yield 方法即可很方便的循环执行列表 qs 中每一个测试集:

class test_doubanSearch(object):

    @staticmethod
    def search(params, expectNum=None):
        url = 'https://api.douban.com/v2/movie/search'
        r = requests.get(url, params=params)
        print 'Search Params:\n', json.dumps(params, ensure_ascii=False)
        print 'Search Response:\n', json.dumps(r.json(), ensure_ascii=False, indent=4)

    def test_q(self):
        # 校验搜索条件 q
        qs = [u'白夜追凶', u'大话西游', u'周星驰', u'张艺谋', u'周星驰,吴孟达', u'张艺谋,巩俐', u'周星驰,大话西游', u'白夜追凶,潘粤明']
        for q in qs:
            params = dict(q=q)
            f = partial(test_doubanSearch.search, params)
            f.description = json.dumps(params, ensure_ascii=False).encode('utf-8')
            yield (f,)

我们按照测试用例设计,依次编写每个功能的自动化测试脚本即可。

5.3 结果校验

在手工测试接口的时候,我们需要通过接口返回的结果判断本次测试是否通过,自动化测试也是如此。
对于本次的接口,我们搜索 “q=刘德华”,我们需要判断返回的结果中是否含有 “演职人员刘德华或片名刘德华”,搜索 “tag=喜剧” 时,需要判断返回的结果中电影类型是否为 “喜剧”,结果分页时需要校验返回的结果数是否正确等。完整结果校验代码如下:

class check_response():
    @staticmethod
    def check_result(response, params, expectNum=None):
        # 由于搜索结果存在模糊匹配的情况,这里简单处理只校验第一个返回结果的正确性
        if expectNum is not None:
            # 期望结果数目不为None时,只判断返回结果数目
            eq_(expectNum, len(response['subjects']), '{0}!={1}'.format(expectNum, len(response['subjects'])))
        else:
            if not response['subjects']:
                # 结果为空,直接返回失败
                assert False
            else:
                # 结果不为空,校验第一个结果
                subject = response['subjects'][0]
                # 先校验搜索条件tag
                if params.get('tag'):
                    for word in params['tag'].split(','):
                        genres = subject['genres']
                        ok_(word in genres, 'Check {0} failed!'.format(word.encode('utf-8')))

                # 再校验搜索条件q
                elif params.get('q'):
                    # 依次判断片名,导演或演员中是否含有搜索词,任意一个含有则返回成功
                    for word in params['q'].split(','):
                        title = [subject['title']]
                        casts = [i['name'] for i in subject['casts']]
                        directors = [i['name'] for i in subject['directors']]
                        total = title + casts + directors
                        ok_(any(word.lower() in i.lower() for i in total),
                            'Check {0} failed!'.format(word.encode('utf-8')))

    @staticmethod
    def check_pageSize(response):
        # 判断分页结果数目是否正确
        count = response.get('count')
        start = response.get('start')
        total = response.get('total')
        diff = total - start

        if diff >= count:
            expectPageSize = count
        elif count > diff > 0:
            expectPageSize = diff
        else:
            expectPageSize = 0

        eq_(expectPageSize, len(response['subjects']), '{0}!={1}'.format(expectPageSize, len(response['subjects'])))

5.4 执行测试

对于上述测试脚本,我们使用 nosetests 命令可以方便的运行自动化测试,并使用插件生成 html 格式测试报告。
运行命令如下:
nosetests -v test_doubanSearch.py:test_doubanSearch --with-html --html-file=TestReport.html

5.5 发送邮件报告

测试完成之后,我们可以使用 smtplib 模块提供的方法发送 html 格式测试报告。基本流程是读取测试报告 -> 添加邮件内容及附件 -> 连接邮件服务器 -> 发送邮件 -> 退出,示例代码如下:

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def send_mail():
    # 读取测试报告内容
    with open(report_file, 'r') as f:
        content = f.read().decode('utf-8')

    msg = MIMEMultipart('mixed')
    # 添加邮件内容
    msg_html = MIMEText(content, 'html', 'utf-8')
    msg.attach(msg_html)

    # 添加附件
    msg_attachment = MIMEText(content, 'html', 'utf-8')
    msg_attachment["Content-Disposition"] = 'attachment; filename="{0}"'.format(report_file)
    msg.attach(msg_attachment)

    msg['Subject'] = mail_subjet
    msg['From'] = mail_user
    msg['To'] = ';'.join(mail_to)
    try:
        # 连接邮件服务器
        s = smtplib.SMTP(mail_host, 25)
        # 登陆
        s.login(mail_user, mail_pwd)
        # 发送邮件
        s.sendmail(mail_user, mail_to, msg.as_string())
        # 退出
        s.quit()
    except Exception as e:
        print "Exceptioin ", e

6. 结果分析

打开 nosetests 运行完成后生成的测试报告,可以看出本次测试共执行了 51 条测试用例,50 条成功,1 条失败。

失败的用例可以看到传入的参数是:{"count": -10, "tag": "喜剧"},此时返回的结果数与我们的期望结果不一致(count 为负数时,期望结果是接口报错或使用默认值 20,但实际返回的结果数目是 189。赶紧去给豆瓣提 bug 啦- -)

7. 完整脚本

豆瓣电影搜索接口的完整自动化测试脚本,我已上传到的 GitHub。下载地址:https://github.com/lovesoo/test_demo/tree/master/test_douban

下载完成之后,在当前脚本目录下,使用如下命令即可进行完整的接口自动化测试并使用邮件发送最终测试报告(执行命令之前,别忘了修改脚本中邮件发件人及收件人信息为你自己的实际信息):
python test_doubanSearch.py

最终发送测试报告邮件,截图如下:

8. 参考资料

1) requests: http://cn.python-requests.org/zh_CN/latest/
2) nosetests: http://nose.readthedocs.io/en/latest/testing.html
3) nose-html-reporting: https://pypi.python.org/pypi/nose-html-reporting

9. Python 3 版本

本文是基于 Python 2.7 编写,因为有的小伙伴使用的 Python 3。特地修改了一个 Python3 的版本:https://github.com/lovesoo/test_demo/blob/master/test_douban/test_doubanSearch_py3.py

获取更多信息,欢迎加入测试开发交流群:25452556

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
最佳回复
Joo #1 · 2017年12月04日 Author
donly 回复

修改了一个 Python3 的版本,代码上传到了 GitHub:https://github.com/lovesoo/test_demo/blob/master/test_douban/test_doubanSearch_py3.py

由于原有插件 nose-html-reporting 不兼容 Python3,使用了新的插件 nose-htmloutput (https://pypi.python.org/pypi/nose-htmloutput),可以使用如下命令安装:
pip install nose-htmloutput

直接在脚本目录下,使用如下命令运行即可(当然执行测试之前,别忘了修改脚本中邮件发件人及收件人信息为你自己的实际信息):
python test_doubanSearch_py3.py

最终执行结果如下:

有问题的话,欢迎再提问~

共收到 70 条回复 时间 点赞
匿名 #1 · 2017年12月01日

这样写用例,如果后续接口变动会不会维护很麻烦

学习了!

Joo #3 · 2017年12月01日 Author

不会啊,其实可以借鉴 WEB 自动化测试中 page-object 模式,我们设计接口自动化测试的时候可以对接口的调用做封装,如果接口变动了,比如增加了一些参数,只需维护封装好的调用方法就行了。

思寒_seveniruby 将本帖设为了精华贴 12月01日 23:51

不错的入门文章,连夜进行加精。

学习了!!

学习了!!

Joo #8 · 2017年12月04日 Author

感谢版主😀

Joo #9 · 2017年12月04日 Author
thanksdanny 回复

一起学习~😀

Joo #10 · 2017年12月04日 Author
艾里艾兰 回复

💪

不错的实践

学习了!!,写的蛮详细

执行的时候提示没有--with-html 这个选项?为嘛呢?
直接运行脚本的话,也没有任何报告输出,似乎都没有执行测试。。。
这边环境是 python3 的

Joo #14 · 2017年12月04日 Author
donly 回复

pip install nose
pip install nose-html-reporting
pip install requests
这些 lib 安装了吗,代码必须要通过 nosetests 来运行的~

看了下 nosetests -help,真的没有--with-html 和--html-report 这两个选项。。。。。

Joo 回复

肯定是都有安装的啦
刚刚试过把--with-html 和--html-report 改成了--with-xunit --xunit-file,可以运行了,但是一堆报错啊。。。难道说,nosetests 不支持 py3 吗??
基本都是这样的错

nosetests -h 的部分选项


这是安装后的包

Joo #18 · 2017年12月04日 Author
donly 回复

我安装下 Python 3 看看 ,这个 demo 用的是 Python 2.7~

Joo 回复

我把所有的 encode('utf-8') 都注释掉了,然后就可以运行了。只是有报错。可能还是哪里编码不对吧。
对了,代码开头的 reload 和设置 utf-8 的两句,我也注释掉了。。

Joo #1 · 2017年12月04日 Author
donly 回复

修改了一个 Python3 的版本,代码上传到了 GitHub:https://github.com/lovesoo/test_demo/blob/master/test_douban/test_doubanSearch_py3.py

由于原有插件 nose-html-reporting 不兼容 Python3,使用了新的插件 nose-htmloutput (https://pypi.python.org/pypi/nose-htmloutput),可以使用如下命令安装:
pip install nose-htmloutput

直接在脚本目录下,使用如下命令运行即可(当然执行测试之前,别忘了修改脚本中邮件发件人及收件人信息为你自己的实际信息):
python test_doubanSearch_py3.py

最终执行结果如下:

有问题的话,欢迎再提问~

发件人邮箱登陆密码要设置为邮箱的授权码才行,楼主!

Joo #22 · 2017年12月05日 Author
qingtian 回复

我直接用账号密码就可以的 网易 163 邮箱

Joo 回复

速度👍
有一点小问题,nose_html_reporting 的 init 中,引入了 StringIO,在 py3 中,没有这个模块了。应该引入 io,使用 io.StringIO 就可以了。
另外,我看邮件中中文是正常显示的。但是如果直接打开 html 文件的话,中文却是乱码的哦,这个怎么破呢?

另外,关于授权码,163 邮箱可以打开或关闭授权码,开了就得使用授权码,没开就使用密码。QQ 的话,好像就必须使用授权码了。

Joo #24 · 2017年12月05日 Author
donly 回复

是的,之前插件的问题就是 StringIO,改插件源码相对比较麻烦,所以直接换了另外的插件了。
直接打开 Html 文件乱码,我这边使用 Chrome 浏览器看了是正常的。不知道你是用的什么浏览器,猜测应该是需要修改下页面的编码?(右键修改编码为 UTF-8)

授权码的问题,应该就是你说的那样~👍

mark,学习了

Joo 回复

我也是 chrome,firefox 也打开看了,都是乱码。。。
右键看编码已经是 utf-8 了

另外,刚刚运行第一遍的时候基本都是正确的,后面改了 io,好多失败的。。只看 test_q 这一个,发现检查 (大话西游)的时候失败了。但是我看返回的响应里面都是 大话西游之 xxx 之类的。不知道是不是那个检查的语句问题,还是我这边的其他问题。

Joo #27 · 2017年12月05日 Author

报错应该是导出 html 报告插件的 Python 3 兼容性问题,麻烦看下上面的置顶帖(又...换了个插件)。

搜索 “周星驰,大话西游” 报错,是因为返回的第一个结果是 “西游降魔篇”,结果校验时这里我目前只校验了第一个结果与搜索词是否匹配,“西游降魔篇” 与 “大话西游” 不匹配,所以报错了。

装了新插件啦。并且也执行了 pip uninstall nose-html-reporting。。

Joo #29 · 2017年12月05日 Author
donly 回复

还有问题嘛~现在😂

Joo 回复

除了报告乱码,暂时没有其他问题。
剩下的就是好好来消化你这些代码了。才开始学习接口自动化,nosetest 这个框架也不熟悉,json 也不熟悉,所以要学习的很多呢!!多谢你给指了一个方向!

Joo #31 · 2017年12月05日 Author
donly 回复

额,报告乱码的问题我已经解决了啊,看我最新上传的代码和置顶的回复贴~😄

Joo 回复

那就没有问题了。。。是我没有重新下载 py 代码导致。。

Joo #33 · 2017年12月05日 Author
donly 回复

OK~👌


这同一个 test 的 view 点击后都是展开第一个的 div,尤其那种失败的没法看详情啊

Joo #35 · 2017年12月05日 Author
fmhying 回复

我这边看了下是 OK 的~也有可能是 nose-html-reporting 插件的 bug~

还是换 nose-htmloutput 这个插件吧😂

Joo C/S 架构系统自动化测试入门 中提及了此贴 12月06日 14:54
37楼 已删除

好了,万分感谢~~~

楼主,能说下 yield 的作用么?我没理解到!

Joo #40 · 2017年12月11日 Author
qingtian 回复

可以参考我之前写的博客文章:http://www.cnblogs.com/lovesoo/p/7735380.html
官方文档:http://nose.readthedocs.io/en/latest/writing_tests.html

简单来说就是 Nose 支持测试方法或函数是生成器(使用 yield 关键字定义),Nose 每次执行测试时会迭代函数生成器,为每一个 yiled 的 tuple 创建一个测试用例。

Joo 回复

我的天啊,我是刚学 python,我一直再看 python 的 yield,看的一脸懵逼,我还想呢返回个元组干啥。。。。
nose 有啥好的中文资料吗?
还有个问题,我看网上说 pytest 好像更好一些,这个一般怎么选择呢,谢谢

Joo #42 · 2017年12月14日 Author
Pastel 回复

框架的选择具体可以参考如下文章:
https://blog.zengrong.net/post/2170.html
https://wiki.python.org/moin/PythonTestingToolsTaxonomy
就我自己而言,nose 是最简单的,而且与 pycharm 配合特别好用~😳

python 2.7.12 运行后 cmd 中的中文乱码,生成报告也乱码?有人遇到没?

看起来很不错,Python 的接口测试

#20 楼 @lovesoo 楼主,真厉害👍👍👍

—— 来自 TesterHome 官方 安卓客户端

Joo #46 · 2017年12月20日 Author
qigao 回复

这个应该是 cmd 的中文编码不是 utf-8 编码导致的吧~建议用 pycharm 运行看看呢😅

Joo #47 · 2017年12月20日 Author
you 回复

💪 💪 💪 相互学习,共同进步!

{
"msg": "rate_limit_exceeded2: 183.12.27.100",
"code": 112,
"request": "GET /v2/movie/search"
}

112 rate_limit_exceeded2 IP 访问速度限制
尴尬,访问这个豆瓣接口次数多,IP 会被封禁?

Joo #49 · 2017年12月21日 Author
kilalonger 回复

嗯,我也遇到过,应该是豆瓣做的安全处理,这里只是举例用的。
可以换成你们自己项目的接口哈~


楼主你好 我导入结果校验的 code 怎么提示 “eq_” 没有定义 这个是内置函数吗

Python 环境 2.7.10 Windows 系统
已经安装
pip install nose
pip install nose-html-reporting
pip install requests

Joo #51 · 2017年12月26日 Author

没有 from nose.tools import *

Joo 回复

还是打算咨询下,为啥这个 nose-htmloutput 插件,运行 pass 的,详情看不了。
只有 fail 的才看的到详情。

Joo #53 · 2017年12月27日 Author
kilalonger 回复

现在用的这个插件就是不会显示 pass 用例的详细日志的,如果你是 Python2.7 的话,可以用之前我推荐的 nose-html-reporting。
另外如果你使用 Pycharm 的话,也可以使用 Pycharm 自带的测试报告导出功能,非常好用的~

Joo 回复

谢谢 导入之后就好了

requests.exceptions.SSLError: HTTPSConnectionPool(host='api.douban.com', port=443): Max retries exceeded with url: /v2/movie/search?q=big (Caused by SSLError(SSLError(1, u'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)'),))

昨天成功过,今天为啥又一直报这个错了
Python -m requests.help ( "using_pyopenssl": false )
https://github.com/requests/requests/issues/4246
看了这篇文章 人家说重新装了 OpenSSL 就好了 我重新装了 还是没有好 按照这篇文章装的http://www.cnblogs.com/aveenzhou/p/3912539.html

而且我网页是可以拿到接口收据的 也不至于说豆瓣把我限制了

Joo 回复

明白了


楼主你好 我改成 奇艺的搜索,单个测试就可以

使用 nose 命令行测试就又报 豆瓣 SSL 验证错误 ,实际我 url 都改为 奇艺了
@staticmethod
def search(params, expectNum=None):
url = 'http://suggest.video.iqiyi.com'
r = requests.get(url, params=params)
print 'Search Params:\n', json.dumps(params, ensure_ascii=False)
print 'Search Response:\n', json.dumps(r.json(), ensure_ascii=False, indent=4)

def test_q(self):
# 校验搜索条件 q
qs = [u'白夜追凶.', u'大话西游', u'周星驰', u'张艺谋', u'周星驰,吴孟达', u'张艺谋,巩俐', u'周星驰,大话西游', u'白夜追凶,潘粤明']
for q in qs:
params = dict(key=q)
f = partial(test_doubanSearch.search, params)
f.description = json.dumps(params, ensure_ascii=False).encode('utf-8')
yield (f,)

Joo #58 · 2017年12月28日 Author
moonshan 回复

要看下完整的代码才可以帮你查问题,不知道你是如何执行用例的呢

Joo 回复

好的 代码我怎么给你 代码全部都是粘贴的你这个页面的 我就改了个 url
我感觉可能是不是我的环境的问题 只要是 https 的就报 SSL 验证错误 但是前几天我调试的时候 是好的

Joo 回复

我就是在 Eclipse 的工作空间 当前项目下执行的这个命令 nosetests -v test_doubanSearch.py:test_doubanSearch --with-html --html-file=TestReport.html
文件名 test_doubanSearch.py 类名 test_doubanSearch 是这样的跑的吗 之前是写 java 的 Python 不怎么会 也不会调试😂

r = requests.get(url, params = params, verify=True ); 刚把 verify=True 添加上 https 的也可以了 单个调试的时候 ,现在就是不知道怎么跑你那个自动化的脚本😭

Joo #62 · 2017年12月28日 Author
moonshan 回复

nosetests -v test_doubanSearch.py:test_doubanSearch --with-html --html-file=TestReport.html

前面的 nosetests -v test_doubanSearch.py:test_doubanSearch 的意思是执行 test_doubanSearch.py 文件下的 test_doubanSearch 测试类

你调试时,可以改成
nosetests -s test_doubanSearch.py:test_doubanSearch.test_q

其中 -s 是打印所有输出,执行 test_doubanSearch.py 文件下的 test_doubanSearch 测试类下的 test_q 测试方法

详见文档:http://nose.readthedocs.io/en/latest/usage.html

另外,如果写 python 脚本的话,还是建议用 PyCharm

PyCharm 运行 Nosetests 并导出测试报告方法: http://lovesoo.org/pycharm-run-nosetests-and-exports-test-report.html

Joo 回复

好的 谢谢了 我再看看

仅楼主可见
Joo #65 · 2018年01月03日 Author
speng 回复

自己直接改下代码中的 report_file 参数就行了

MARK 学习下

仅楼主可见
Joo · #68 · 2018年08月07日 Author
仅楼主可见
simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
仅楼主可见
Joo #71 · 2018年12月19日 Author
Yellow 回复

这个链接是豆瓣官方的 api 文档,现在应该是失效了。
但是接口还是可以调通,只是看不了文档了...

仅楼主可见
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08

学习学习

楼主能告诉下为什么报这个错呢,是哪里没定义类型吗

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册