文档章节

写Laravel测试代码(2)

botkenni
 botkenni
发布于 2017/09/03 22:38
字数 970
阅读 9
收藏 0

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

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

数据库设计图纸

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

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

临时数据库构建类

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

...
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="DB_DATABASE" value="lx1036"/>
        <env name="DB_USERNAME" value="testing"/>
        <env name="DB_PASSWORD" value="testing"/>
    </php>
</phpunit>

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

'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            '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'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],

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

<?php

namespace Tests;

use PDO;

/**
 * Singleton class to enable parallel PHPUnit processes
 *
 * 1) Generate a random testing database with automatic destroy upon finish
 * 2) Initialize the database schemas using SQL file specified by constant SQL_PATH
 * 3) Remove orphan test databases
 */
class Database
{
    /** @var  \Tests\Database singleton to drop test database in destructor */
    protected static $instance;

    /** @var string */
    protected static $db_name;

    /** @var string */
    protected static $host;

    /** @var string */
    protected static $username;

    /** @var string */
    protected static $password;

    public function __construct(string $db_name)
    {
        static::$db_name = $db_name;
    }

    public function __destruct()
    {
        if (static::$db_name) {
            $pdo = new PDO('mysql:host=' . static::$host . ';' . 'dbname=' . static::$db_name, static::$username, static::$password);
            $pdo->exec('DROP DATABASE `' . static::$db_name . '`');
        }
    }

    public static function getRandomDBName(string $prefix, string $host, string $username, string $password, string $charset = 'utf8mb4', string $collation = 'utf8mb4_unicode_ci'): string
    {
        if (static::$instance) {
            return static::$instance->getDBName();
        }

        $db_name = $prefix . '_' . date('ymd') . '_' . str_random();

        $pdo = new PDO('mysql:host=' . $host, $username, $password);

        // Remove orphan database
        static::removeOrphans($pdo, $prefix);

        // Create random database
        $pdo->exec('CREATE DATABASE `' . $db_name . '` DEFAULT CHARACTER SET ' . $charset . ' COLLATE ' . $collation);
        $pdo->exec('USE `' . $db_name . '`');

        // Create tables in specified random database
        $schema_file = __DIR__ . '/../database/seeds/mysql.sql';

        if ($pdo->exec(file_get_contents($schema_file)) === false) {
            throw new \ErrorException("Cannot create tables by sql file: " . $schema_file . ' because of ' . $pdo->errorInfo()[2]);
        }

        /*
        // Check if tables are inserted.
        $result = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_NUM);
        dump($result);*/

        static::$instance = new static($db_name);
        static::$host     = $host;
        static::$username = $username;
        static::$password = $password;

        dump($db_name);
        return $db_name;
    }

    /**
     * Remove orphan database if exists.
     *
     * @param PDO $pdo
     * @param string $prefix
     */
    public static function removeOrphans(PDO $pdo, string $prefix)
    {
        $databases = $pdo->query('SHOW DATABASES LIKE "' . $prefix . '%"')->fetchAll();

        foreach ($databases as $database) {
            $database = reset($database);

            if (starts_with($database, $prefix) && is_numeric(explode('_', $database)[1])) {
                $pdo->exec('DROP DATABASE `' . $database . '`');

                echo 'Drop database ' . $database . PHP_EOL;
            }
        }
    }

    /**
     * @return string
     */
    public static function getDBName(): string
    {
        return static::$db_name;
    }

    /**
     * @return string
     */
    public static function getHost(): string
    {
        return static::$host;
    }

    /**
     * @return string
     */
    public static function getUsername(): string
    {
        return static::$username;
    }

    /**
     * @return string
     */
    public static function getPassword(): string
    {
        return static::$password;
    }
}

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

CREATE USER 'testing'@'localhost' IDENTIFIED BY 'testing';
GRANT ALL ON `lx1036%`.* TO 'testing'@'localhost';

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

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

© 著作权归作者所有

共有 人打赏支持
botkenni
粉丝 17
博文 405
码字总数 433796
作品 0
西城
程序员
私信 提问
Laravel5.3之PHP反射(Reflection) (上)

说明:Laravel中经常使用PHP的反射特性来设计代码,本文主要学习PHP的反射特性,来提高写代码时的设计质量。PHP提供一套检测的两个工具包:和,类似于探针一样的东西来探测这些一等公民。本文...

botkenni
2016/11/07
29
0
谈谈我第一次如何为 Laravel 贡献源码

起源 办法总比困难多,所以发现问题比解决问题更难得到。 事情的起源是因为在调试一段模型事件时出现的,当时随便往数据库里插入了一段字符串 ,而不是标准的 。然后发现我的模型事件中查找不...

等月人
05/28
0
0
Laravel5.3之函数处理(Function Handling)

说明:Laravel中经常使用PHP的来设计代码,本文主要学习PHP的特性,来提高写代码时的设计质量。PHP提供了一些函数处理操作的内置函数,主要有: Function Handling calluserfuncarray()/callu...

botkenni
2016/11/15
5
0
技术分享 - 人生苦短, 我用 laravel

date: 2017-12-27 18:35:16 title: 技术分享 - 人生苦短, 我用 laravel 应聘 慕课网 的讲师, 选题是 laravel 相关的, 仔细斟酌之下, 定了这个选题: 人生苦短, 我用 laravel 希望这次分享, 能...

daydaygo
2017/12/28
0
0
Laravel之基于PHPStorm编辑器的Laravel开发

引言 本文主要讲述在PHPStorm编辑器中如何使用PHPStorm的Laravel插件和Laravel IDE Helper来开发Laravel程序,结合个人积累的一点经验来说明使用PHPStorm编辑器来开发程序还是很顺手的,内容...

botkenni
2016/10/10
44
0

没有更多内容

加载失败,请刷新页面

加载更多

/etc/profile和/etc/profile.d/的区别

/etc/profile 是文件, /etc/profile.d/ 是目录,用在设置环境变量方面,/etc/profile 文件和 /etc/profile.d/ 目录实现效果是一样的,可根据自己对环境变量配置的需求场景,选择一种最好的方...

calmsnow
4分钟前
0
0
支付系统设计

概述 支付系统是连接消费者、商家(或平台)和金融机构的桥梁,管理支付数据,调用第三方支付平台接口,记录支付信息(对应订单号,支付金额等),金额对账等功能,根据不同公司对于支付业务...

小致dad
5分钟前
0
0
解决Docker Image镜像无法删除问题的方法

前言 Docker是Docker.Inc公司开源的一个基于轻量级虚拟化技术的容器引擎项目,整个项目基于Go语言开发,并遵从Apache 2.0协议。通过分层镜像标准化和内核虚拟化技术,Docker使得应用开发者和运...

临江仙卜算子
9分钟前
0
0
centos 7 redis_install.sh

#!/bin/bashprintf "============开始安装redis\n"printf "============输入redis下载url,按enter默认下载5.0.3版本\n"download_url='';while truedoread download_url...

偶遇一只小仙女
13分钟前
0
0
分布式消息通信ActiveMQ原理-消费消息策略-笔记

消费端消费消息的原理 我们通过上一节课的讲解,知道有两种方法可以接收消息, 一种是使用同步阻塞的MessageConsumer#receive方法。 另一种是使用消息监听器MessageListener。 这里需要注意的...

Java搬砖工程师
15分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部