Skip to content

Commit b74a1b5

Browse files
committed
docs: Laravel 基于 PHPUnit 的单元测试
1 parent 71f8ab7 commit b74a1b5

File tree

1 file changed

+232
-0
lines changed

1 file changed

+232
-0
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
## 25. 2019-06-10-Laravel 基于 PHPUnit 的单元测试
2+
3+
### 简介
4+
介绍完 PHPUnit 的基本使用和 Laravel 框架自带的编排文件 phpunit.xml 文件,今天开始我们正式准备在 Laravel 项目中基于 PHPUnit 编写单元测试和功能测试,通过上篇教程介绍的编排文件我们知道,Laravel 的单元测试用例位于 tests/Unit 目录下,框架本身也为我们提供了一个示例测试文件 ExampleTest.php:
5+
6+
```php
7+
<?php
8+
9+
namespace Tests\Unit;
10+
11+
use Tests\TestCase;
12+
use Illuminate\Foundation\Testing\RefreshDatabase;
13+
14+
class ExampleTest extends TestCase
15+
{
16+
/**
17+
* A basic test example.
18+
*
19+
* @return void
20+
*/
21+
public function testBasicTest()
22+
{
23+
$this->assertTrue(true);
24+
}
25+
}
26+
```
27+
28+
其中包含了一行最基本的断言测试,用于判断指定的参数是否为真,并且这个测试永远是通过的。Laravel 的单元测试其实是原原本本继承了 PHPUnit 的单元测试功能,这里的父类 Tests\TestCase 从根源上继承自 PHPUnit\Framework\TestCase,所以我们可以在测试用例中使用所有 PHPUnit 支持的断言方法和测试注解。
29+
30+
下面我们通过几个常见的场景介绍下如何基于 PHPUnit 编写单元测试。
31+
32+
### 对变量进行测试
33+
PHPUnit 底层提供了很多断言方法用于对变量进行测试,这些变量通常是业务代码类方法或函数的返回值,我们在 Unit\ExampleTest 中新增一个 testVariables 方法:
34+
35+
```
36+
public function testVariables()
37+
{
38+
$bool = false;
39+
$number = 100;
40+
$arr = ['Laravel', 'PHP', '学院君'];
41+
$obj = null;
42+
43+
// 断言变量值是否为假,与 assertTrue 相对
44+
$this->assertFalse($bool);
45+
// 断言给定变量值是否与期望值相等,与 assertNotEquals 相对
46+
$this->assertEquals(100, $number);
47+
// 断言数组中是否包含给定值,与 assertNotContains 相对
48+
$this->assertContains('学院君', $arr);
49+
// 断言数组长度是否与期望值相等,与 assertNotCount 相对
50+
$this->assertCount(3, $arr);
51+
// 断言数组是否不会空,与 assertEmpty 相对
52+
$this->assertNotEmpty($arr);
53+
// 断言变量是否为 null,与 assertNotNull 相对
54+
$this->assertNull($obj);
55+
}
56+
```
57+
58+
相应的断言用途在注释中已经说明了,我们可以对各种类型的变量从各种维度进行断言,甚至还可以对文件、目录、正则表达式进行断言,并且很多断言都可以从正反两个方法进行,相关的调用都很简单,你可以在需要的时候查看官方文档选择相应的断言方法:https://phpunit.readthedocs.io/zh_CN/latest/assertions.html。
59+
60+
运行上面的测试用例,结果如下,每个测试方法都代表一个测试用例,所以上面的单元测试包含两个测试用例,7个断言:
61+
62+
63+
64+
### 对输出进行测试
65+
除了对变量进行测试外,还可以对页面输出进行测试,这可以通过 PHPUnit 提供的 expectOutputString 方法来实现:
66+
67+
```
68+
public function testOutput()
69+
{
70+
$this->expectOutputString('学院君');
71+
echo '学院君';
72+
}
73+
```
74+
此外还可以对输出数据进行正则判断:
75+
76+
```
77+
public function testOutputRegex()
78+
{
79+
$this->expectOutputRegex('/Laravel/i');
80+
echo 'Laravel学院';
81+
}
82+
```
83+
84+
上述测试结果都是通过的:
85+
86+
87+
88+
### 对异常进行测试
89+
类似的,还可以通过 expectException 方法对异常进行测试,为了让测试用例更加符合真实场景,我们在 app 目录下新增一个 Services 子目录,然后在该子目录下创建一个 TestService 类并初始化代码如下:
90+
91+
```
92+
<?php
93+
namespace App\Services;
94+
95+
class TestService
96+
{
97+
public function invalidArgument()
98+
{
99+
throw new \InvalidArgumentException('无效的参数');
100+
}
101+
}
102+
```
103+
然后回到 Unit\ExampleTest,编写一个新的测试用例如下:
104+
105+
```
106+
public function testException()
107+
{
108+
$this->expectException(\InvalidArgumentException::class);
109+
$service = new TestService();
110+
$service->invalidArgument();
111+
}
112+
```
113+
运行测试用例,结果为通过:
114+
115+
116+
117+
如果传入的抛出异常类的是父类,也会通过:
118+
119+
```
120+
$this->expectException(\Exception::class);
121+
```
122+
123+
除此之外,还可以进一步对异常明细进行测试,比如通过 expectExceptionCode()、expectExceptionMessage() 和 expectExceptionMessageRegExp() 方法可以用于测试异常码、异常信息。
124+
125+
除了通过上述方法,还可以通过注解对异常进行测试,这种方式更加方便:
126+
127+
```
128+
/**
129+
* @expectedException \InvalidArgumentException
130+
*/
131+
public function testExceptionAnnotation()
132+
{
133+
$this->service->invalidArgument();
134+
}
135+
```
136+
137+
由于两个测试用例中都用到了 TestService,所以我们将其在 setUp 方法进行初始化:
138+
139+
```
140+
/**
141+
* @var TestService
142+
*/
143+
protected $service;
144+
145+
protected function setUp(): void
146+
{
147+
parent::setUp();
148+
$this->service = new TestService();
149+
}
150+
```
151+
152+
上面两个测试用例从功能上说是等价的:
153+
154+
155+
156+
### 对错误进行测试
157+
默认情况下,PHPUnit 会将 PHP 错误、警告和通知都转化为异常,在上一篇 PHP 编排文件 phpunit.xml 中我们提到,Laravel 也默认配置为做这些转化,所以我们可以通过测试异常的方式对业务代码中的错误进行测试。具体用法和异常测试一样,就不再赘述了。
158+
159+
### 测试的依赖关系
160+
有的时候,我们需要测试的两个用例之间可能有依赖关系,比如我们在 TestService 定义如下个方法:
161+
162+
protected $stack = [];
163+
164+
public function init()
165+
{
166+
$this->stack = ['学院君', 'Laravel学院', '单元测试'];
167+
}
168+
169+
public function stackContains($value)
170+
{
171+
return in_array($value, $this->stack);
172+
}
173+
174+
public function getStackSize()
175+
{
176+
return count($this->stack);
177+
}
178+
我们在测试 stackContains 方法时,往往要先调用 init 方法,但是在单元测试中,每个方法都有独立的测试用例,如果多次调用有可能会对数据造成污染,那我们能否在 init 方法测试用例的运行基础上运行 stackContains 方法的测试用例呢?这个时候,我们说这两个测试用例之间是具有依赖关系的,PHPUnit 中通过 @depends 注解对这种依赖关系进行了支持,我们可以在 Unit\ExampleTest 中编写测试用例如下:
179+
180+
public function testInitStack()
181+
{
182+
$this->service->init();
183+
$this->assertEquals(3, $this->service->getStackSize());
184+
185+
return $this->service;
186+
}
187+
188+
/**
189+
* @depends testInitStack
190+
* @param TestService $service
191+
*/
192+
public function testStackContains(TestService $service)
193+
{
194+
$contains = $service->stackContains('学院君');
195+
$this->assertTrue($contains);
196+
}
197+
在 testStackContains 用例中,我们将 testInitStack 用例返回的 $service 实例传递进来,并在此基础上进行测试。
198+
199+
### 数据提供器
200+
除了支持测试用例之间的依赖之外,PHPUnit 还可以通过 @dataProvider 注解为多个测试用例提供初始化数据:
201+
202+
public function initDataProvider()
203+
{
204+
return [
205+
['学院君'],
206+
['Laravel学院']
207+
];
208+
}
209+
210+
/**
211+
* @depends testInitStack
212+
* @dataProvider initDataProvider
213+
*/
214+
public function testIsStackContains()
215+
{
216+
$arguments = func_get_args();
217+
$service = $arguments[1];
218+
$value = $arguments[0];
219+
$this->assertTrue($service->stackContains($value));
220+
}
221+
在这个测试用例中,我们通过 initDataProvider 方法作为数据提供器,数据供给器方法必须声明为 public,其返回值要么是一个数组,其每个元素也是数组;要么是一个实现了 Iterator 接口的对象,在对它进行迭代时每步产生一个数组。每个数组都是测试数据集的一部分,将以它的内容作为参数来调用测试方法。
222+
223+
然后我们在需要用到这个数据提供器的测试用例上用 @dataProvider 注解进行声明,在这个示例中我们迭代数据提供器数组,将其中的数据作为参数传入 TestService 的 stackContains 方法以判断对应值在 stack 属性中是否存在。
224+
225+
以下是上述用例运行结果:
226+
227+
228+
229+
关于 Laravel 基于 PHPUnit 编写单元测试用例我们就简单介绍到这里,在这篇教程中我们基本上使用的都是 PHPUnit 提供的原生测试功能,下一篇我们将围绕 Laravel 功能测试展开,主要是对控制器和 API 接口的测试,在那里,我们将开始就 Laravel 框架在 PHPUnit 基础上封装的新功能新特性进行介绍。
230+
231+
232+
参考地址:[https://laravelacademy.org/post/19586.html](https://laravelacademy.org/post/19586.html)

0 commit comments

Comments
 (0)