Yii2單元測試初探

tests目錄結構解析,怎麼這麼多yml和_bootstrap?codeception運行流程,build幹了什麼?run幹了什麼?codeception.yml怎樣發揮做用?modules如何被加載?$tester->haveFixtures()方法是哪裏來的?php

1.環境bootstrap

Yii2 advanced模板,version:2.0.11.2,已安裝codeception擴展,yii2-codeception擴展yii2

2.tests目錄結構app

這一版本的tests目錄結構不一樣於後來的更高版本,但整體思想理解後便於版本升級後的快速理解。frontend

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.運行單元測試yii

(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測試

窺探總體運行流程ui

(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'));
        }

        //......此處省略部分代碼
    }
相關文章
相關標籤/搜索