tests目录结构解析,怎么这么多yml和_bootstrap?codeception运行流程,build干了什么?run干了什么?codeception.yml怎样发挥作用?modules如何被加载?$tester->haveFixtures()方法是哪里来的?
1.环境
Yii2 advanced模板,version:2.0.11.2,已安装codeception扩展,yii2-codeception扩展
2.tests目录结构
这一版本的tests目录结构不同于后来的更高版本,但总体思想理解后便于版本升级后的快速理解。
tests--codeception----backend--------unit backend单元测试文件目录------------_bootstrap.php backend单元测试所需变量定义,执行run->Codecept::run()->runSuite()->SuitManager::initialize()触发SUITE_INIT事件,此时加载这里的_bootstrap文件,注意:此处文件名称应与yml文件中指定的settings:bootstrap一致,否则抛异常------------TestCase.php 继承自yii\codeception\TestCase,指定了配置文件为'@tests/codeception/config/backend/unit.php'------------DbTestCase.php 继承自yii\codeception\DbTestCase,指定了配置文件为'@tests/codeception/config/backend/unit.php'--------acceptance backend验收测试文件目录--------functional backend功能测试文件目录--------_bootstrap.php 执行build命令时若codeception.yml文件指定了settings:bootstrap则在此时加载其内容,参见Codeception\Configuration::config()--------codeception.yml backend所有测试的测试配置信息--------unit.suite.yml backend所有单元测试套件的测试配置信息--------acceptance.suite.yml backend所有验收测试套件的测试配置信息--------functional.suite.yml backend所有功能测试套件的测试配置信息----config--------backend------------unit.php backend单元测试指定的配置文件------------acceptance.php backend验收测试指定的配置文件------------functional.php backend功能测试指定的配置文件------------config.php backend所有测试均需设置的配置信息--------frontend------------配置结构同backend--------acceptance.php 面向所有验收测试的配置信息--------functional.php 面向所有功能测试的配置信息--------unit.php 面向所有单元测试的配置信息--------config.php 面向所有测试的公共配置信息--codeception.yml 执行所有测试的测试配置信息
3.运行单元测试
(1)cd 进入项目根目录;
(2)执行build命令,-c指定测试配置文件php codeception/codeception/codecept build -c /tests/codeception/backend/codeception.yml
(3)指定测试yml路径,指定执行某个单元测试
php codeception/codeception/codecept run -c /tests/codeception/backend/codeception.yml -- unit resource/MyTest.php窥探整体运行流程
(1)入口:codeception/codeception/Codecept,文件路径vendor/codeception/codeception/codecept,内容其实是php代码,实例化一个Codeception\Application对象$app,并添加了预设的命令,例如:build,run,GenerateCept等,$app->run();
(2)上面$app->run()获得命令行输入的参数后执行基类Symfony\Component\Console\Application->run() =>doRun()=>doRunCommand();
这一步从参数获得要执行的命令,接下来执行命令。
(3)codeception的所有命令都在vendor/codeception/src/Codeception/Command目录中,均继承自Symfony\Component\Console\Command\Command。以build命令为例:
Symfony\Component\Console\Command\Command::run()=>Codeception\Command\Build::execute()
这里开始了build命令的真正执行,找到了命令的入口,接下来就看看常用的build和run命令具体干了些什么,那些配置文件是在何时发挥作用的。
4.build干了什么?
(1)命令入口:Codeception\Command\Build::execute()=>$this->buildActorsForConfig()
(2)加载全局测试配置信息: Codeception\Command\Shared\Config::getGlobalConfig() => Codeception\Configuration::config()
这里执行了一个trait的方法用于加载全局测试配置信息,也就是-c参数指定的codeception.yml文件的内容,过程中会调用Codeception\Configuration::loadBootstrap()此时加载指定测试目录下的bootstrap文件,通常是_bootstrap.php,在3小节中会加载tests/backend/_bootstrap.php文件内容。
(3)构建测试套件:$this->buildSuiteActors(),这里主要看两步骤内容:
1)构建测试方法:在_support/_generated目录下生成与suite的class_name对应的trait文件,内容是在yml中配置的module的所有可以在测试文件中使用的actions
=>$this->buildActions()=>Codeception\Lib\Generator\Actions::produce()
2)构建测试角色:在_support目录下生成与suite的class_name对应的文件,内容是一个actor类,引用了上一步生成的对应suite的action trait
=>$this->buildActor()=>Codeception\Lib\Generator\Actor::produce()到这里,找到了codeception.yml的加载时机,知道了_support里的文件是怎么来的,以及yml中指定的modules内容是如何可以在测试文件中通过actor对象直接访问的,终于知道模板示例中的$tester->haveFixture()方法是怎么来的了。
5.run命令干了什么?
(1)命令入口:Codeception\Command\Run::execute();=> new Codeception\Codecept();
(2)准备运行测试套件: Codeception\Codecept::run($suite,$test); => Codeception\Codecept::runSuite();
(3)由套件管理器运行测试套件: $suiteManager = new Codeception\SuiteManager(); $suiteManager->initialize()
这里的套件管理器初始化时会触发Events::MODULE_INIT事件,从而执行yml文件指定的modules的_initialize()方法,Events::SUITE_INIT事件,这一事件的订阅者会加载对应suite下的bootstrap文件,在3小节示例中对应的是tests/backend/unit/_bootstrap.php文件。
(4)运行测试:PHPUnit\Runner::doEnhanceRun();
=> yii\codeception\TestCase::run(); => \PHPUnit_Framework_TestCase::run(); => \PHPUnit_Framework_TestResult::run(); startTest(); endTest();
到这里,找到了各个套件下_bootstrap.php文件的加载时机,也找到了modules的初始化时机,这里提到了codeception的事件处理,下面总结下codeception的事件发布订阅机制。
6.dispatcher和subscriber
发布者:Symfony\Component\EventDispatcher\EventDispatcher 实现了EventDispatcherInterface
订阅者:codeception实现的订阅者在目录vendor/codeception/codeception/src/Codeception/Subscriber
第5小节提到的两个订阅者分别是Bootstrap和Module,订阅者都有一个静态成员$events记录着各自订阅的事件与对应的处理方法。
7.modules是怎样运作的
codeception的modules存在于目录:vendor/codeception/codeception/src/Codeception/Module
在build命令执行过程中需要加载测试依赖的modules并实例化,modules之间可以存在依赖关系,这里离不开依赖注入的实现Codeception\Lib\Di类。
build命令执行到Codeception\Lib\Generator\Actions::produce()前,在Codeception\Lib\Generator\Actions::__construct()这一步实例化Di,ModuleContainer,获取所需modules;
(1)获取yml配置的modules名称列表: Codeception\Configuration::modules()(2)使用Di实例化modules并获取可用actions:Codeception\Lib\ModuleContainer::__construct(Di $di,$config) =>Codeception\Lib\ModuleContainer::create()到这里知道了yml中配置的modules是如何被找到并实例化,以及依赖关系是怎样处理的,那一个具体的module是如何在测试文件中发挥作用的?
以Yii2这个module为例,这里有一些钩子函数,_initialize(),_before()等,分别在测试的不同环节被执行,就像run命令运行到套件管理器初始化时会触发事件导致module的_initialize()方法得以执行。_before()方法在Events::TEST_BEFORE事件触发时运行,并会根据$configFile指定的配置文件实例化一个Yii::$app对象供测试方法使用。
8.$this->tester属性是在哪里定义?何时实例化的?
猜想应该是在Codeception\Test\Unit基类里,绝不会跑到PhpUnit层。上面已经知道Actor是在build命令中构建的,这里的$tester就是Actor的实例。果然在Codeception\Test\Unit类的setUp()方法中找到了该属性的注入代码。
protected function setUp() { //......此处省略部分代码 /** @var $di Di **/ $di = $this->getMetadata()->getService('di'); $di->set(new Scenario($this)); // auto-inject $tester property if (($this->getMetadata()->getCurrent('actor')) && ($property = lcfirst(Configuration::config()['actor_suffix']))) { $this->$property = $di->instantiate($this->getMetadata()->getCurrent('actor')); } //......此处省略部分代码 }