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')); } //......此處省略部分代碼 }