• 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'));
            }
    
            //......此处省略部分代码
        }
  • 相关阅读:
    Struts2框架
    读者写者问题
    哲学家就餐问题
    理解中断
    理解处理机调度
    理解死锁
    理解进程
    Linux CentOS 6.7 挂载U盘
    家庭-养老院模型理解IOC和DI
    Bash基础
  • 原文地址:https://www.cnblogs.com/ling-diary/p/9110906.html
Copyright © 2020-2023  润新知