166°

进阶篇:如何为ThinkPHP5编写优质的单元测试?

前言

在项目开发中,需要使用到ThinkPHP 5,为了编写单元测试,解决了几个难题,特此纪录分享一下。

 

难点1:TP5自带的单元测试感觉不好用,如何使用纯粹的原生PHPUnit?

在看云上,有TP5官方关于单元测试的使用说明,链接是:https://www.kancloud.cn/manual/thinkphp5/182511

但上面的说明过于简单,对于实际使用帮助有限。

 

对于一直钟情于自动化单元测试以及PHPUnit原生单元测试的我,决定对此优化一番,引入并在ThinkPHP 5下使用原生PHPUnit。

第一步:准备工作

在tests目录下,创建一个phpunit目录,然后创建两个文件:测试启动文件bootstrap.php和单元测试的配置文件phpunit.xml。

测试启动文件bootstrap.php,可以参考项目的启动文件,复制过来后调整下,例如这样:

<?php
// 定义应用目录
define('APP_PATH', __DIR__ . '/../../application/');

define('APP_DEBUG', true);     //开启调试模式
define("APP_STATUS", "tests"); //定义为本地环境
define("RUNTIME_PATH", __DIR__ . "/../../runtime/"); //定义缓存目录

require APP_PATH . '/define.php';

// ThinkPHP 引导文件
// 加载基础文件
require __DIR__ . '/../../thinkphp/base.php';

// 加载应用
\think\Loader::addNamespace('app', APP_PATH);

// 兼容旧版本的PHPUnit
if (!class_exists('PHPUnit_Framework_TestCase')) {
    class PHPUnit_Framework_TestCase extends PHPUnit\FrameWork\TestCase {
    }
}

// 手动再引入一次测试配置
\think\Config::load(APP_PATH . '/tests/test.php');

// 手动引入框架和应用的函数
require_once APP_PATH . '../thinkphp/helper.php';
require_once APP_PATH . 'common.php';

对于phpunit.xml配置文件,可以这样写:

<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
    backupStaticAttributes="false"
    colors="true"
    bootstrap="./bootstrap.php"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    processIsolation="false"
    stopOnFailure="false"
    syntaxCheck="false"
    >
    <php>
        <ini name="intl.default_locale" value="en"/>
        <ini name="intl.error_level" value="0"/>
        <ini name="memory_limit" value="-1"/>
    </php>

    <testsuites>
        <testsuite name="Test Suite">
            <directory suffix="Test.php">./</directory>
            <directory suffix="Test.php">./application</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">../../application</directory>
        </whitelist>
    </filter>
</phpunit>

第二步:写个简单的demo

下面编写一个简单的测试用例,试运行一下。在phpunit下创建一个demo目录,并创建TestCaseClass.php文件。此时结构如下:

$ tree ./phpunit 
./phpunit
├── bootstrap.php
├── demo
│   └── TestCaseClass.php
└── phpunit.xml

TestCaseClass.php文件代码是:

<?php
use PHPUnit\Framework\TestCase;

namespace tests\demo;

class TestCaseClass extends \PHPUnit_Framework_TestCase
{
    public function testHere()
    {
        $this->assertTrue(true);
    }
}

这样编写成功后,就可以试运行了。

第三步:运行一个简单的PHPUnit测试

$ phpunit ./demo/TestCaseClass.php    
PHPUnit 5.7.25 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 121 ms, Memory: 10.00MB

OK (1 test, 1 assertion)

后面就可以继续这样编写原生的PHPUnit单元测试啦。

 

难点2:如何使用PhalApi开源框架的脚本,为TP5项目自动生成测试代码?

下面就要为TP5的项目代码编写单元测试了,但每次都要人工手动重复编写测试代码是一件很累人、很耗时、很低率的工作。有没更好的技能?有!

参考我曾经编写的脚本工具:[PHPUnit]自动生成PHPUnit测试骨架脚本-提供您的开发效率【2015升级版】

现在已经成为PhalApi开源框架内置的脚本命令了。PhalApi是一个专注于接口开发的开源框架,因此我们可以把PhalApi的phalapi-buildtest脚本命令整合到ThinkPHP 5 的项目里,

 

非常重要的链接

使用说明和详细的官方文档:PhalApi 2.x 单元测试

脚本命令Github下载地址(需要同时下载这两个文件):

https://github.com/phalapi/phalapi/blob/master-2x/bin/phalapi-buildtest

https://github.com/phalapi/phalapi/blob/master-2x/bin/build_test.php

 

下载后放到tests/phpunit目录下,此时文件结构如下:

$ tree ./phpunit 
./phpunit
├── bootstrap.php
├── build_test.php
├── demo
│   └── TestCaseClass.php
├── phalapi-buildtest
└── phpunit.xml

 

准备好后,就可以开始生成单元测试的代码啦!

例如,可执行:

$ ./phalapi-buildtest ../../application/controller/Site.php 'app\controller\Site' > ./controller/SiteTest.php

生成后,便可执行。

 

难点3:如何测试controller,以及如何解决input()的参数缓存?

正常情况下,进行单个单元测试时,以下测试代码是可以的:

    public function testLogin()
    {
        $_POST['email'] = 'phpunit123';
        $_POST['password'] = '123456';
        $_POST['remember'] = '1';

        $rs = $this->appcontrollerSite->login();

        $this->assertEquals(1, $rs['code']);
    }

对应的源代码是:

<?php

class Site extends Controller {

    public function login() {
        $username = input('post.email');
        $password = input('post.password');
        $remember = input('post.remember/d', 0);

        // todo
    }
}

但是,如果执行全部单元测试的话,传给controller的$_POST参数就失效了。这是因为ThinkPHP5的Request是一个单例,并且在think\Request::$post变量中缓存了POST参数,导致后面的参数不生效。

为此,需要这样调整传递参数:

    public function testLogin()
    {
        $_POST['email'] = 'phpunit123';
        $_POST['password'] = '123456';
        $_POST['remember'] = '1';

        // 加多这两行,重置POST参数
        $params = ['POST' => $_POST];
        \think\Request::create('/', 'POST', $params);

        $rs = $this->appcontrollerSite->login();

        $this->assertEquals(1, $rs['code']);
    }

参考

发现了一篇写得很赞的文章:PHPUnit简介及使用(thinkphp5的单元测试安装及使用)

本文由【暗夜在火星】发布于开源中国,原文链接:https://my.oschina.net/dogstar/blog/3047041

全部评论: 0

    我有话说: