[ Laravel从入门到精通 ] 测试系列 —— 在 Laravel 中基于 PHPUnit 进行代码测试:单元测试篇

栏目: 编程工具 · 发布时间: 5年前

内容简介:介绍完 PHPUnit 的基本使用和 Laravel 框架自带的编排文件其中包含了一行最基本的断言测试,用于判断指定的参数是否为真,并且这个测试永远是通过的。Laravel 的单元测试其实是原原本本继承了 PHPUnit 的单元测试功能,这里的父类下面我们通过几个常见的场景介绍下如何基于 PHPUnit 编写单元测试。

简介

介绍完 PHPUnit 的基本使用和 Laravel 框架自带的编排文件 phpunit.xml 文件,今天开始我们正式准备在 Laravel 项目中基于 PHPUnit 编写单元测试和功能测试,通过上篇教程介绍的编排文件我们知道,Laravel 的单元测试用例位于 tests/Unit 目录下,框架本身也为我们提供了一个示例测试文件 ExampleTest.php

<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $this->assertTrue(true);
    }
}

其中包含了一行最基本的断言测试,用于判断指定的参数是否为真,并且这个测试永远是通过的。Laravel 的单元测试其实是原原本本继承了 PHPUnit 的单元测试功能,这里的父类 Tests\TestCase 从根源上继承自 PHPUnit\Framework\TestCase ,所以我们可以在测试用例中使用所有 PHPUnit 支持的 断言方法测试注解

下面我们通过几个常见的场景介绍下如何基于 PHPUnit 编写单元测试。

对变量进行测试

PHPUnit 底层提供了很多断言方法用于对变量进行测试,这些变量通常是业务代码类方法或函数的返回值,我们在 Unit\ExampleTest 中新增一个 testVariables 方法:

public function testVariables()
{
    $bool = false;
    $number = 100;
    $arr = ['Laravel', 'PHP', '学院君'];
    $obj = null;

    // 断言变量值是否为假,与 assertTrue 相对
    $this->assertFalse($bool);
    // 断言给定变量值是否与期望值相等,与 assertNotEquals 相对
    $this->assertEquals(100, $number);
    // 断言数组中是否包含给定值,与 assertNotContains 相对
    $this->assertContains('学院君', $arr);
    // 断言数组长度是否与期望值相等,与 assertNotCount 相对
    $this->assertCount(3, $arr);
    // 断言数组是否不会空,与 assertEmpty 相对
    $this->assertNotEmpty($arr);
    // 断言变量是否为 null,与 assertNotNull 相对
    $this->assertNull($obj);
}

相应的断言用途在注释中已经说明了,我们可以对各种类型的变量从各种维度进行断言,甚至还可以对文件、目录、正则表达式进行断言,并且很多断言都可以从正反两个方法进行,相关的调用都很简单,你可以在需要的时候查看官方文档选择相应的断言方法: https://phpunit.readthedocs.io/zh_CN/latest/assertions.html

运行上面的测试用例,结果如下,每个测试方法都代表一个测试用例,所以上面的单元测试包含两个测试用例,7个断言:

[ Laravel从入门到精通 ] 测试系列 —— 在 Laravel 中基于 PHPUnit 进行代码测试:单元测试篇

对输出进行测试

除了对变量进行测试外,还可以对页面输出进行测试,这可以通过 PHPUnit 提供的 expectOutputString 方法来实现:

public function testOutput()
{
    $this->expectOutputString('学院君');
    echo '学院君';
}

此外还可以对输出数据进行正则判断:

public function testOutputRegex()
{
    $this->expectOutputRegex('/Laravel/i');
    echo 'Laravel学院';
}

上述测试结果都是通过的:

[ Laravel从入门到精通 ] 测试系列 —— 在 Laravel 中基于 PHPUnit 进行代码测试:单元测试篇

对异常进行测试

类似的,还可以通过 expectException 方法对异常进行测试,为了让测试用例更加符合真实场景,我们在 app 目录下新增一个 Services 子目录,然后在该子目录下创建一个 TestService 类并初始化代码如下:

<?php
namespace App\Services;

class TestService
{
    public function invalidArgument()
    {
        throw new \InvalidArgumentException('无效的参数');
    }
}

然后回到 Unit\ExampleTest ,编写一个新的测试用例如下:

public function testException()
{
    $this->expectException(\InvalidArgumentException::class);
    $service = new TestService();
    $service->invalidArgument();
}

运行测试用例,结果为通过:

[ Laravel从入门到精通 ] 测试系列 —— 在 Laravel 中基于 PHPUnit 进行代码测试:单元测试篇

如果传入的抛出异常类的是父类,也会通过:

$this->expectException(\Exception::class);

除此之外,还可以进一步对异常明细进行测试,比如通过 expectExceptionCode()expectExceptionMessage()expectExceptionMessageRegExp() 方法可以用于测试异常码、异常信息。

除了通过上述方法,还可以通过注解对异常进行测试,这种方式更加方便:

/**
 * @expectedException \InvalidArgumentException
 */
public function testExceptionAnnotation()
{
    $this->service->invalidArgument();
}

由于两个测试用例中都用到了 TestService ,所以我们将其在 setUp 方法进行初始化:

/**
 * @var TestService
 */
protected $service;

protected function setUp(): void
{
    parent::setUp();
    $this->service = new TestService();
}

上面两个测试用例从功能上说是等价的:

[ Laravel从入门到精通 ] 测试系列 —— 在 Laravel 中基于 PHPUnit 进行代码测试:单元测试篇

对错误进行测试

默认情况下,PHPUnit 会将 PHP 错误、警告和通知都转化为异常,在上一篇 PHP 编排文件 phpunit.xml 中我们提到,Laravel 也默认配置为做这些转化,所以我们可以通过测试异常的方式对业务代码中的错误进行测试。具体用法和异常测试一样,就不再赘述了。

测试的依赖关系

有的时候,我们需要测试的两个用例之间可能有依赖关系,比如我们在 TestService 定义如下个方法:

protected $stack = [];

public function init()
{
    $this->stack = ['学院君', 'Laravel学院', '单元测试'];
}

public function stackContains($value)
{
    return in_array($value, $this->stack);
}

public function getStackSize()
{
    return count($this->stack);
}

我们在测试 stackContains 方法时,往往要先调用 init 方法,但是在单元测试中,每个方法都有独立的测试用例,如果多次调用有可能会对数据造成污染,那我们能否在 init 方法测试用例的运行基础上运行 stackContains 方法的测试用例呢?这个时候,我们说这两个测试用例之间是具有依赖关系的,PHPUnit 中通过 @depends 注解对这种依赖关系进行了支持,我们可以在 Unit\ExampleTest 中编写测试用例如下:

public function testInitStack()
{
    $this->service->init();
    $this->assertEquals(3, $this->service->getStackSize());

    return $this->service;
}

/**
 * @depends testInitStack
 * @param TestService $service
 */
public function testStackContains(TestService $service)
{
    $contains = $service->stackContains('学院君');
    $this->assertTrue($contains);
}

testStackContains 用例中,我们将 testInitStack 用例返回的 $service 实例传递进来,并在此基础上进行测试。

数据提供器

除了支持测试用例之间的依赖之外,PHPUnit 还可以通过 @dataProvider 注解为多个测试用例提供初始化数据:

public function initDataProvider()
{
    return [
        ['学院君'],
        ['Laravel学院']
    ];
}

/**
 * @depends testInitStack
 * @dataProvider initDataProvider
 */
public function testIsStackContains()
{
    $arguments = func_get_args();
    $service = $arguments[1];
    $value = $arguments[0];
    $this->assertTrue($service->stackContains($value));
}

在这个测试用例中,我们通过 initDataProvider 方法作为数据提供器,数据供给器方法必须声明为 public ,其返回值要么是一个数组,其每个元素也是数组;要么是一个实现了 Iterator 接口的对象,在对它进行迭代时每步产生一个数组。每个数组都是测试数据集的一部分,将以它的内容作为参数来调用测试方法。

然后我们在需要用到这个数据提供器的测试用例上用 @dataProvider 注解进行声明,在这个示例中我们迭代数据提供器数组,将其中的数据作为参数传入 TestServicestackContains 方法以判断对应值在 stack 属性中是否存在。

以下是上述用例运行结果:

[ Laravel从入门到精通 ] 测试系列 —— 在 Laravel 中基于 PHPUnit 进行代码测试:单元测试篇

关于 Laravel 基于 PHPUnit 编写单元测试用例我们就简单介绍到这里,在这篇教程中我们基本上使用的都是 PHPUnit 提供的原生测试功能,下一篇我们将围绕 Laravel 功能测试展开,主要是对控制器和 API 接口的测试,在那里,我们将开始就 Laravel 框架在 PHPUnit 基础上封装的新功能新特性进行介绍。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

网站运维技术与实践

网站运维技术与实践

饶琛琳 / 电子工业出版社 / 2014-3 / 69.00元

网站运维工作,一向以内容繁杂、覆盖面广著称。《网站运维技术与实践》选取日常工作涉及的监测调优、日志分析、集群规划、自动化部署、存储和数据库等方面,力图深入阐述各项工作的技术要点及协议原理,并介绍相关开源产品的实践经验。在技术之外,作者也分享了一些关于高效工作及个人成长方面的心得。 《网站运维技术与实践》适合Linux 系统管理员、中大型网站运维工程师及技术负责人、DevOps 爱好者阅读。同......一起来看看 《网站运维技术与实践》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具