Yii2单元测试

4 篇文章 0 订阅

初识单元测试

测试驱动开发(TDD)

测试驱动开发是敏捷软件开发的推荐做法。TDD 希望在编写代码之前先编写测试。这些测试提供了必须遵循预期功能的代码。保持测试领先于开发,永远不会有未被测试的代码。编写测试代码的数量和代码和质量是成正比的。例如下面的例子:

class UserTest extends \Codeception\Test\Unit
{
    public function testValidation()
    {
        $user = new User();
        $user->setName(null);
        $this->assertFalse($user->validate(['username']));
        $user->setName('toolooooongnaaaaaaameeee');
        $this->assertFalse($user->validate(['username']));
        $user->setName('davert');
        $this->assertTrue($user->validate(['username']));
    }
}

行为驱动开发(BDD)

行为驱动开发是在 TDD 的基础上发展而来的,它为开发人员和非开发人员提供了一种通用语言,用于描述正确的应用程序行为和模块行为。相对于代码,BDD 通常是使用故事转化为测试的过程。例如以下的例子:

class BasicCest
{
    // test
    public function tryToTest(\AcceptanceTester $I)
    {
        $I->amOnPage('/');
        $I->click('Login');
        $I->fillField('username', 'john');
        $I->fillField('password', 'coltrane');
        $I->click('Enter');
        $I->see('Hello, John');
        $I->seeInCurrentUrl('/account');
    }
}

什么是可测试的代码

短小但也不太复杂的代码,完整的注释,以及松偶合。这些特性更让代码具有“可测试性”。利用可测试特性和测试工具,可以让代码更具有可测试性。

开始

创建第一个单元测试模型

Yii2 的基础模板和高级模板中已经默认包含了单元测试工具 Codeception,可使用 PHPUnit 对项目进行 TDD 测试和使用 Chrome 驱动进行 BDD 测试。以下谨以高级模板为例进行说明。在项目的根目录中执行以下命令 codeception 工具将生成单元测试模型。
Lunix:

vendor\bin\codecept -c common generate:test unit models/Custom

Windows:

vendor\bin\codecept.bat -c common generate:test unit models/Custom

单元测试工具安装在项目的 vendor/bin 目录下,参数-c 指定单元测试工具的配置文件所在的目录:common,命令的开关是 generate:test,创建的类型是 nuit(单元测试),测试模型的生成目录为(基于 common/testes/unit/models/Custom,生成的测试模型文件为 CustomTest.php

执行单元测试

助手生成的单元测试模型默认携带了一个单元测试的功能点,我们可以执行以下命令进行测试

vendor\bin\codecept.bat -c common run unit

在这里插入图片描述

认识单元测试模型

<?php namespace common\tests\models;
class CustomTest extends \Codeception\Test\Unit
{
    /**
     * @var \common\tests\UnitTester
     */
    protected $tester;
    
    protected function _before()
    {
    }
    protected function _after()
    {
    }
    // tests
    public function testSomeFeature()
    {
    }
}

代码中包含有两_before 和_after 两人拦截器方法,和一个以 test 起头的测试方法。_before 拦截器会在执行单元测试之前被执行,我们可以在这里进行一些测试的初始化操作,如:给要测试的变量进行赋值。

第一段测试代码

<?php namespace common\tests\models;
class TesterTest extends \Codeception\Test\Unit
{
    /**
     * @var \common\tests\UnitTester
     */
    protected $tester;
    
    protected function _before()
    {
        $this->tester = [
            'one' => 12345,
            'two' => 67890,
        ];
    }
    
    protected function _after()
    {
    }
    
    // tests
    public function testSomeFeature()
    {
    }
    
    public function testCheckValue()
    {
        expect('check array type', $this->tester)->isArray();
        expect('check has key', $this->tester)->hasKey('one');
    }
}

我们在_before 拦截器中为类变量 tester 赋值,并添加一个新的测试方法 testCheckValue。使用 expect(预期)方法测试需要测试的值,这里我们要测试的是$this->testerisArray(),我们预期得到的结果是数组类型,hasKey(‘one’),我们预期的结果为数据中包含 one 这个键。我们测试一下看看结果:
在这里插入图片描述
两个方法前面的"+"号,代表方法测试通过。

OK (2 tests, 2 assertions)

最后一行的信息显示,本次单元测试中总共两个测试方法(以 test 开头的方法),和两个断言(调用 expect 方法的次数)

制造一个失败的测试

为了看一下测试失败的结果,我们对代码进行一下改造:

protected function _before()
    {
        $this->tester = [
            'done' => 12345,
            'two' => 67890,
        ];
    }

把数组的 one 修改为 done,执行测试:
在这里插入图片描述
现在我们可以看到,testCheckValue 方法名前面的状态显示为一个"x"(错误),错误的测试点(expect)的说明为"check has key",错误的描述为"Failed asserting that an array has the key ‘one’."(数组是否包含键名为 one 的键的断言失败)。#1 处提示出错误的代码所在的行号以方便我们的定位。
结尾的提示:

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

测试方法两个,断言两个,错误的断言一个。

更多的预期方法

项目Value
equals等于
notEquals不等于
contains数组中包含值,或字符串中包含字符串
notContains以上一条相反
true布尔:真
false布尔:假
null等于 null
notNull不等于 null
isEmpty为空
notEmpty非空
hasKey数组中包含键
hasntKey数组中不包含键
isInstanceOf对象比对为真
isNotInstanceOf对象比对为假
hasAttribute对象是包含属性
notHasAttribute对象不包含属性
hasStaticAttribute对象是否包含静态属性
notHasStaticAttribute对象不包含静态属性
count数组元素等于
notCount数组的元素不等于
equalXMLStructure等于 XML 文件结构
exists文件存在
notExists文件不存在
equalsJsonFile等于 JSON 文件数据
equalsJsonString等于 JSON 数据
regExp正则比对
equalsFile等于文件的数据
notEqualsFile不等于文件的数据
equalsXmlFile等于 XML 文件数据
equalsXmlString等于 XML 数据
stringContainsString字符串包含
stringNotContainsString字符串不包含
stringContainsStringIgnoringCase字符串包含,忽略大小写
stringNotContainsStringIgnoringCase字符串不包含,忽略大小写
isArray
bool
float
int
numeric
object
resource
string
scalar
isCallable是可调用类型
notArray
notBool
notFloat
notInt
notNumeric
notObject
notResource
notString
notScalar
notCallable
equalsCanonicalizing
notEqualsCanonicalizing
equalsIgnoringCase
notEqualsIgnoringCase
equalsWithDelta
notEqualsWithDelta
notStartsWith
startsWith
notEndsWith
endsWith
notSame
same
notMatchesFormatFile
matchesFormatFile
notMatchesFormat
matchesFormat
containsOnly
notContainsOnly
containsOnlyInstancesOf
internalType
notInternalType
greaterThan
lessThan
greaterOrEquals
lessOrEquals

使用替身进行依赖注入

当我们在测试某些方法需要返回指定的值时,如从数据库模型里查询数据返回时,我们并不需要真的去数据库里为这次测试插入一条测试数据。我们可以使用测试工具提供的机制,把某个类的指定的方法进行覆盖。

public function testIsAdmin()
{
    $user = $this->make(\yii\web\User::class, [
        'getIdentity' => function($autoRenew = true) {
            return true;
            }
        ]
    );
    expect('check is admin', $user->getIdentity())->true();
}

我们覆盖\yii\web\User 类中 getIdentity 方法的返回值并进行测试。我们可以控制方法的返回值,以便进行相关功能的测试。

提示:
使用依赖注入的对象,可以把我们替身对象注入到要测试的对象中进行后续的测试。如果是对象从内部执行 new 生成的 User 对象,我们就无法完成上面的测试,只能从数据库同添加相关的数据才能进行后续的测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yagas

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值