资讯专栏INFORMATION COLUMN

写Laravel测试代码(一)

MageekChiu / 2630人阅读

摘要:在中可以在设置数据库重装操作这样就可以在每一个中定义本次污染的数据表,保证下一个在运行前重刷下被污染的数据表,如这样会极大提高数据库测试效率,不推荐使用给出的和,效率并不高。

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

写laravel程序时,除了写生产代码,还需要写测试代码。其中,写数据库测试比较麻烦,因为需要针对每一个test case需要建立好数据集,该次test case污染的数据表还需要恢复现场,避免影响下一个test case运行,同时还得保证性能问题,否则随着程序不断膨胀,测试数量也越多,那每一次测试运行需要花费大量时间。

有两个比较好的方法可以提高数据库测试性能:

对大量的tests按照功能分组。如有1000个tests,可以按照业务功能分组,如group1:1-200, group2:201-800, group3: 801-1000。这样可以并发运行每组测试包裹。

只恢复每个test case污染的表,而不需要把所有的数据表重新恢复,否则表数量越多测试代码执行越慢。

这里聊下方法2的具体做法。

假设程序有50张表,每次运行测试时首先需要为每组构建好独立的对应数据库,然后创建数据表,最后就是填充测试数据(fixtures)。fixtures可用yml格式定义,既直观也方便维护,如:

#simple.yml
accounts:
  - id: 1
    person_id: 2
    type: investment
    is_included: true
  - id: 2
    person_id: 2
    type: investment
    is_included: true
transactions:
  - account_id: 1
    posted_date: "2017-01-01"
    amount: 10000
    transaction_category_id: 1   
  - account_id: 2
    posted_date: "2017-01-02"
    amount: 10001
    transaction_category_id: 2

然后需要写个yamlSeeder class来把数据集填充到临时数据库里:

abstract class YamlSeeder extends IlluminateDatabaseSeeder
{
    private $files;

    public function __construct(array $files)
    {
        $this->files = $files
    }
    
    public function run(array $tables = []): void
    {
        // Close unique and foreign key constraint
        $db = $this->container["db"];
        $db->statement("SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;");
        $db->statement("SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;");
        
        foreach($this->files as $file) {
            ...
            
            // Convert yaml data to array
            $fixtures = SymfonyComponentYamlYaml::parse(file_get_contents($file));
            
            ...
            
            foreach($fixtures as $table => $data) {
                // Only seed specified tables, it is important!!!
                if ($tables && !in_array($table, $tables, true)) {
                    continue;
                }
                
                $db->table($table)->truncate();

                if (!$db->table($table)->insert($data)) {
                    throw new RuntimeException("xxx");
                }
            }
            
            ...
        }
        
        // Open unique and foreign key constraint
        $db->statement("SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;");
        $db->statement("SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;");
    }
}

class SimpleYamlSeeder extends YamlSeeder
{
    public function __construct()
    {
        parent::__construct([database.path("seeds/simple.yml")]);
    }
}

上面的代码有一个关键处是参数$tables:如果参数是空数组,就把所有数据表数据插入随机数据库里;如果是指定的数据表,只重刷指定的数据表。这样会很大提高数据库测试的性能,因为可以在每一个test case里只需要指定本次测试所污染的数据表。在tests/TestCase.php中可以在setUp()设置数据库重装操作:

    abstract class TestCase extends IlluminateFoundationTestingTestCase
    {
        protected static $tablesToReseed = [];
        
        public function seed($class = "DatabaseSeeder", array $tables = []): void
        {
            $this->artisan("db:seed", ["--class" => $class, "--tables" => implode(",", $tables)]);
        }
        
        protected function reseed(): void
        {
            // TEST_SEEDERS is defined in phpunit.xml, e.g. 
            $seeders = env("TEST_SEEDERS") ? explode(",", env("TEST_SEEDERS")) : [];
            
            if ($seeders && is_array(static::$tablesToReseed)) {
                foreach ($seeders as $seeder) {
                    $this->seed($seeder, static::$tablesToReseed);
                }
            }
            
            Cache::flush();
            
            static::$tablesToReseed = false;
        }
        
        protected static function reseedInNextTest(array $tables = []): void
        {
            static::$tablesToReseed = $tables;
        }
    }

这样就可以在每一个test case中定义本次污染的数据表,保证下一个test case在运行前重刷下被污染的数据表,如:

    final class AccountControllerTest extends TestCase
    {
        ...
        
        public function testUpdateAccount()
        {
            static::reseedInNextTest([Account::class, Transaction::class]);
            
            ...
        }
        
    }

这样会极大提高数据库测试效率,不推荐使用Laravel给出的IlluminateFoundationTestingDatabaseMigrations 和 IlluminateFoundationTestingDatabaseTransactions,效率并不高。

laravel的db:seed命令没有--tables这个options,所以需要扩展IlluminateDatabaseConsoleSeedsSeedCommand:

class SeedCommand extends IlluminateDatabaseConsoleSeedsSeedCommand
{
    public function fire()
    {
        if (!$this->confirmToProceed()) {
            return;
        }

        $this->resolver->setDefaultConnection($this->getDatabase());

        Model::unguarded(function () {
            $this->getSeeder()->run($this->getTables());
        });
    }
    
    protected function getTables()
    {
        $tables = $this->input->getOption("tables");

        return $tables ? explode(",", $tables) : [];
    }

    protected function getOptions()
    {
        $options   = parent::getOptions();
        $options[] = ["tables", null, InputOption::VALUE_OPTIONAL, "A comma-separated list of tables to seed, all if left empty"];

        return $options;
    }
}

当然还得写SeedServiceProvider()来覆盖原有的IlluminateFoundationProvidersConsoleSupportServiceProvider::registerSeedCommand()中注册的command.seed,然后在config/app.php中注册:

class SeedServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * @see IlluminateDatabaseSeedServiceProvider::registerSeedCommand()
     */
    public function register()
    {
        $this->app->singleton("command.seed", function ($app) {
            return new SeedCommand($app["db"]);
        });

        $this->commands("command.seed");
    }

    public function provides()
    {
        return ["command.seed"];
    }
}

OK,这样所有的工作都做完了。。以后写数据库测试性能会提高很多,大量的test case可以在短时间内运行完毕。

最后,写测试代码是必须的,好处非常多,随着项目程序越来越大,就会深深感觉到写测试是必须的,一劳永逸,值得花时间投资。也是作为一名软件工程师的必备要求。

RightCapital招聘Laravel DevOps

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/23105.html

相关文章

  • Laravel测试代码(二)

    摘要:本文主要探讨数据库测试。在写测试代码一中聊了关于如何提高数据库测试性能,其实简单一句就是每一个只重新被污染的表。最后还得在中创建用户并授权,以用户登录这样就临时测试数据库就准备完毕了,然后就是测试数据,执行,执行等等,可以参考写测试代码一。 本文主要探讨数据库测试。 在写Laravel测试代码(一) 中聊了关于如何提高 laravel 数据库测试性能,其实简单一句就是:每一个test ...

    Jackwoo 评论0 收藏0
  • Laravel 测试代码(五)

    摘要:写一个,的是,的内容参照写测试代码三,然后写上很明显,这里测试的是,即和,是一个自定义的,主要功能就是实现了全部,并保存在文件里作为。 本文主要探讨写laravel integration/functional test cases时候,如何assert。前面几篇文章主要聊了如何reseed测试数据,mock数据,本篇主要聊下assert的可行实践,尽管laravel官方文档聊了Tes...

    xbynet 评论0 收藏0
  • Laravel 菜鸟晋级之路

    摘要:用也有三四个月了,虽然是兼职开发,但是使用的频率非常之高,毕竟是产品化的一个项目。第二阶段数据库和开发了比较多的功能之后,会发现需要大量的测试数据,这时候和就该大显身手了。 用Laravel也有三四个月了,虽然是兼职开发,但是使用的频率非常之高,毕竟是产品化的一个项目。在这期间,也踩了无数的坑,走了很多弯路,所以准备把最近的感悟记录下来,方便后来者。 第一阶段:简单的增删改查 这是最...

    YacaToy 评论0 收藏0
  • Laravel 中设计模式的实战分享

    摘要:如何在实战中能应用上设计模式,我思考了接近两年。最开始我接触设计模式,出发点就是为了重构代码,以便更好的复用和测试。也就是说基本是被当成一组静态函数使用的。 MVC是Laravel自带的,大家也多少都会用一些。今天我们不谈MVC,谈一些大中型项目的设计思路。 前言 虽然标题说是设计模式,但是我并不打算去讲什么singleton、strategy、factory,不知道为什么,每次看到...

    李义 评论0 收藏0
  • Laravel 中的数据迁移和数据填充

    摘要:本文首发于作者这是一篇基础教程,对标文档中的数据迁移和数据填充。那么,中的数据库迁移概念,就是用于解决团队中保证数据库结构一致的方案。和不同,如果多次执行就会进行多次数据填充。好了,数据迁移和数据填充的基本操作也就这些了。 showImg(https://segmentfault.com/img/remote/1460000012252769?w=648&h=422); 本文首发于 h...

    mengera88 评论0 收藏0

发表评论

0条评论

MageekChiu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<