是否有PHPUnit的分层测试运行器? - php

我已经看到了层次化上下文运行器是如何在JUnit中工作的,这非常棒。

它允许您在单个测试类中的方法组之前安排多个设置。当您测试多个方案时,这非常好;感觉更像是在做BDD。
Hierarchical Runner Explanation

在PHPUnit中拥有这样的东西会很好,但我只是无法实现。

我尝试在自定义方法上使用@before批注,以期规定顺序。另外,我尝试声明内部类,但是后来我发现PHP 5中不允许这样做。我还尝试了许多其他事情,但均未成功。

可以使用PHPUnit实现吗?

参考方案

您无法完全执行JUnit Heirarchical Context Runner的操作,因为您已经发现,您无法在PHP中嵌套类。分层上下文运行器依赖于嵌套类。但是,您可以非常接近同一件事。最终,考虑到如何命名测试方法,您可以生成更易于导航和理解的更简洁的代码,与使用嵌套类相比,可以降低意外引入全局状态或隐藏依赖项的风险。

重要警告

在我们潜水之前,请注意you generally do not want to share fixtures or other state between tests。单元测试的全部重点是测试单个代码单元,当您还通过在测试之间拥有持久性(或更糟糕的是实际可变的)数据来创建这些单元之间的链接时,很难做到这一点。作为explained in the PHPUnit docs,

在测试之间共享固定装置的理由很少,但是在大多数情况下,在测试之间共享固定装置的需求源于未解决的设计问题。 [重点添加]

一个可以在多个测试之间共享的固定装置的好例子是数据库连接:您一次登录数据库并重用数据库连接,而不是为每个测试创建新的连接。这使您的测试运行更快。

使用引导文件

如果您的代码应该在所有测试类中的所有测试类之前运行一次,则use a bootstrap file。例如,如果您的代码依赖于自动加载器或常量(如包含特定文件的路径),或者您只需要运行一系列includerequire语句来加载某些功能,则使用引导文件。

您可以使用--bootstrap命令行选项执行此操作,如下所示:

phpunit --bootstrap src/autoload.php tests

您还可以在XML配置文件中指定引导文件,如下所示:

<phpunit bootstrap="src/autoload.php">
  <!-- other configuration stuff here -->
</phpunit>

使用设置方法

您也可以specify a setup method to run before running any other tests in a particular class。在这里,您可以将所有应运行的代码放在类中的任何测试之前。但是,它将仅运行一次,因此您不能使用它在测试之间运行。

例如,您可以执行以下操作在运行任何测试之前为一个或多个方案填充数据:

<?php
class NameValidatorTest extends PHPUnit_Framework_TestCase
{
    protected static $nameForEnglishScenario;
    protected static $nameForFrenchScenario;

    /**
     * Runs before any tests and sets up data for the test
     */
    public static function setUpBeforeClass()
    {
        self::$nameForEnglishScenario = 'John Smith';
        self::$nameForFrenchScenario = 'Séverin Lemaître';
    }

    public function testEnglishName()
    {
        $this->assertRegExp('/^[a-z -]+$/i', self::$nameForEnglishScenario);
    }

    public function testFrenchName()
    {
        $this->assertRegExp('/^[a-zàâçéèêëîïôûùüÿñæœ -]+$/i', self::$nameForFrenchScenario);
    }
}

(不要注意此示例中的实际逻辑;这里的测试是la脚的,实际上不是测试类。重点是设置。)

考虑在测试类别中针对每个目标方法使用多种测试方法

测试多个方案的典型方法是创建多个方法,这些方法的名称可以反映其条件。例如,如果要测试名称验证器类,则可以执行以下操作:

<?php
class NameValidatorTest extends PHPUnit_Framework_TestCase
{
    public function testIsValid_Invalid_English_Actually_French()
    {
        $validator = new NameValidator();
        $validator->setName('Séverin Lemaître');
        $validator->setLocale('en');
        $this->assertFalse($validator->isValid());
    }

    public function testIsValid_Invalid_French_Gibberish()
    {
        $validator = new NameValidator();
        $validator->setName('Séverin J*E08RW)8WER Lemaître');
        $validator->setLocale('fr');
        $this->assertFalse($validator->isValid());
    }

    public function testIsValid_Valid_English()
    {
        $validator = new NameValidator();
        $validator->setName('John Smith');
        $validator->setLocale('en');
        $this->assertTrue($validator->isValid());
    }

    public function testIsValid_Valid_French()
    {
        $validator = new NameValidator();
        $validator->setName('Séverin Lemaître');
        $validator->setLocale('fr');
        $this->assertTrue($validator->isValid());
    }
}

这样做的优点是可以将一个类的所有测试合并到一个地方,并且,如果您以智能的方式命名它们,则即使使用许多测试方法,也可以轻松导航。

考虑使用数据提供者方法

您也可以使用data provider method。从the manual:

测试方法可以接受任意参数。这些参数将由数据提供者方法(Example 2.5中的provider())提供。使用@dataProvider注释指定要使用的数据提供者方法。

有关更多详细信息,请参见the section called “Data Providers”。

您可以使用数据提供者多次运行相同的测试代码,每次运行使用不同的数据来测试不同的方案。

考虑使用依赖项

您也可以通过specifying dependencies between them强制测试类中​​的测试以特定顺序运行。您可以在文档块中使用@depends进行此操作。文档中的示例:

<?php
class MultipleDependenciesTest extends PHPUnit_Framework_TestCase
{
    public function testProducerFirst()
    {
        $this->assertTrue(true);
        return 'first';
    }

    public function testProducerSecond()
    {
        $this->assertTrue(true);
        return 'second';
    }

    /**
     * @depends testProducerFirst
     * @depends testProducerSecond
     */
    public function testConsumer()
    {
        $this->assertEquals(
            array('first', 'second'),
            func_get_args()
        );
    }
}

在此示例中,保证testProducerFirsttestProducerSecond都可以在testConsumer之前运行。但是请注意,testConsumer将接收testProducerFirsttestProducerSecond的结果作为参数,并且如果其中一项测试失败,它将根本不运行。

考虑对每个目标类别使用多个测试类别

如果要在非常不同的方案上运行大量测试,则可以考虑为给定的目标类创建多个测试类。但是,通常这不是您最好的选择。这意味着创建和维护更多的类(因此,如果要在文件中仅放置一个类,则可以创建更多的文件),这使得一次查看和理解所有测试代码变得更加困难。这仅真正适用于其中您的目标类以一种测试与另一种测试完全不同的方式使用的实例。

但是,如果您正在编写SOLID代码并正确使用设计模式,则您的代码不应能够在如此有意义的不同条件下运行。因此,这在某种程度上是一个观点问题,但这可能永远不是编写测试的正确方法。

考虑使用测试套件按特定顺序运行测试

您还可以告诉PHPUnit运行"test suite.",这允许您以逻辑方式对测试进行分组(例如,所有数据库测试或具有i18n逻辑的类的所有测试)。当使用XML配置文件编写测试套件时,可以明确告诉PHPUnit以特定顺序运行测试。如in the docs所述,

如果当前工作目录中存在phpunit.xmlphpunit.xml.dist(按此顺序)并且不使用--configuration,则将从该文件中自动读取配置。

可以明确执行测试的顺序:

例5.2:使用XML配置组成测试套件

   <phpunit bootstrap="src/autoload.php">
     <testsuites>
       <testsuite name="money">
         <file>tests/IntlFormatterTest.php</file>
         <file>tests/MoneyTest.php</file>
         <file>tests/CurrencyTest.php</file>
       </testsuite>
     </testsuites>
   </phpunit>

不利的一面是,您再次引入了一种全球状态。如果在实际应用程序中使用Money类,然后再运行IntlFormatter类的某些关键功能,该怎么办?

放在一起

最好的选择是使用setUpBeforeClass()方法在每个测试类别的基础上进行设置。然后,对每个目标方法使用多种测试方法来测试您的各种方案。

我已经在上面列出了许多其他方法来强制测试以特定顺序运行。但是它们都引入了某种形式的全球状态,混乱或两者兼而有之。每当您进行一项仅在另一项测试完成后才运行时,您可能会冒险引入依赖关系而没有意识到它。如果您的测试相互依赖,那么您并不是真正的单元测试。在某种程度上,您正在集成测试。

通常,最好进行真正的单元测试。测试目标类的每个公共方法,就好像没有其他方法一样。当您能够做到这一点并使您的测试通过所有可能的方案时,您便拥有了可靠的代码。

PHP PDO组按列名称查询结果 - php

以下PDO查询返回以下结果:$db = new PDO('....'); $sth = $db->prepare('SELECT ...'); 结果如下: name curso ABC stack CDE stack FGH stack IJK stack LMN overflow OPQ overflow RS…

php Singleton类实例将在多个会话中保留吗? - php

举一个简单的例子,如果我想计算一个不使用磁盘存储的脚本的命中次数,我可以使用静态类成员来执行此操作吗?用户1:<?php $test = Example::singleton(); $test->visits++; ?> 用户2:<?php $test = Example::singleton(); $test->visits+…

PHP:对数组排序 - php

请如何排序以下数组Array ( 'ben' => 1.0, 'ken' => 2.0, 'sam' => 1.5 ) 至Array ( 'ken' => 2.0, 'sam' => 1.5, 'ben' =&…

哪个更好的做法?从Jquery响应获取HTML - php

这只是一个问题,以了解人们如何以及如何做到这一点,但是假设用户向列表中添加了一些内容,完成后,它将运行下面的ajax并更新.user-stream-list$.ajax({ url: "user-stream-list.php", success: function(data){ $(".user-stream-list…

PHP strtotime困境 - php

有人可以解释为什么这在我的服务器上输出为true吗?date_default_timezone_set('Europe/Bucharest'); var_dump( strtotime('29.03.2015 03:00', time()) === strtotime('29.03.2015 04:00�…