在 Laravel 中设置 PHPUnit 测试

2022-01-25 00:00:00 php laravel phpunit laravel-5.1

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

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

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

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

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

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

这是我的代码:

class CommentTest 扩展了 TestCase{受保护的静态 $blog;受保护的静态 $comments;公共静态函数 setUpBeforeClass(){父::setUpBeforeClass();self::$blog = factory(AppModelsContentBlog::class)->create();self::$comments = factory(AppModelsContentComment::class, 6)->create();}公共函数 testSomething(){$this->assertTrue(true);}}

但是,当我运行 phpunit 时,出现以下错误:

致命错误:在第 54 行对 vendorlaravelframeworksrcIlluminateFoundationhelpers.php 中的非对象调用成员函数 make()调用堆栈:0.0002 240752 1. {main}() vendorphpunitphpunitphpunit:00.0173 1168632 2. PHPUnit_TextUI_Command::main() vendorphpunitphpunitphpunit:470.0173 1175304 3. PHPUnit_TextUI_Command->run() vendorphpunitphpunitsrcTextUICommand.php:1002.9397 5869416 4. PHPUnit_TextUI_TestRunner->doRun() vendorphpunitphpunitsrcTextUICommand.php:1492.9447 6077272 5. PHPUnit_Framework_TestSuite->run()vendorphpunitphpunitsrcTextUITestRunner.php:4402.9459 6092880 6. PHPUnit_Framework_TestSuite->run()vendorphpunitphpunitsrcFrameworkTestSuite.php:7472.9555 6096160 7. call_user_func:{vendorphpunitphpunitsrcFrameworkTestSuite.php:697}()vendorphpunitphpunitsrcFrameworkTestSuite.php:6972.9555 6096272 8. CommentTest::setUpBeforeClass() vendorphpunitphpunitsrcFrameworkTestSuite.php:6972.9555 6096480 9. factory() 	estsCommentTest.php:182.9556 6096656 10.app()vendorlaravelframeworksrcIlluminateFoundationhelpers.php:350

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

我的问题:

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

解决方案

好的,经过一番调查(类),我确定 Laravel 应用程序在静态 setUpBeforeClass() 方法被调用.

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

容器然后存储在 vendorlaravelframeworksrcilluminateFoundationTestingApplicationTrait.php 中的 $app 属性中..p>

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

$app = 需要 __DIR__.'/../bootstrap/app.php';$app->make(IlluminateContractsConsoleKernel::class)->bootstrap();

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

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

class CommentTest 扩展了 TestCase{使用数据库迁移;受保护的静态 $blog;受保护的静态 $comments;公共函数设置(){父::setUp();$this->runDatabaseMigrations();if (is_null(self::$blog)) {self::$blog = factory(AppModelsContentBlog::class, 1)->create();self::$comments = factory(AppModelsContentComment::class, 6)->create();}}}

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

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

编辑

另外,看起来(虽然我不是100%),如果你有自定义的setUp()方法,你需要手动调用runDatabaseMigrations() 从重写的 setUp() 方法:

公共函数 setUp(){父::setUp();$this->runDatabaseMigrations();/** 其余设置 **/}

如果您重载 setUp() 方法,

runDatabaseMigrations() 似乎不会被自动调用.

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

I'm fairly new to unit testing, but I've read pretty much all the documentation on phpunit.de (Up to chapter 10).

It states that testing using databases can be slow, but if setup correctly, it can be just as fast as non-database testing.

As such, I want to test a model in Laravel. I've created a model factory to seed data into the database.

I've also created a basic test.

In PHPUnits documentation, it states that before every test, the setUp() method is called to setup the test. There's also another static method setUpBeforeClass().

I want to seed my database table only once, and use the records within my test. So I used Laravels factory() function to seed the database from within the setUpBeforeClass() method.

This is my code:

class CommentTest extends TestCase
{
    protected static $blog;
    protected static $comments;

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

        self::$blog = factory(AppModelsContentBlog::class)->create();
        self::$comments = factory(AppModelsContentComment::class, 6)->create();
    }

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

However, when I run phpunit, I get the following error:

Fatal error: Call to a member function make() on a non-object in vendorlaravelframeworksrcIlluminateFoundationhelpers.php on line 54

Call Stack:
    0.0002     240752   1. {main}() vendorphpunitphpunitphpunit:0
    0.0173    1168632   2. PHPUnit_TextUI_Command::main() vendorphpunitphpunitphpunit:47
    0.0173    1175304   3. PHPUnit_TextUI_Command->run() vendorphpunitphpunitsrcTextUICommand.php:100
    2.9397    5869416   4. PHPUnit_TextUI_TestRunner->doRun() vendorphpunitphpunitsrcTextUICommand.php:149
    2.9447    6077272   5. PHPUnit_Framework_TestSuite->run() vendorphpunitphpunitsrcTextUITestRunner.php:440
    2.9459    6092880   6. PHPUnit_Framework_TestSuite->run() vendorphpunitphpunitsrcFrameworkTestSuite.php:747
    2.9555    6096160   7. call_user_func:{vendorphpunitphpunitsrcFrameworkTestSuite.php:697}() vendorphpunitphpunitsrcFrameworkTestSuite.php:697
    2.9555    6096272   8. CommentTest::setUpBeforeClass() vendorphpunitphpunitsrcFrameworkTestSuite.php:697
    2.9555    6096480   9. factory() 	estsCommentTest.php:18
    2.9556    6096656  10. app() vendorlaravelframeworksrcIlluminateFoundationhelpers.php:350

If I move the code from setUpBeforeClass() to setUp() and run it, it works as expected, but surely this is inefficient as its seeding the database for every test?

My questions:

  1. Is seeding the database from within the setUpBeforeClass() the correct way to do this?
  2. If it is (question 1), then why am I getting the fatal error when running phpunit, and is there anything I should be doing before calling factory()?
  3. If I do have to place the code in the setUp() method, are there going to be performance issues?
  4. Should I even be seeding from the setUpBeforeClass() or setUp() methods? In Laravels documentation it shows examples where the seeding is happening in the test itself, but If i'm running 100 tests (for example), is it a good idea to be seeding 100 times?

解决方案

Ok, after a bit of investigating (the classes), I've determined that the Laravel application has not yet been created when the static setUpBeforeClass() method is called.

The Laravel container is created the first time setUp() is called in vendorlaravelframeworksrcilluminateFoundationTestingTestCase.php. That's why it works fine when I move my code to the setUp() method.

The container is then stored in the $app property stored in vendorlaravelframeworksrcilluminateFoundationTestingApplicationTrait.php.

I can manually create a container instance by adding this code to the setUpBeforeClass() method:

$app = require __DIR__.'/../bootstrap/app.php';
$app->make(IlluminateContractsConsoleKernel::class)->bootstrap();

But this method seems pretty hacky, and I don't like it.

Instead, I moved the seeding code to the setUp() method, but only seeded the database if the class properties were null. Therefore it only gets seeded on the first call of setUp(). Any subsequent calls do not get seeded:

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(AppModelsContentBlog::class, 1)->create();
            self::$comments = factory(AppModelsContentComment::class, 6)->create();
        }
    }
}

In combination with Laravels DatabaseMigrations trait for testing, This is now the workflow:

  1. PHPUnit is called
  2. The Test class is called, which contains the DatabaseMigrations trait
  3. The database is migrated (tables created)
  4. The setUp() method is called for the first time, which seeds the relevant tables with testing data
  5. The test is run, and and access the test data
  6. There is no tearDown() method invoked, instead the DatabaseMigrations trait simply resets the database, so my test doesn't have to worry about cleaning up the test data.

EDIT

In addition, it seems (although I'm not 100%), that if you have a custom setUp() method, you need to manually call runDatabaseMigrations() from the overridden setUp() method:

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

    /** Rest of Setup **/
}

runDatabaseMigrations() doesn't seem to get called automatically if you overload the setUp() method.

I hope this helps, but if anyone else has a better solution, please feel free to let me know :)

相关文章