合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 单元测试 > 单元测试是对单独的代码对象进行测试的过程,比如对函数、类、方法进行测试。单元测试可以使用任意一段已经写好的测试代码,也可以使用一些已经存在的测试框架,比如`PHPUnit`、`Phake`或者`SimpleTest`,单元测试框架提供了一系列共同、有用的功能来帮助人们编写自动化的检测单元,例如检查一个实际的值是否符合我们期望的值的断言。单元测试框架经常会包含每个测试的报告,以及给出你已经覆盖到的代码覆盖率。 单元测试对于团队开发而言,好处不言而喻,主要作用包括: - 尽可能减少开发中的BUG; - 帮助提高应用(或者接口)设计; - 协助代码文档的编写; - 减少开发过程的代码修改带来的错误; `ThinkPHP5.0`目前支持`PHPUnit`进行单元测试,这是最常用的单元测试工具,ThinkPHP单元测试扩展还针对控制器和模型的单元测试做了完善支持,添加了额外的单元测试方法和断言。 `ThinkPHP5.0`核心也是采用`PHPUnit`作为单元测试工具,本文主要讲述如何对应用进行`PHPUnit`单元测试,但不打算详细介绍单元测试的用法,如果你还不了解`PHPUnit`以及什么是单元测试,我们建议首先阅读了解下[PHPUnit手册](http://ihavenolimitations.xyz/manual/phpunit-book)或者相关书籍。 ## 安装扩展 首先安装`ThinkPHP5`的单元测试扩展,进入命令行,切换到应用根目录下面后,执行: ``` <pre class="calibre18"> ``` composer <span class="hljs-keyword">require</span> topthink/think-testing ``` ``` 你不需要单独安装`PHPUnit`包,安装单元测试扩展的时候会自动安装相关的依赖包,其中就包括`PHPUnit`。 > 由于单元测试扩展的依赖较多,因此安装过程会比较久,请耐心等待。 安装完成后,会在应用根目录下面增加`tests`目录和`phpunit.xml`文件。 `tests`目录是应用的单元测试用例目录,该目录下面的`TestCase.php`文件是所有单元测试类必须基础的基础类,**不能删除**,`ExampleTest.php`是测试示例文件,可以删除。 ## 运行测试 由于单元测试扩展默认自带了一个`tests/ExampleTest.php`单元测试文件,内容如下: ``` <pre class="calibre18"> ``` namespace tests; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleTest</span> <span class="hljs-keyword"><span class="hljs-operator">extends</span></span> <span class="hljs-title">TestCase</span></span>{ public function testBasicExample() { $<span class="hljs-keyword">this</span>->visit('/')->see(<span class="hljs-operator">'ThinkPH</span>P'); } } ``` ``` 表示测试访问网站首页的响应输出内容中是否包含`ThinkPHP`内容,因为我们的默认。 因此我们可以直接在命令行下面运行单元测试: ``` <pre class="calibre18"> ``` <span class="hljs-title">php</span> think unit ``` ``` 如果你是第一次安装ThinkPHP5的话,或者没有改动过Index模块Index控制器的index方法的话,测试是OK的,会显示类似下面的结果: ``` <pre class="calibre18"> ``` PHPUnit <span class="hljs-number">4.8</span><span class="hljs-number">.27</span> by Sebastian Bergmann and contributors. . Time: <span class="hljs-number">221</span> ms, Memory: <span class="hljs-number">6.00</span>MB ``` ``` 如果改过index方法,并且页面输出内容没有ThinkPHP的话,测试就会失败。 ``` <pre class="calibre18"> ``` PHPUnit <span class="hljs-number">4.8</span><span class="hljs-number">.27</span> by Sebastian Bergmann and contributors. F <span class="hljs-operator"> Time:</span> <span class="hljs-number">230</span> ms, <span class="hljs-string">Memory:</span> <span class="hljs-number">6.00</span>MB There was <span class="hljs-number">1</span> <span class="hljs-string">failure:</span><span class="hljs-number">1</span>) tests\<span class="hljs-string">ExampleTest:</span>:testBasicExample Failed asserting that <span class="hljs-string">'Hello,world'</span> matches PCRE pattern <span class="hljs-string">"/ThinkPHP/i"</span>. <span class="hljs-operator"> D:</span>\www\tp5\vendor\topthink\think-testing\src\InteractsWithPages.<span class="hljs-string">php:</span><span class="hljs-number">67</span><span class="hljs-string">D:</span>\www\tp5\tests\ExampleTest.<span class="hljs-string">php:</span><span class="hljs-number">18</span><span class="hljs-string">D:</span>\www\tp5\vendor\topthink\think-testing\src\command\Test.<span class="hljs-string">php:</span><span class="hljs-number">39</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\console\command\Command.<span class="hljs-string">php:</span><span class="hljs-number">195</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">728</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">188</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">125</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">94</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\console.<span class="hljs-string">php:</span><span class="hljs-number">20</span> FAILURES! <span class="hljs-string">Tests:</span> <span class="hljs-number">1</span>, <span class="hljs-string">Assertions:</span> <span class="hljs-number">2</span>, <span class="hljs-string">Failures:</span> <span class="hljs-number">1.</span> ``` ``` > 请始终使用以上命令进行单元测试,而不是直接用`phpunit`来运行单元测试。 ## 添加单元测试用例 我们来添加一个控制器类的单元测试文件,为了便于管理,我们按照模块名创建单元测试的子目录和应用类库的目录结构对应起来,该单元测试文件为`tests/index/IndexTest.php`,内容如下: ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">tests</span>\<span class="hljs-title">index</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">tests</span>\<span class="hljs-title">TestCase</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span></span>{ <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span><span class="hljs-number">()</span></span>{ <span class="hljs-keyword">parent</span>::setUp(); } <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testSome</span><span class="hljs-number">()</span></span>{ <span class="hljs-regexp">$this</span>->assertTrue(<span class="hljs-keyword">true</span>); } <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tearDown</span><span class="hljs-number">()</span></span>{ } }</span> ``` ``` > 每个单元测试类必须继承`TestCase`类或其子类,每个单元测试方法必须以`test`开头。 除了单元测试方法之外,还可以增加一些特殊的单元测试定义方法(但不是必须的)。 `setUp`方法会在每一个单元测试方法之前自动调用,所以该方法可以用于一些测试的初始化,也就是所说的基境(`fixture`),对应还有一个`tearDown`方法会在每个测试方法完成后自动调用,通常用于重置一些测试的数据。 每个单元测试方法必须以`test`开头,后面的测试方法名称的命名可以随意,例如上面的`testSome`方法改成`testSomethingToTrue`并不会影响测试结果。 如果你需要同时对控制器类和模型类做单元测试的话,也可以在模块目录下面创建`controller`和`model`子目录,然后分别创建单元测试文件。 控制器单元测试文件`tests/index/controller/IndexTest.php` ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">tests</span>\<span class="hljs-title">index</span>\<span class="hljs-title">controller</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">tests</span>\<span class="hljs-title">TestCase</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span></span>{ <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testIndex</span><span class="hljs-number">()</span></span>{ <span class="hljs-regexp">$this</span>->call(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'/'</span>); <span class="hljs-regexp">$this</span>->assertResponseOk(); } }</span> ``` ``` 测试用例对网站首页进行了一次GET请求,并判断是否返回正确的响应。 模型单元测试文件`tests/index/model/IndexTest.php` ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">tests</span>\<span class="hljs-title">index</span>\<span class="hljs-title">model</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">tests</span>\<span class="hljs-title">TestCase</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span></span>{ <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testSearch</span><span class="hljs-number">()</span></span>{ <span class="hljs-regexp">$this</span>->seeInDatabase(<span class="hljs-string">'user'</span>, [<span class="hljs-string">'id'</span> => <span class="hljs-number">2</span>]); } }</span> ``` ``` > 在测试模型用例之前,请首先确保正确配置了数据库配置文件。 该测试用例测试数据表`think_user`(假设你的数据表前缀设置为`think_`)是否存在`id`为2的数据。 其实模型的测试用例和控制器的测试用例没有明确的界限,大多数情况,我们只需要针对控制器类做单元测试即可,同样,你仍然可以对不同的应用层类库做单元测试。 除了支持`PHPUnit`自带的测试方法外,`ThinkPHP`单元测试扩展还封装了一些额外的方法,我们以后会给你详细描述。 ## 定义单元测试 单元测试最大的工作是使用断言(`assertion`),所以你会发现PHPUnit自身带了很多的断言方法,常用的方法包括: 断言方法 描述 assertTrue($var) 断言变量为true assertFalse($var) 断言变量为false assertEquals($value,$var) 断言变量为$value assertNull($var) 断言变量为null assertContains($needle,$var) 断言变量中包含$needle