自动化测试开发 - 让Migration生成数据库镜像

  • 作者:KK

  • 发表日期:2017.5.27


前提知识

阅读本文章前请先确保你了解Codeception的Db模块,并且知道如何通过Db模块指定dump.sql在每次测试启动时恢复数据库,相关阅读《PHP-Codeception测试开发 - 验收测试 - 基础 - 自动恢复测试数据

一般极少甚至不可能会有人专门找这种解决方案,然而如果发现了它的好处的时候,用起来的确是很棒的,能使Codeception单元测试的数据镜像与Yii的migration实现自动同步,大大增加了开发的便利性!


问题的思考

如何每次运行Migration的时候都生成对应的dump.sql

思路1:执行完migrate控制器后在日志messages里取出SQL保存起来

这个简单点,通过监听yii\console\controllers\BaseMigrateController::EVENT_AFTER_ACTION事件来收集所有执行过的SQL:

//配置如下:
$config['controllerMap'] = [
	'migrate' => [
		'class' => 'yii\console\controllers\MigrateController',
		'on afterAction' => function($event){
			if($event->action->id != 'up'){
				return;
			}
			
			$messages = &Yii::getLogger()->messages;
			$sqlList = [];
			foreach($messages as $message){
				if(in_array($message[2], [
					'yii\db\Command::query',
					'yii\db\Command::execute',
				])){
					$sqlList[] = $message[0] . ';';
				}
				
			}
			file_put_contents(Yii::getAlias('@app/tests/codeception/_data/dump.sql'), implode(PHP_EOL . PHP_EOL, $sqlList));
		}
	],
];

每次调整了数据库结构后,执行migrate/down all清空数据库,再migrate/up all然后发现dump.sql被生成了

有了这个dump.sql就可以在启动测试时导入一个与开发数据库结构一样的镜像了!

缺点:当执行的SQL条数有很多的时候,logger的$messages不会全部记下,而且它里面存的也不只是SQL,一切Yii的消息都在里面,在这种情况下多出的一些旧消息会被unset掉,所以在数据库有一定规模的时候无法记录全部的SQL记录


思路2:边执行边记录

既然执先完migrate/up后保存的messages可能会出现数量过多而丢失消息的情况,那就趁它还没被丢弃就保存下来好了——那就是边执行边记录SQL

这样的话要改造一下了,这里我实施的过程倒是有点难以说清的,涉及的修改代码位置有几个,我先说一下思路:

  1. 创建一个app\ext\DbCommand类继承yii\db\Command,接下来重写父类的execute方法,所做的事情与父类一模一样,但在执行完SQL后加多一个事件触发代码,触发一个afterExecute的事件(具体自己命名)

  2. 创建一个app\ext\MigrationController类,重写父类的beforeAction方法,在这个方法里将db的comandClass的值修改为app\ext\DbCommand,使得migrate的整个过程创建的command都是新定义的类,于是每次执行SQL就会触发事件了

  3. 监听事件,还是在MigrationController::beforeAction方法里,通过yii\base\Event::on方法监听app\ext\DbCommand类的afterExecute事件,提供一个回调,这个回调就是用于将刚执行完的SQL写到dump.sql里

这个过程有点复杂,多数人还是会看不懂,建议去我的项目xoa里看相关代码,涉及的地方有: