Yii2单元测试初探

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      继承自yiicodeceptionTestCase,指定了配置文件为'@tests/codeception/config/backend/unit.php'
------------DbTestCase.php    继承自yiicodeceptionDbTestCase,指定了配置文件为'@tests/codeception/config/backend/unit.php'
--------acceptance            backend验收测试文件目录
--------functional            backend功能测试文件目录
--------_bootstrap.php        执行build命令时若codeception.yml文件指定了settings:bootstrap则在此时加载其内容,参见CodeceptionConfiguration::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代码,实例化一个CodeceptionApplication对象$app,并添加了预设的命令,例如:build,run,GenerateCept等,$app->run();

(2)上面$app->run()获得命令行输入的参数后执行基类SymfonyComponentConsoleApplication->run() =>doRun()=>doRunCommand();

这一步从参数获得要执行的命令,接下来执行命令。

(3)codeception的所有命令都在vendor/codeception/src/Codeception/Command目录中,均继承自SymfonyComponentConsoleCommandCommand。以build命令为例:

SymfonyComponentConsoleCommandCommand::run()=>CodeceptionCommandBuild::execute()

这里开始了build命令的真正执行,找到了命令的入口,接下来就看看常用的build和run命令具体干了些什么,那些配置文件是在何时发挥作用的。

4.build干了什么?

(1)命令入口:CodeceptionCommandBuild::execute()=>$this->buildActorsForConfig()

(2)加载全局测试配置信息: CodeceptionCommandSharedConfig::getGlobalConfig() => CodeceptionConfiguration::config()

这里执行了一个trait的方法用于加载全局测试配置信息,也就是-c参数指定的codeception.yml文件的内容,过程中会调用CodeceptionConfiguration::loadBootstrap()此时加载指定测试目录下的bootstrap文件,通常是_bootstrap.php,在3小节中会加载tests/backend/_bootstrap.php文件内容。

(3)构建测试套件:$this->buildSuiteActors(),这里主要看两步骤内容:

1)构建测试方法:在_support/_generated目录下生成与suite的class_name对应的trait文件,内容是在yml中配置的module的所有可以在测试文件中使用的actions

=>$this->buildActions()=>CodeceptionLibGeneratorActions::produce()

2)构建测试角色:在_support目录下生成与suite的class_name对应的文件,内容是一个actor类,引用了上一步生成的对应suite的action trait
=>$this->buildActor()=>CodeceptionLibGeneratorActor::produce()

到这里,找到了codeception.yml的加载时机,知道了_support里的文件是怎么来的,以及yml中指定的modules内容是如何可以在测试文件中通过actor对象直接访问的,终于知道模板示例中的$tester->haveFixture()方法是怎么来的了。

5.run命令干了什么?

(1)命令入口:CodeceptionCommandRun::execute();=> new CodeceptionCodecept();

(2)准备运行测试套件: CodeceptionCodecept::run($suite,$test); => CodeceptionCodecept::runSuite();

(3)由套件管理器运行测试套件: $suiteManager = new CodeceptionSuiteManager(); $suiteManager->initialize()

这里的套件管理器初始化时会触发Events::MODULE_INIT事件,从而执行yml文件指定的modules的_initialize()方法,Events::SUITE_INIT事件,这一事件的订阅者会加载对应suite下的bootstrap文件,在3小节示例中对应的是tests/backend/unit/_bootstrap.php文件。

(4)运行测试:PHPUnitRunner::doEnhanceRun();

=> yiicodeceptionTestCase::run(); => PHPUnit_Framework_TestCase::run(); => PHPUnit_Framework_TestResult::run(); startTest(); endTest();

到这里,找到了各个套件下_bootstrap.php文件的加载时机,也找到了modules的初始化时机,这里提到了codeception的事件处理,下面总结下codeception的事件发布订阅机制。

6.dispatcher和subscriber

发布者:SymfonyComponentEventDispatcherEventDispatcher 实现了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之间可以存在依赖关系,这里离不开依赖注入的实现CodeceptionLibDi类。

 build命令执行到CodeceptionLibGeneratorActions::produce()前,在CodeceptionLibGeneratorActions::__construct()这一步实例化Di,ModuleContainer,获取所需modules;
(1)获取yml配置的modules名称列表: CodeceptionConfiguration::modules()
(2)使用Di实例化modules并获取可用actions:CodeceptionLibModuleContainer::__construct(Di $di,$config) =>CodeceptionLibModuleContainer::create()

到这里知道了yml中配置的modules是如何被找到并实例化,以及依赖关系是怎样处理的,那一个具体的module是如何在测试文件中发挥作用的?

以Yii2这个module为例,这里有一些钩子函数,_initialize(),_before()等,分别在测试的不同环节被执行,就像run命令运行到套件管理器初始化时会触发事件导致module的_initialize()方法得以执行。_before()方法在Events::TEST_BEFORE事件触发时运行,并会根据$configFile指定的配置文件实例化一个Yii::$app对象供测试方法使用。

8.$this->tester属性是在哪里定义?何时实例化的?

猜想应该是在CodeceptionTestUnit基类里,绝不会跑到PhpUnit层。上面已经知道Actor是在build命令中构建的,这里的$tester就是Actor的实例。果然在CodeceptionTestUnit类的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'));
        }

        //......此处省略部分代码
    }
原文地址:https://www.cnblogs.com/ling-diary/p/9110906.html