只运行一次设置方法

6

我有:

1. IntegrationTestCase extends TestCase  
2. UnitTestCase extends TestCase
3. AcceptanceTestCase extends TestCase  

在这些测试中,我有很多非静态方法,这些方法在很多测试中都被使用。我的所有测试类都扩展自这三个类之一。
现在,在很多测试类中,我都有一个名为setUp的方法,它准备所需的数据和服务,并将它们分配给类变量:
class SomeTestClass extends IntegrationTestCase
{
    private $foo;

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

        $bar = $this->createBar(...);
        $this->foo = new Foo($bar);
    }

    public function testA() { $this->foo...; }  
    public function testB() { $this->foo...; }
}

问题是每个测试都会运行setUp方法,这与我想要做的相矛盾,如果setUp方法执行的内容需要很长时间,那么这个时间会被测试方法的数量所乘以。使用public function __construct(...) { parent::__construct(..); ... }会带来问题,因为现在Laravel中的较低级别的方法和类不可用。

3
setup() 的整个意义在于每次运行单元测试时都应该运行它;因为每个单元测试都应该完全独立于任何其他单元测试而运行。 - Mark Baker
你看过这个吗:https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.beforeClass?但你还在考虑Mark Baker的评论。 - xmike
@xmike 无法帮助我,因为这是一个静态方法。 - Sterling Duchess
1
但是单元测试不应该针对实际数据库运行;你应该模拟数据库。 - Mark Baker
@MarkBaker 没有真正帮助。 - Sterling Duchess
显示剩余4条评论
4个回答

19

针对遇到类似问题的下一个人:

我曾经遇到这样一个问题,即在运行测试之前我想要迁移数据库,但是我不想每个单独的测试都要迁移一次数据库,因为执行时间太长了。

对我而言,解决方法是使用静态属性来检查数据库是否已经被迁移:

class SolutionTest extends TestCase
{
    protected static $wasSetup = false;

    protected function setUp()
    {
        parent::setUp();

        if ( ! static::$wasSetup) {
            $this->artisan('doctrine:schema:drop', [
                '--force' => true
            ]);

            $this->artisan('doctrine:schema:create');

            static::$wasSetup = true;
        }
    }
}

我在这里添加了一个类似的例子: https://dev59.com/mJ7ha4cB1Zd3GeqPn76E#54933522 - Ryan

1
Saman Hosseini提供的解决方案对我没有用。使用静态属性标记会在下一个测试类中被重置。
为了克服这个问题,我编写了一个单独的测试类来测试测试数据库连接,并初始化测试数据库一次,并确保在所有其他测试之前运行。
<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Artisan;

/**
 * @runTestsInSeparateProcesses
 */
class DatabaseConnectionTest extends TestCase
{
    /**
     * Test the database connection
     *
     * @return void
     */
    public function testDatabaseConnection()
    {
        $pdo = DB::connection()->getPdo();
        $this->assertNotNull($pdo);
    }

    /**
     * Initialize the test database for once
     *
     * @return void
     */
    public function testInititializeTestDatabase()
    {
        Artisan::call('migrate:fresh');
        Artisan::call('db:seed');
    }
}


0

我建议使用模板方法setUpBeforeClasstearDownAfterClass

setUpBeforeClass()方法在执行第一个测试之前调用,tearDownAfterClass()方法在执行最后一个测试之后调用。

我们使用这两个方法来与所有测试共享设置。

例如,在setUpBeforeClass()方法中一次获取数据库连接,然后在setUp()方法中多次获取连接是明智的。

同样地,在tearDownAfterClass()方法中仅关闭一次数据库连接,然后在tearDown()方法中在每个测试之后关闭数据库连接也是明智的。


-1

我不确定你对于 setUpBeforeClass 的问题是指除了 Mark Baker 提到的那一个之外还有哪些。虽然如此,我认为你应该知道自己在做什么。这里提供一个可能的使用示例。

class BeforeAllTest extends PHPUnit_Framework_TestCase
{
    private static $staticService;
    private $service; // just to use $this is tests

    public static function setUpBeforeClass() {
        self::createService();
    }

    public static function createService(){
        self::$staticService = 'some service';
    }

    /**
     * just to use $this is tests
     */
    public function setUp(){
        $this->service = self::$staticService;
    }

    public function testService(){
        $this->assertSame('some service', $this->service);
    }
}

更新:你可以在https://phpunit.de/manual/current/en/database.html(搜索“提示:使用自己的抽象数据库测试用例”)中看到类似的方法。我相信你已经在进行密集的数据库测试时使用了它。但是这种方式不仅限于处理数据库问题。

更新2:好吧,我想你应该使用self::createService而不是$this->createService(我已经更新了上面的代码)。


好的,但是我需要调用 self::$staticService = $this->createService(..) 并没有真正帮助我。 - Sterling Duchess

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