在 Laravel 中设置 PHPUnit 测试

Posted

技术标签:

【中文标题】在 Laravel 中设置 PHPUnit 测试【英文标题】:Setting up PHPUnit tests in Laravel 【发布时间】:2016-02-18 15:40:27 【问题描述】:

我对单元测试还很陌生,但我已经阅读了phpunit.de 上的几乎所有文档(直到第 10 章)。

它指出使用数据库进行测试可能会很慢,但如果设置正确,它可以与非数据库测试一样快。

因此,我想在 Laravel 中测试一个模型。我创建了一个模型工厂来将数据播种到数据库中。

我还创建了一个基本测试。

在 PHPUnits 文档中,它声明在每次测试之前,都会调用 setUp() 方法来设置测试。还有一个静态方法setUpBeforeClass()

我只想为我的数据库表播种一次,并在我的测试中使用这些记录。因此,我使用 Laravel factory() 函数从 setUpBeforeClass() 方法中为数据库播种。

这是我的代码:

class CommentTest extends TestCase

    protected static $blog;
    protected static $comments;

    public static function setUpBeforeClass()
    
        parent::setUpBeforeClass();

        self::$blog = factory(App\Models\Content\Blog::class)->create();
        self::$comments = factory(App\Models\Content\Comment::class, 6)->create();
    

    public function testSomething()
    
        $this->assertTrue(true);
    

但是,当我运行 phpunit 时,我收到以下错误:

Fatal error: Call to a member function make() on a non-object in \vendor\laravel\framework\src\Illuminate\Foundation\helpers.php on line 54

Call Stack:
    0.0002     240752   1. main() \vendor\phpunit\phpunit\phpunit:0
    0.0173    1168632   2. PHPUnit_TextUI_Command::main() \vendor\phpunit\phpunit\phpunit:47
    0.0173    1175304   3. PHPUnit_TextUI_Command->run() \vendor\phpunit\phpunit\src\TextUI\Command.php:100
    2.9397    5869416   4. PHPUnit_TextUI_TestRunner->doRun() \vendor\phpunit\phpunit\src\TextUI\Command.php:149
    2.9447    6077272   5. PHPUnit_Framework_TestSuite->run() \vendor\phpunit\phpunit\src\TextUI\TestRunner.php:440
    2.9459    6092880   6. PHPUnit_Framework_TestSuite->run() \vendor\phpunit\phpunit\src\Framework\TestSuite.php:747
    2.9555    6096160   7. call_user_func:\vendor\phpunit\phpunit\src\Framework\TestSuite.php:697() \vendor\phpunit\phpunit\src\Framework\TestSuite.php:697
    2.9555    6096272   8. CommentTest::setUpBeforeClass() \vendor\phpunit\phpunit\src\Framework\TestSuite.php:697
    2.9555    6096480   9. factory() \tests\CommentTest.php:18
    2.9556    6096656  10. app() \vendor\laravel\framework\src\Illuminate\Foundation\helpers.php:350

如果我将代码从 setUpBeforeClass() 移动到 setUp() 并运行它,它会按预期工作,但是这肯定是低效的,因为它为每次测试都为数据库播种?

我的问题:

    setUpBeforeClass() 中为数据库播种是正确的方法吗? 如果是(问题 1),那么为什么我在运行 phpunit 时会出现致命错误,在调用 factory() 之前我应该​​做些什么? 如果我必须将代码放在setUp() 方法中,会不会出现性能问题? 我是否应该从setUpBeforeClass()setUp() 方法播种?在 Laravels 文档中,它显示了在测试本身中进行播种的示例,但是如果我正在运行 100 个测试(例如),那么播种 100 次是个好主意吗?

【问题讨论】:

【参考方案1】:

好的,经过一番调查(类),我确定在调用静态 setUpBeforeClass() 方法时尚未创建 Laravel 应用程序。

Laravel 容器是在 setUp() 第一次在 \vendor\laravel\framework\src\illuminate\Foundation\Testing\TestCase.php 中调用时创建的。这就是为什么当我将代码移动到 setUp() 方法时它可以正常工作的原因。

容器然后存储在$app 属性中,该属性存储在\vendor\laravel\framework\src\illuminate\Foundation\Testing\ApplicationTrait.php 中。

我可以通过将此代码添加到setUpBeforeClass() 方法来手动创建容器实例:

$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

但是这种方法看起来很hacky,我不喜欢它。

相反,我将播种代码移至setUp() 方法,但仅在类属性为空时才播种数据库。因此,它只会在第一次调用setUp() 时播种。任何后续调用都不会被播种:

class CommentTest extends TestCase

    use DatabaseMigrations;

    protected static $blog;
    protected static $comments;

    public function setUp()
    
        parent::setUp();

        $this->runDatabaseMigrations();

        if (is_null(self::$blog)) 
            self::$blog = factory(App\Models\Content\Blog::class, 1)->create();
            self::$comments = factory(App\Models\Content\Comment::class, 6)->create();
        
    

结合 Laravel DatabaseMigrations trait 进行测试,现在的工作流程如下:

    PHPUnit 被调用 Test 类被调用,其中包含DatabaseMigrations trait 数据库已迁移(已创建表) 第一次调用setUp()方法,用测试数据播种相关表 测试运行,并访问测试数据 没有调用tearDown() 方法,而是DatabaseMigrations trait 只是简单地重置数据库,所以我的测试不必担心清理测试数据。

编辑

另外,看起来(虽然我不是 100%),如果你有一个自定义的setUp() 方法,你需要从被覆盖的setUp() 方法中手动调用runDatabaseMigrations()

public function setUp()

    parent::setUp();
    $this->runDatabaseMigrations();

    /** Rest of Setup **/

如果重载setUp() 方法,runDatabaseMigrations() 似乎不会被自动调用。

我希望这会有所帮助,但如果其他人有更好的解决方案,请随时告诉我:)

【讨论】:

Phil,知道为什么 setUp 超载时不会触发“runDatabaseMigrations”吗?我查看了代码,似乎无法理解为什么? @Matt 不幸的是我不知道为什么! (如果我的假设是正确的)。我也查看了代码,也无法弄清楚:(这个问题也提到了这个问题:***.com/questions/33584071/… 谢谢!!我自己刚刚遇到这个问题,想知道为什么在 setUpBeforeClass 之前没有设置应用程序。我也在跟踪代码并自己创建一个 SO 问题,直到我发现这个:) @JosephCrawford 很高兴它有帮助:) 我开始测试提到的 runDatabaseMigrations 问题,但正如 github.com/laravel/framework/commit/… 所述,它已在 5.2 中修复

以上是关于在 Laravel 中设置 PHPUnit 测试的主要内容,如果未能解决你的问题,请参考以下文章

在 catch 块中设置对象属性时 PHPUnit 测试失败

在 PHPUnit/DBUnit 中设置外键约束

Laravel PHPUnit 加载 .env.testing 文件

phpunit 8 及更高版本中设置套件的问题

使用 WSL-2 和 Docker 在 PhpStorm 中设置 PHPUnit:无法解析 PHPUnit 版本输出:无法打开输入文件

在 laravel 8 中设置单元测试环境