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

Posted 程序员老波

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口自动化测试,完整入门篇相关的知识,希望对你有一定的参考价值。

目录

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-html-reporting
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命令可以方便的运行自动化测试,并可使用nose-html-reporting插件生成html格式测试报告。

运行命令如下:

nosetests -v test_doubanSearch.py:test_doubanSearch --with-html --html-report=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。下载地址:test_demo/test_douban at master · lovesoo/test_demo · GitHub

下载完成之后,使用如下命令即可进行完整的接口自动化测试并通过邮件发送最终的测试报告:

python test_doubanSearch.py

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

8. 参考资料

1) requests: Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档

2) nosetests: Testing with nose — nose 1.3.7 documentation

3) nose-html-reporting: nose-html-reporting · PyPI

走进Java接口测试之测试框架TestNG数据驱动(入门篇)

前言

我们在前面的文章中,和大家分享过接口自动化测试一些基本的实现方法,但是,你很快就会发现,如果在测试脚本中硬编码测试数据的话,测试脚本灵活性会非常低。而且,对于那些具有重复的请求,而只是测试入参不同的用例来说,就会存在大量重复的代码。那么怎么把自己从简单、重复的工作中解放出来呢?这个时候我们应考虑把测试数据和测试脚本分离,也就是说数据驱动

数据驱动的优势?

  • 数据驱动很好地解决了大量重复脚本的问题,实现了“测试脚本和数据的解耦”。目前几乎所有主流的自动化测试工具和框架都支持。

  • 数据驱动测试的数据不仅可以包括测试输入数据,还可以包含测试验证结果数据,甚至可以包含测试逻辑分支的控制变量。

  • 数据驱动的测试思想不仅适用于接口测试,也适合与单元测试,UI自动化测试,性能测试等

常见提供数据的方式?

  • 硬编码

  • txt文件

  • Json

  • Yaml

  • 配置文件properties

  • execl

  • db

  • 网络中

数据驱动的原理?

测试脚本中通过 data provider 去数据源中读取一行数据,赋值给相应的变量,执行用例。接着再去文件中读取下一行数据,读取完所有的数据后,测试结束。参数化文件中有几行数据,测试用例就会被执行几次。如图所示:\'走进Java接口测试之测试框架TestNG数据驱动(入门篇)_testNG\'

TestNG如何实现?

我们可以在每个测试方法上使用任意数量的参数,并指示 TestNG 使用 @Parameters 注释传递正确的参数。

TestNG有两种方法可以设置这些参数(@Factory 数据工厂不在此介绍):

  • 使用 testng.xml \'走进Java接口测试之测试框架TestNG数据驱动(入门篇)_testNG_02\'

  • DataProvider \'走进Java接口测试之测试框架TestNG数据驱动(入门篇)_testNG_03\'

\'走进Java接口测试之测试框架TestNG数据驱动(入门篇)_testNG_04\' 注意:

  • TestNG.xml 中的参数可以是套件或测试级别;

  • DataProvider 中的参数可以将 Method 和 ITestContext 作为参数。

testng.xml 中的参数

如果简单参数,则可以在 testng.xml 中指定它们,在以下代码中,我们指定的参数 name 和 age 值。此 XML 参数在 testng.xml 中 定义:

  1. <suite name="parameter">

  2. <test name="param">

  3. <parameter name="name" value="zhangsan"/>

  4. <parameter name="age" value="10"/>

  5.  

  6. <classes>

  7. <class name="com.zuozewei.springboottestngdatadrivendemo.paramter.ParamterTest"/>

  8. </classes>

  9. </test>

  10. </suite>

测试方法将分别接收参数 name 和 age 的值。

  1. @Slf4j

  2. public class ParamterTest {

  3.  

  4. @Test

  5. @Parameters({"name","age"})

  6. public void paramTest(String name,int age){

  7. log.info("name = [{}] ; age = [{}]" ,name,age);

  8. }

  9.  

  10. }

注意 @Parameters 可以被放置在下列位置:

  • 在任何已经有 @Test,@Before/After 或 @Factory 注释的方法上;

  • 最多只有一个测试类的构造函数。在这种情况下,TestNG 将调用此特定构造函数,并在需要实例化测试类时将参数初始化为 testng.xml 中指定的值。此功能可用于将类中的字段初始化为测试方法随后将使用的值。

  1. @Parameters({ "name", "age" })

  2. @BeforeMethod

  3. public void beforeTest(String name, String age) {

  4. m_name = name; // 查询数据源值

  5. m_age = age;

  6. }

注意:

  • XML 参数按照与注释中相同的顺序映射到 Java 参数,如果数字不匹配,TestNG 将报错;

  • 参数是存在作用域的。在 testng.xml 中,可以在 suite 标记下或 test 下声明它们 。如果两个参数具有相同的名称,则它是 test 中定义的具有优先权。如果需要指定适用于所有测试的参数并仅为某些测试覆盖其值,这将非常方便。

使用 DataProviders 的参数

如果需要传递复杂参数或需要从 Java 创建的参数(复杂对象,从文件或数据库读取的对象等等),则在 testng.xml 中指定参数可能不够。在这种情况下,可以使用数据提供程序提供测试所需的值。数据提供程序是类上的一个方法,它返回一组对象数组。此方法使用 @DataProvider 注释。

简单使用

@DataProvider函数,需要定义属性 name:

  1. @DataProvider(name="data")

  2. public Object[][] providerData(){

  3. Object[][] objects = new Object[][]{

  4. {"zhangsan",10},

  5. {"lisi",20},

  6. {"wangwu",30}

  7. };

  8.  

  9. return objects;

  10. }

@Test 测试用例,属性 dataProvider 需要指定对应的数据提供者名称。

  1. @Test(dataProvider = "data")

  2. public void testDataProvider(String name,int age){

  3. log.info("name = [{}] ; age = [{}]" ,name,age);

  4. }

执行结果:

  1. name = [zhangsan] ; age = [10]

  2. name = [lisi] ; age = [20]

  3. name = [wangwu] ; age = [30]

  4.  

  5. ===============================================

  6. Default Suite

  7. Total tests run: 3, Failures: 0, Skips: 0

  8. ===============================================

@DataProvider函数插入参数使用

@DataProvider 函数可以插入 Method 和 ITestContext 类型参数,这两个参数里面可以获取很多有用的信息。

@DataProvider函数:

  1. @DataProvider(name="methodData")

  2. public Object[][] methodDataTest(Method method){

  3. Object[][] result=null;

  4.  

  5. if(method.getName().equals("test1")){

  6. result = new Object[][]{

  7. {"zhangsan",20},

  8. {"lisi",25}

  9. };

  10. }else if(method.getName().equals("test2")){

  11. result = new Object[][]{

  12. {"wangwu",50},

  13. {"zhaoliu",60}

  14. };

  15. }

  16.  

  17. return result;

  18. }

@Test 测试执行脚本:

  1. @Test(dataProvider = "methodData")

  2. public void test1(String name,int age){

  3. log.info("test111方法: name = [{}] ; age = [{}]" ,name,age);

  4. }

  5.  

  6. @Test(dataProvider = "methodData")

  7. public void test2(String name,int age){

  8. log.info("test222方法: name = [{}] ; age = [{}]" ,name,age);

  9. }

执行结果:

  1. test111方法: name = [zhangsan] ; age = [20]

  2. test111方法: name = [lisi] ; age = [25]

  3. test222方法: name = [wangwu] ; age = [50]

  4. test222方法: name = [zhaoliu] ; age = [60]

  5.  

  6. ===============================================

  7. Default Suite

  8. Total tests run: 7, Failures: 0, Skips: 0

  9. ===============================================

延迟数据提供者

有的场景我们需要大量参数进行读取,比如参数数据源是 DB,而数据达到百万级,这样测试程序遍历所有数据时,可能就会导致内存溢出,

那么我们怎样解决这个问题?当我们获取了一条数据,对它执行测试方法,然后就废弃这个数据对象,再测试下一个书。这个原则是延迟初始化,这个思想就是当你真正需要一个对象时才创建它,而不是提前创建它。

为了实现这种方法,TestNG 允许我们从数据提供者返回一个 Iterator 对象,而不是一个二维对象数组。

Iterator 是 java.util 包中的一个接口,它的方法签名如下:

  1. public interface Iterator<E> {

  2.  

  3. boolean hasNext();

  4. E next();

  5. default void remove();

  6.  

  7. }

它可以通过 next 调用下一组数据,这样就有机会在最后一刻实例化相应的对象,即刚好在需要在这些参数的测试方法被调用之前。

下面例子是重写后的例子,我们实现了一个 Iterator,它将返回 4 个带有不同ID的对象:

  1. public class AccoutIterator implements Iterator {

  2.  

  3. private int index =0;

  4. static private final int MAX =4;

  5.  

  6. @Override

  7. public boolean hasNext() {

  8. return index < MAX;

  9. }

  10.  

  11. @Override

  12. public Object next() {

  13. return new Object[]{

  14. //这里就是放入要实现的对象或者一组数据

  15. "延迟数据提供:"+ (index++)

  16. };

  17. }

  18.  

  19. @Override

  20. public void remove() {

  21. throw new UnsupportedOperationException("remove");

  22. }

@DataProvider函数调用:

  1. @DataProvider(name = "iterator")

  2. public Iterator<Object[]> iteratorDataProvider(){

  3. return new AccoutIterator();

  4.  

  5. }

@Test测试运行函数:

  1. @Test(dataProvider = "iterator")

  2. public void testcase2(String name){

  3. log.info(" name = [{}] " ,name);

  4. }

运行结果:

  1. name = [延迟数据提供:0]

  2. name = [延迟数据提供:1]

  3. name = [延迟数据提供:2]

  4. name = [延迟数据提供:3]

  5.  

  6. ===============================================

  7. Default Suite

  8. Total tests run: 4, Failures: 0, Skips: 0

  9. ===============================================

其他的高级玩法

数据提供程序可以与并行属性并行运行:

  1. @DataProvider(parallel = true)

  2. // ...

从 XML 文件运行的并行数据提供程序共享相同的线程池,默认情况下大小为 10。可以在 XML 文件的 suite 标记中修改此值:

  1. <suite name="Suite1" data-provider-thread-count="20" >

如果要在不同的线程池中运行几个特定的数据提供程序,则需要从其他 XML文件运行它们。

小结

这篇的知识点:

  • 需要参数化来创建数据驱动测试;

  • TestNG 支持两种参数化,使用 @Parameter + TestNG.xml 并使用 @DataProvider;

  • 在 @Parameter + TestNG.xml中,参数可以放在套件级别和测试级别。如果在两个地方声明相同的参数名称,测试级别参数将优先于套装级别参数;

  • 使用 @Parameter + TestNG.xml,一次只能设置一个值,但 @DataProvider 返回一个2维的 Object 数组;

  • 如果 DataProvider 存在于不同的类中,那么测试方法所在的类,DataProvider 应该是静态方法;

  • 有通过支持两个参数的 DataProvider 的方法和 ITestContext;

  • TestNG 允许我们从数据提供者返回一个 Iterator 对象,实现延迟提供数据。

当然,DataProvider 只是从行为操作上分离了数据的提供方式,没有从根本上解决自动化测试中测试数据本身的稳定性、快速响应变化、数据丢失、数据被修改的这些难点和阻碍:

  • 比如生产数据库里的数据导入并刷新测试数据库,之前用例里使用的数据被覆盖;

  • 比如几个小组在一个系统里使用同一个测试数据库,AB组使用存在交叉,B组还要把数据改变一下再用,或者B组用完后测试数据已经发生改变;

  • 比如使用的测试数据具备时效性,状态会改变的,从 active 变成 inactive 的等;

自动化测试的其他方面都不是什么大问题,最主要的阻碍就是测试数据本身(特别是在真实的测试环境上时)。

 

本文源码:

https://github.com/7DGroup/Java-API-Test-Examples/tree/master/springboot-testng-data-driven-demo

以上是关于接口自动化测试,完整入门篇的主要内容,如果未能解决你的问题,请参考以下文章

新入职测试岗,我需要怎样快速熟悉项目业务?总结......

自动化测试工具Cucumber的简单介绍,入门篇

web自动化测试入门篇03——selenium使用教程

清华学长熬夜15天整理出来的 “ Python - 接口自动化测试 ”入门篇新手小白必看!

走进Java接口测试之测试框架TestNG数据驱动(入门篇)

因为这套自己搭建的接口自动化测试框架,成功入职阿里,拿下19k