测试 yii2-codeception(1)--单元测试

2017-01-12

测试

1、测试主要分为以下三种类型:

  • 单元测试:验证一个独立的代码单元是否按照期望的方式运行。test
  • 功能测试:在浏览器模拟器中以用户视角来验证期望的场景是否发生。cest
  • 验收测试:与功能测试相同,但实际上通过真实的Web浏览器运行测试。cept

2、BDD 与 TDD

TDD(Test Driven Development),测试驱动开发。是测试优先的开发模式。先写测试代码,再编写具体业务逻辑代码,然后运行单元测试,如果失败,就继续修正代码~。 如果有新的功能,就如下面的流程图一样: Test Driven Development

但是,TDD开发模式,可能会造成一些问题~它要求先写测试,这就要求项目需求清晰,而且程序员对整个项目架构很熟悉,能提前抽离一些借口代码。否则,项目很容易失控。 但是当一个业务模型及其复杂、内部模块之间的相互依赖性非常强,就比较麻烦了,这会导致程序员在拆分接口和写测试代码的时候工作量非常大。

BDD(Behavior Driven Development),行为驱动开发。这相当于,TDD的扩展。它旨在消除TDD可能导致的问题。

这里只简单的说明下,如果大家很感兴趣,建议大家再多查查资料,深入了解一下~。

codeception 与 PHPUnit 之间是什么关系?

codeception 采用PHPUnit作为它运行测试的后端,所以任何PHPUnit测试都可以在codeception测试中运行。 另外,如果安装了codeception就不需要再另外安装PHPUnit啦~

全局安装codeception

1、全局安装

1
2
3
composer global require "codeception/codeception"
composer global require "codeception/specify=*" //用于在单元测试中写入规范
composer global require "codeception/verify=*"

安装codeception/specifycodeception/verify,可以更好的形成BDD(Behavior Driven Development)规范测试.

2、查看状态:composer global status

3、建立符号链接:sudo ln -s ~/.composer/vendor/bin/codecept /usr/local/bin/codecept

4、codecept 常用命令:

  • codecept bootstrap
  • codecept build
  • codecept run

也可以这样使用

  • composer exec codecept build
  • composer exec codecept run

暂时还不清楚两种方式的区别~~

yii-basic

我们在yii2中尝试使用codeception,采用的版本是basic版本。

使用composer创建yii-basic项目的时候,yii2已经添加codeception框架了。如果已经全局安装了codeception,建议移除项目中的codeception,至于为啥,你看最下面(踩过的那些坑儿-2)

但是没有安装yii2的扩展yiisoft/yii2-codeception,所以使用 composer require --prefer-dist yiisoft/yii2-codeception命令安装此扩展。

yii2-codeception 扩展安装

yii2下的测试目录

1
2
3
4
5
6
7
8
9
10
11
12
-test
- _data
- _output # 输出文件夹
- _support
- acceptance # 验收测试代码放置的文件夹
- bin # 放置的是Yii2测试环境下,可以执行命令文件
- functional # 功能测试代码放置的文件夹
- unit # 单元测试代码放置的文件夹
- _bootstrap.php # 初始化文件
- acceptance.suite.yml.example # 验收测试配置文件
- functional.suite.yml # 功能测试配置文件
- unit.suite.yml # 单元测试配置文件
  1. acceptance.suite.yml.example文件配置
  2. functional.suite.yml文件配置
  3. unit.suite.yml文件配置

建立测试数据库:

  • 测试数据库的配置在,config/test_db.php文件中:$db['dsn'] = 'mysql:host=localhost;dbname=yii2_basic_tests';;根据自己的情况,修改相应host,dbname即可;
  • 进入test目录,
  • 如果有codeception目录的话,需进入codeception/bin/目录;
  • 没有的话,直接进入/bin目录;
  • 这里有两个文件:yiiyii.bat
  • 在这个目录下,执行数据库迁移命令。php yii migrate/up

有关数据库迁移,请看migrations

试一下命令

在创建的yii2-basic项目中,已经有一些预先框架自带的测试用例。我们尝试运行以下命令,看一下~

在项目中执行,php -S localhost:8080,即可通过http://localhost:8080/web/index.php访问了~

  • codecept build,构建测试
  • codecept run,运行所有可以运行的测试代码
  • codecept run acceptance,运行验收测试的代码
  • codecept run functional,运行功能测试的代码
  • codecept run unit,运行单元测试的代码

运行验收测试的时候,会报错~Suite 'acceptance' could not be found,因为没有找到验收测试。

写个单元测试,小试身手~

  1. 创建测试文件,以下命令皆可,声称ExampleTest.php文件:
  • codecept generate:phpunit unit Example
  • codecept generate:test unit Example

使用codecept generate:test unit Test命令,创建的Test.php文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class Test extends \Codeception\Test\Unit
{
/**
* @var \UnitTester
*/
protected $tester;
//预定义方法,可以在每个测试方法运行前创建一个测试对象
protected function _before()
{
}
//预定义方法,可以在每个测试运行结束后销毁一个测试对象
protected function _after()
{
}
// tests
public function testMe()
{
}
}

使用codecept generate:phpunit unit Example命令,创建的ExampleTest.php文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class ExampleTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
}
protected function tearDown()
{
}
// tests
public function testMe()
{
}
}

大家可以发现以下区别: codecept generate:test unit Test命令创建的文件:

  • 父类是\Codeception\Test\Unit
  • 方法是_before()_after()

而codecept generate:phpunit unit Example`命令创建的文件:

  • 父类是\PHPUnit_Framework_TestCase,这个是PHPUnit创建形式。
  • 方法是setUp()tearDown()

codeception_before()_after()取代了在PHPUnit中的setUp()tearDown(), 事实上,setUp()tearDown()是在其继承的父类\Codeception\Test\Unit中已经完成了。

  1. 运行单元测试,以下命令皆可
  • codecept run unit ExampleTest运行某一个测试文件 或
  • codecept run unit运行所有的单元测试
  1. 传统的单元测试:

在yii2中写一个传统的单元测试,相关的model部分,自己补充,这里不多说了,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
namespace tests\unit;
use app\models\User;
use Codeception\Specify;
class Test extends \Codeception\Test\Unit
{
use Specify;
/**
* @var \UnitTester
*/
protected $tester;
protected function _before()
{
}
protected function _after()
{
}
public function testValidation() {
$user = new User();
$user->username = null;
$this->assertFalse($user->validate(['username']));
$user->username = 'toolooooongnaaaaaaameeeeeeeeeeeeeee';
$this->assertFalse($user->validate(['username']));
$user->username = 'davert';
$this->assertTrue($user->validate(['username']));
}
}
  1. 测试数据库:

在上面代码中,添加一个方法:

1
2
3
4
5
6
7
public function testSavingUser() {
$user = new User();
$user->setAttributes(['username'=>'username','password'=>'password','email'=>'yuan@qq.com']);
$user->save(false);
$this->assertEquals('username',$user->getAttribute('username'));
$this->tester->canSeeRecord('app\models\User',array('username'=>'username','password'=>'password','email'=>'yuan@qq.com'));
}

如果执行失败,会在terminal中显示failure,这时说明创建用户数据有问题,要开发者自己检查相应的错误原因并改正。 数据库数据,会在每次测试结束后填充和清除~

一般情况下,我们是不能直接访问数据库的ORM的,这里为什么能访问呢?

是因为,框架在单元测试配置文件中已经设置好了,单元测试的配置都在unit.suite.yml中,默认配置如下:

1
2
3
4
5
6
7
8
9
10
11
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: UnitTester
modules:
enabled:
- Asserts
- Yii2:
part: [orm, email] #此处配置了orm

除了canSeeRecord()方法,在Yii2中还有其他类似的方法,如下:

  • seeRecord()
  • dontSeeRecord()
  • haveRecord()

每个框架的配置略微不一样,具体想看官方文档单元测试-Interacting with the Framework部分

  1. BBD规范测试

BBD 全称 Behavior Driven Development,中文翻译过来是行为驱动测试。前文我们也介绍过~

在写测试的时候,当应功能需求发生变化,测试也要随时相应变化,所以测试随时准备好变化。 所以测试代码要非常的易读和容易维护。如果在团队中没有记录测试的约定,那当功能发生变化或者引入新功能时,找到相应的测试代码就不容易了~ 所以我们不仅要测试代码覆盖整个应用程序,也要让测试代码可读性很强。 因此,我们要引入一个独立的项目 specify用于单元测试的规范~

我们将之前的代码修改下,引入Codeception\Specify,这个微小的库可以增加更多的可读性断言; 然后使用specify方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function testValidation() {
$user = new User();
$this->specify('username is required',function() use($user) {
$user->username = null;
$this->assertFalse($user->validate(['username']));
});
$this->specify('username is too long',function() use($user) {
$user->username = 'toolooooongnaaaaaaameeeeeeeeeeeeeee';
$this->assertFalse($user->validate(['username']));
});
$this->specify('test true assert user',function() use($user) {
$user->username = 'davert';
$this->assertTrue($user->validate(['username']));
});
}
  • specify(String $description,function closure)
    • 第一个参数description,是用于描述你要测试的场景。这样便于维护和阅读;
    • 第二个参数是一个匿名回调函数,是测试某场景的具体逻辑代码;
    • 每个specify里的代码都是相互独立的、不互相影响的;specify使用了深浅克隆策略来保存对象在每个作用域中保存对象。
    • 缺点是,在使用深克隆的时候会增加内存消耗,使用浅克隆的时候会不完全隔离;
  1. 总结

PHPUnit测试是测试套件中的一流的。每当需要编写和执行单元测试时,不需要单独安装PHPUnit,而是直接使用Codeception来执行它们。 通过集成Codeception模块,可以将一些不错的功能添加到常规单元测试中。对于大多数单元和集成测试,PHPUnit测试就足够了。它们速度快,易于维护。

这篇文章只能算codeception测试框架的初探,还有很多关于单元的东西没有讲太清楚~这个需要我花更多的时间去研究和探索~~ 当我深入研究有新发现的时候,会跟大家在这里分享的~

踩过的坑儿~~

1、如果已经执行过codecept bootstrap,再执行此命令,换出现如下情况:

1
2
3
bash:basic yuan$ codecept bootstrap
Project is already initialized in '.'

2、我执行codecept buildcodecept run就会出现下面的报错情况。 这是因为已经全局安装codeception 2.0,然后项目里安装了codeception 2.1+。 解决办法: 我用命令composer remove codeception/codeception把项目里的codeception移除之后,就正常了。 我是在这里发现的,大家有兴趣也可以看看~~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bash:basic yuan$ codecept build
Building Actor classes for suites: functional, unit
PHP Catchable fatal error: Argument 1 passed to Codeception\Module::__construct() must be an instance of Codeception\Lib\ModuleContainer, array given, called in /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Configuration.php on line 327 and defined in /Users/yuan/PhpstormProjects/basic/vendor/codeception/base/src/Codeception/Module.php on line 70
PHP Stack trace:
PHP 1. {main}() /Users/yuan/.composer/vendor/codeception/codeception/codecept:0
PHP 2. Symfony\Component\Console\Application->run() /Users/yuan/.composer/vendor/codeception/codeception/codecept:27
PHP 3. Symfony\Component\Console\Application->doRun() /Users/yuan/.composer/vendor/symfony/console/Application.php:126
PHP 4. Symfony\Component\Console\Application->doRunCommand() /Users/yuan/.composer/vendor/symfony/console/Application.php:195
PHP 5. Symfony\Component\Console\Command\Command->run() /Users/yuan/.composer/vendor/symfony/console/Application.php:878
PHP 6. Codeception\Command\Build->execute() /Users/yuan/.composer/vendor/symfony/console/Command/Command.php:259
PHP 7. Codeception\Command\Build->buildActorsForConfig() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Command/Build.php:46
PHP 8. Codeception\Lib\Generator\Actor->__construct() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Command/Build.php:67
PHP 9. Codeception\Configuration::modules() /Users/yuan/PhpstormProjects/basic/vendor/codeception/base/src/Codeception/Lib/Generator/Actor.php:44
PHP 10. Codeception\Configuration::createModule() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Configuration.php:287
PHP 11. Codeception\Module->__construct() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Configuration.php:327
Catchable fatal error: Argument 1 passed to Codeception\Module::__construct() must be an instance of Codeception\Lib\ModuleContainer, array given, called in /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Configuration.php on line 327 and defined in /Users/yuan/PhpstormProjects/basic/vendor/codeception/base/src/Codeception/Module.php on line 70
Call Stack:
0.0002 238464 1. {main}() /Users/yuan/.composer/vendor/codeception/codeception/codecept:0
0.0171 2700328 2. Symfony\Component\Console\Application->run() /Users/yuan/.composer/vendor/codeception/codeception/codecept:27
0.0198 3058600 3. Symfony\Component\Console\Application->doRun() /Users/yuan/.composer/vendor/symfony/console/Application.php:126
0.0200 3059520 4. Symfony\Component\Console\Application->doRunCommand() /Users/yuan/.composer/vendor/symfony/console/Application.php:195
0.0200 3060024 5. Symfony\Component\Console\Command\Command->run() /Users/yuan/.composer/vendor/symfony/console/Application.php:878
0.0202 3063904 6. Codeception\Command\Build->execute() /Users/yuan/.composer/vendor/symfony/console/Command/Command.php:259
0.0202 3064536 7. Codeception\Command\Build->buildActorsForConfig() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Command/Build.php:46
0.0363 4901592 8. Codeception\Lib\Generator\Actor->__construct() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Command/Build.php:67
0.0372 5024000 9. Codeception\Configuration::modules() /Users/yuan/PhpstormProjects/basic/vendor/codeception/base/src/Codeception/Lib/Generator/Actor.php:44
0.0372 5024720 10. Codeception\Configuration::createModule() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Configuration.php:287
0.0382 5226888 11. Codeception\Module->__construct() /Users/yuan/.composer/vendor/codeception/codeception/src/Codeception/Configuration.php:327

如果大家有什么问题,或者本文有错误,或者有什么好资源共享,欢迎在留言或者在这里提issue

参考文章: