在Laravel中设置PHPUnit测试

16

我对单元测试还比较新,但已经阅读过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()并运行它,它会按预期工作,但这显然效率低下,因为它为每个测试都提供了种子数据库。

我的问题:

  1. 是否在setUpBeforeClass()中设置数据库是正确的方式?
  2. 如果是(问题1),那么为什么我在运行phpunit时会出现致命错误?在调用factory()之前是否需要做任何事情?
  3. 如果我确实必须将代码放在setUp()方法中,那么会有性能问题吗?
  4. 我是否应该从setUpBeforeClass()setUp()方法种子?在Laravel文档中,示例显示种子是在测试本身中发生的,但如果我运行100个测试(例如),是否多次播种是一个好主意?
1个回答

27

经过一番调查(有关类),我发现在静态方法 setUpBeforeClass() 被调用时,Laravel应用程序尚未被创建。

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

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

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

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

但是这种方法似乎相当繁琐,我不喜欢。

相反,我将初始化代码移到了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测试特性,现在的工作流程如下:

  1. 调用PHPUnit
  2. 调用包含DatabaseMigrations特性的测试类
  3. 迁移数据库(创建表)
  4. 首次调用setUp()方法,使用测试数据填充相关表格
  5. 运行测试并访问测试数据
  6. 无需调用tearDown()方法,DatabaseMigrations特性会重置数据库,因此我的测试不必担心清除测试数据。

编辑

此外,如果您有一个自定义的setUp()方法,则需要从覆盖的setUp()方法中手动调用runDatabaseMigrations()

public function setUp()
{
    parent::setUp();
    $this->runDatabaseMigrations();

    /** Rest of Setup **/
}

runDatabaseMigrations()方法在你重载setUp()方法时似乎不会自动调用。

希望这可以帮到你,但如果有其他更好的解决方案,请随时让我知道:)


Phil,你有什么想法为什么在setUp被重载时“runDatabaseMigrations”不会被触发吗?我已经查看了代码,但似乎无法理解原因? - Matt
很抱歉,@Matt,我不确定为什么!(如果我的假设是正确的话)。我也查看了代码,但也无法弄清楚 :( 这个问题在这个问题中也有提到:http://stackoverflow.com/questions/33584071/laravel-databasetransactions-databasemigrations-have-no-effect-when-testing - Phil Cross
谢谢!我也遇到了这个问题,一直在想为什么应用程序没有在setUpBeforeClass之前设置。我一直在追踪代码,准备自己创建一个SO问题,直到我找到这个答案 :) - Joseph Crawford
1
我开始测试runDatabaseMigrations问题,但是如https://github.com/laravel/framework/commit/231213d5f8ef9779e25af61dcedea0a0984b3cf9所述,它已在5.2中得到修复。 - Dustin Graham
@DustinGraham 很好!谢谢您的评论 :) - Phil Cross
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接