查看: 1619|回复: 0

[PHP学习] 写Laravel测试代码(二)

发表于 2017-8-12 08:00:03
尚学堂AD

本文主要探讨数据库测试。

在写Laravel测试代码(一) 中聊了关于如何提高 laravel 数据库测试性能,其实简单一句就是:每一个test case, 只重新 seed 被污染的表。 OK,这里有一个前提问题:那如何构建临时测试数据库呢?本文主要探讨如何构建临时测试数据库。

数据库设计图纸

任何一个软件都需要数据库设计图纸,可以使用免费的MySqlWorkbench或者收费的Navicat Data Modler软件。这里使用免费的MySqlWorkbench来设计数据库图纸,类似下图:

这里作为范例简单设计了5个model,当然大型程序都会有100个以上model。再利用软件的Export SQL功能导出数据库的schema,这个schema文件就作为构建临时测试数据库的原料,schema文件类似如下:

临时数据库构建类

在得到 schema 文件后,就可以写一个临时数据库构建类来创建临时测试数据库。这里临时表示该测试数据库使用完后即drop掉,且数据库名字是随机的,这样可以保证同时并发进行测试。需要先在phpunit.xml中指定数据库配置信息:

  1. ...
  2. <php>
  3. <env name="APP_ENV" value="testing"/>
  4. <env name="CACHE_DRIVER" value="array"/>
  5. <env name="SESSION_DRIVER" value="array"/>
  6. <env name="QUEUE_DRIVER" value="sync"/>
  7. <env name="DB_DATABASE" value="lx1036"/>
  8. <env name="DB_USERNAME" value="testing"/>
  9. <env name="DB_PASSWORD" value="testing"/>
  10. </php>
  11. </phpunit>
复制代码

然后在config/database.php中写上当运行测试时指定新构建的测试数据库:

  1. 'mysql' => [
  2. 'driver' => 'mysql',
  3. 'host' => env('DB_HOST', '127.0.0.1'),
  4. 'port' => env('DB_PORT', '3306'),
  5. 'database' => env('APP_ENV') === 'testing' ? \Tests\Database::getRandomDBName(env('DB_DATABASE', 'lx1036'), env('DB_HOST', 'localhost'), env('DB_USERNAME', 'root'), env('DB_PASSWORD')) : env('DB_DATABASE', 'forge'),
  6. 'username' => env('DB_USERNAME', 'forge'),
  7. 'password' => env('DB_PASSWORD', ''),
  8. 'unix_socket' => env('DB_SOCKET', ''),
  9. 'charset' => 'utf8mb4',
  10. 'collation' => 'utf8mb4_unicode_ci',
  11. 'prefix' => '',
  12. 'strict' => true,
  13. 'engine' => null,
  14. ],
复制代码

然后写一个临时测试数据库构建类:

  1. <?php
  2. namespace Tests;
  3. use PDO;
  4. /**
  5. * Singleton class to enable parallel PHPUnit processes
  6. *
  7. * 1) Generate a random testing database with automatic destroy upon finish
  8. * 2) Initialize the database schemas using SQL file specified by constant SQL_PATH
  9. * 3) Remove orphan test databases
  10. */
  11. class Database
  12. {
  13. /** @var \Tests\Database singleton to drop test database in destructor */
  14. protected static $instance;
  15. /** @var string */
  16. protected static $db_name;
  17. /** @var string */
  18. protected static $host;
  19. /** @var string */
  20. protected static $username;
  21. /** @var string */
  22. protected static $password;
  23. public function __construct(string $db_name)
  24. {
  25. static::$db_name = $db_name;
  26. }
  27. public function __destruct()
  28. {
  29. if (static::$db_name) {
  30. $pdo = new PDO('mysql:host=' . static::$host . ';' . 'dbname=' . static::$db_name, static::$username, static::$password);
  31. $pdo->exec('DROP DATABASE `' . static::$db_name . '`');
  32. }
  33. }
  34. public static function getRandomDBName(string $prefix, string $host, string $username, string $password, string $charset = 'utf8mb4', string $collation = 'utf8mb4_unicode_ci'): string
  35. {
  36. if (static::$instance) {
  37. return static::$instance->getDBName();
  38. }
  39. $db_name = $prefix . '_' . date('ymd') . '_' . str_random();
  40. $pdo = new PDO('mysql:host=' . $host, $username, $password);
  41. // Remove orphan database
  42. static::removeOrphans($pdo, $prefix);
  43. // Create random database
  44. $pdo->exec('CREATE DATABASE `' . $db_name . '` DEFAULT CHARACTER SET ' . $charset . ' COLLATE ' . $collation);
  45. $pdo->exec('USE `' . $db_name . '`');
  46. // Create tables in specified random database
  47. $schema_file = __DIR__ . '/../database/seeds/mysql.sql';
  48. if ($pdo->exec(file_get_contents($schema_file)) === false) {
  49. throw new \ErrorException("Cannot create tables by sql file: " . $schema_file . ' because of ' . $pdo->errorInfo()[2]);
  50. }
  51. /*
  52. // Check if tables are inserted.
  53. $result = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_NUM);
  54. dump($result);*/
  55. static::$instance = new static($db_name);
  56. static::$host = $host;
  57. static::$username = $username;
  58. static::$password = $password;
  59. dump($db_name);
  60. return $db_name;
  61. }
  62. /**
  63. * Remove orphan database if exists.
  64. *
  65. * @param PDO $pdo
  66. * @param string $prefix
  67. */
  68. public static function removeOrphans(PDO $pdo, string $prefix)
  69. {
  70. $databases = $pdo->query('SHOW DATABASES LIKE "' . $prefix . '%"')->fetchAll();
  71. foreach ($databases as $database) {
  72. $database = reset($database);
  73. if (starts_with($database, $prefix) && is_numeric(explode('_', $database)[1])) {
  74. $pdo->exec('DROP DATABASE `' . $database . '`');
  75. echo 'Drop database ' . $database . PHP_EOL;
  76. }
  77. }
  78. }
  79. /**
  80. * @return string
  81. */
  82. public static function getDBName(): string
  83. {
  84. return static::$db_name;
  85. }
  86. /**
  87. * @return string
  88. */
  89. public static function getHost(): string
  90. {
  91. return static::$host;
  92. }
  93. /**
  94. * @return string
  95. */
  96. public static function getUsername(): string
  97. {
  98. return static::$username;
  99. }
  100. /**
  101. * @return string
  102. */
  103. public static function getPassword(): string
  104. {
  105. return static::$password;
  106. }
  107. }
复制代码

这样,当运行测试时连接的就是临时构建的测试数据库,测试运行完毕就drop掉数据库,并且可以同时开多个窗口(线程)来分组运行test cases。最后还得在mysql localhost中创建testing@testing用户并授权,以root用户登录local mysql:

  1. CREATE USER 'testing'@'localhost' IDENTIFIED BY 'testing';
  2. GRANT ALL ON `lx1036%`.* TO 'testing'@'localhost';
复制代码

这样就临时测试数据库就准备完毕了,然后就是seed 测试数据,执行unit/feature tests, 执行assert等等,可以参考写Laravel测试代码(一)。这里运行phpunit时得到的临时测试数据库是:

OK,后续再聊执行unit/feature tests时一些实践技巧。

RightCapital招聘Laravel DevOps



回复

使用道具 举报