instaclick/base-test-bundle

此捆绑包为Symfony2的功能测试提供低级别支持。

安装次数: 103,831

依赖关系: 4

建议者: 0

安全: 0

星标: 96

关注者: 25

分支: 21

开放问题: 6

类型:symfony-bundle

0.5.2 2014-01-08 19:14 UTC

This package is not auto-updated.

Last update: 2024-09-14 13:53:58 UTC


README

重要声明: 此捆绑包仍在开发中。任何更改都将不事先通知此包的消费者。当然,这段代码最终会变得稳定,但到目前为止,请自行承担风险。

介绍

此捆绑包为Symfony2上的单元测试和功能测试提供低级别支持。通过辅助器和加载器的概念,此捆绑包支持对测试类型(如命令、控制器、服务、验证器等)的单独支持。

此捆绑包要求您至少使用Symfony 2.1。

安装

可以通过以下简单步骤安装此捆绑包

  1. 将此捆绑包添加到您的项目作为composer依赖项
    // composer.json
    {
        // ...
        require-dev: {
            // ...
            "instaclick/base-test-bundle": "dev-master"
        }
    }
  1. 在您的应用程序内核中添加此捆绑包
    // application/ApplicationKernel.php
    public function registerBundles()
    {
        // ...
        if (in_array($this->getEnvironment(), array('test'))) {
            $bundles[] = new IC\Bundle\Base\TestBundle\ICBaseTestBundle();
        }

        return $bundles;
    }
  1. 确保您的会话名称配置正确
# application/config/config_test.yml
    framework:
        test: ~
        session:
            name: "myapp"

单元测试

默认情况下,Symfony2不提供创建单元测试的原生自定义支持。为了解决这个问题,此捆绑包包含了一组基本的单元测试抽象,以帮助您完成这项工作。

受保护的/私有的

有时您可能想直接测试受保护的/private方法或访问非公共属性(且该类缺少getter或setter)。例如,从最近的公共方法到调用链足够长,以至于测试变得很困难。

为了克服这个障碍,TestCase提供了一些辅助方法。

假设这是您的测试主题

class Foo
{
    protected $bar;

    private function getBar()
    {
        return $this->bar;
    }
}

以下是一个示例

use IC\Bundle\Base\TestBundle\Test\TestCase;

class ICFooBarBundleTest extends TestCase
{
    public function testGetBar()
    {
        $subject = new Foo;
        $expected = 'Hello';

        $this->setPropertyOnObject($subject, 'bar', $expected);

        $method = $this->makeCallable($subject, 'getBar');

        $this->assertEquals($expected, $method->invoke($subject));
    }
}

捆绑包测试

大多数人甚至没有想过测试捆绑包初始化。这是一个坏主意,因为每一行代码都值得测试,即使您可能没有手动创建一个类。

捆绑包类是注册您的CompilerPass实例的地方。无论您是否有CompilerPass,为您的Bundle类创建一个默认测试都是一个好习惯。以下是一个如何实现它的示例

use IC\Bundle\Base\TestBundle\Test\BundleTestCase;
use IC\Bundle\Base\MailBundle\ICBaseMailBundle;

class ICBaseMailBundleTest extends BundleTestCase
{
    public function testBuild()
    {
        $bundle = new ICBaseMailBundle();

        $bundle->build($this->container);

        // Add your tests here
    }
}

依赖注入

配置测试

就像捆绑包类一样,配置类在测试时很容易被忽略。测试这个特定的测试是一个好方法,因为它验证了您关于捆绑包配置参数规范化或甚至配置默认值的思路。ICBaseTestBundle已经提供了一个小的类,可以帮助您完成这个任务。

use IC\Bundle\Base\TestBundle\Test\DependencyInjection\ConfigurationTestCase;
use IC\Bundle\Base\MailBundle\DependencyInjection\Configuration;

class ConfigurationTest extends ConfigurationTestCase
{
    public function testDefaults()
    {
        $configuration = $this->processConfiguration(new Configuration(), array());

        $this->assertEquals('INBOX', $configuration['mail_bounce']['mailbox']);
    }

    // ...
}

扩展测试

测试依赖注入扩展可以帮助您验证服务定义和容器配置。可供您使用的有用方法

  • assertAlias($expected, $key)
  • assertParameter($expected, $key)
  • assertHasDefinition($id)
  • assertDICConstructorArguments(Definition $definition, array $arguments)
  • assertDICDefinitionClass(Definition $definition, $expectedClass)
  • assertDICDefinitionMethodCallAt($position, Definition $definition, $methodName, array $params = null)
use IC\Bundle\Base\TestBundle\Test\DependencyInjection\ExtensionTestCase;
use IC\Bundle\Base\MailBundle\DependencyInjection\ICBaseMailExtension;

class ICBaseMailExtensionTest extends ExtensionTestCase
{
    /**
     * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
     */
    public function testInvalidConfiguration()
    {
        $extension     = new ICBaseMailExtension();
        $configuration = array();

        $this->load($extension, $configuration);
    }

    public function testValidConfiguration()
    {
        $this->createFullConfiguration();

        $this->assertParameter('John Smith', 'ic_base_mail.composer.default_sender.name');

        $this->assertDICConstructorArguments(
            $this->container->getDefinition('ic_base_mail.service.composer'),
            array()
        );
        $this->assertDICConstructorArguments(
            $this->container->getDefinition('ic_base_mail.service.sender'),
            array()
        );
        $this->assertDICConstructorArguments(
            $this->container->getDefinition('ic_base_mail.service.bounce_mail'),
            array()
        );
    }

    // ...
}

验证器测试

验证器是系统的关键部分,因为它帮助您验证业务规则是否得到遵守。测试它们变得更加关键。约束可以在不同的位置生成违规。为了帮助您验证它是否分配在正确的位置,ValidatorTestCase为您提供了一组方法

  • assertValid(ConstraintValidator $validator, Constraint $constraint, $value)
  • assertInvalid(ConstraintValidator $validator, Constraint $constraint, $value, $message, array $parameters = array())
  • assertInvalidAtPath(ConstraintValidator $validator, Constraint $constraint, $value, $type, $message, array $parameters = array())
  • assertInvalidAtSubPath(ConstraintValidator $validator, Constraint $constraint, $value, $type, $message, array $parameters = array())
use MyBundle\Validator\Constraints;
use IC\Bundle\Base\TestBundle\Test\Validator\ValidatorTestCase;

class BannedEmailValidatorTest extends ValidatorTestCase
{
    public function testValid()
    {
        $validator  = new Constraints\BannedEmailValidator();
        $constraint = new Constraints\BannedEmail();
        $value      = 'email@domain.com';

        $this->assertValid($validator, $constraint, $value);
    }

    public function testInvalid()
    {
        $validator  = new Constraints\BannedEmailValidator();
        $constraint = new Constraints\BannedEmail();
        $value      = 'domain.com';
        $message    = 'Please provide a valid email.';
        $parameters = array();

        $this->assertInvalid($validator, $constraint, $value, $message, $parameters);
    }
}

创建您的第一个功能测试

就像一个Symfony2测试,实现功能测试很简单

use IC\Bundle\Base\TestBundle\Test\Functional\WebTestCase;

class MyFunctionalTest extends WebTestCase
{
    public function testSomething()
    {
        // Normal test here. You can benefit from an already initialized
        // Symfony2 Client by using directly $this->getClient()
    }
}

需要初始化数据库的功能测试

当构建功能测试时,通常需要创建测试数据库并在其中填充初始信息。这个包提供了对Doctrine数据固定的原生支持,允许在测试实际执行之前加载数据库信息。

为了使您的模式初始化并加载初始数据库信息,只需实现受保护的静态方法getFixtureList

/**
 * {@inheritdoc}
 */
protected static function getFixtureList()
{
    return array(
        'Me\MyBundle\DataFixtures\ORM\LoadData'
    );
}

如果您在测试前不需要加载任何固定数据,但仍想加载空的数据库模式,您可以更改配置属性标志,告诉TestCase强制加载模式

protected $forceSchemaLoad = true;

覆盖默认客户端实例

某些应用程序需要比Symfony2客户端能做的更细粒度的控制。这个包允许您通过覆盖静态方法createClient来更改默认客户端初始化,就像您通常使用Symfony2 WebTestCase一样。

更改客户端环境

这个包允许您轻松地更改客户端初始化的环境。要更改默认环境(默认:"test"),只需重新定义常量ENVIRONMENT

const ENVIRONMENT = "default";

更改默认对象管理器名称

当使用数据库时,您可能想更改测试应运行的默认ObjectManager。就像客户端的环境一样,更改默认对象管理器只需要您重新定义常量MANAGER_NAME

const MANAGER_NAME = "stats";

服务器身份验证

每当您的应用程序使用HTTP身份验证时,您的测试应该仍然能够测试受保护的页面。本着简单化的原则,客户端可以以认证状态初始化HTTP。唯一需要执行的操作是实现受保护的静态方法getServerParameters

/**
 * {@inheritdoc}
 */
protected static function getServerParameters()
{
    return array(
        'PHP_AUTH_USER' => 'admin',
        'PHP_AUTH_PW'   => 'jed1*passw0rd'
    );
}

注意:这假设您已在config_test.yml文件中启用http_basic,使用此设置

# app/config/config_test.yml
security:
    firewalls:
        your_firewall_name:
            http_basic:

有关详细信息,请参阅如何在功能测试中模拟HTTP身份验证

更改客户端初始化

通常,覆盖createClient就足够了。当您需要更精细的支持时,您仍然可以通过覆盖受保护的静态方法initializeClient来覆盖默认客户端初始化(示例实际上是方法的默认实现)

/**
 * {@inheritdoc}
 */
protected static function initializeClient()
{
    return static::createClient(
        array('environment' => static::ENVIRONMENT),
        static::getServerParameters()
    );
}

有用的提示

创建一个MockBuilder类

而不是使用PHPUnit的原生API,WebTestCase提供了一种直接可用的方法

public function testFoo()
{
    $myMock = $this->getClassMock('My\Foo');

    $myMock->expects($this->any())
           ->method('bar')
           ->will($this->returnValue(true));

    // ...
}

获取服务容器

Symfony客户端持有服务容器的一个实例。您可以直接从客户端获取容器实例

public function testFooService()
{
    $container = $this->getClient()->getContainer();

    // ...
}

获取数据库引用

数据库相关应用程序通常要求您在测试/消耗元素之前先获取它们。WebTestCase利用Doctrine数据固定包,允许您获取引用而无需进行数据库提取。

public function testIndex()
{
    $credential = $this->getReferenceRepository()->getReference('core.security.credential#admin');

    // ...
}

数据库相关的功能测试

在大多数情况下,您的应用程序依赖于数据库来工作。为了帮助您完成这项任务,并加快测试套件的执行速度,我们强烈建议您使用SQLite作为测试数据库。这样做的原因是因为这个数据库在一个文件中工作,允许您轻松创建隔离的场景。此外,这个包具有缓存生成的模式并使其适用于每个测试重用的能力。另一个功能:这个包与Doctrine数据固定集成,允许您的SQLite测试数据库在实际测试执行之前就缓存了常见的信息。

最后,但同样重要的是,如果您使用SQLite,您的测试将更快地运行,因为所有内置的原生支持,只需使用这个包即可。

要使用SQLite作为测试数据库,请将以下内容添加到您的app/config_test.yml

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   pdo_sqlite
                path:     %kernel.cache_dir%/test.db

注意:您需要使用Doctrine >= 2.2以使用此功能。

使用辅助工具

此包附带内置辅助工具,用于简化测试软件的各个部分。本节将解释原生辅助工具以及如何注入自己的辅助工具。

检索辅助工具

通过利用WebTestCase中可用的Symfony2客户端实例来访问辅助工具。所有辅助工具都注册在后者中,它允许您通过调用方法轻松检索实例

public function testFoo()
{
    $commandHelper = $this->getHelper('command');

    // ...
}

可用的辅助工具

ICBaseTestBundle附带一系列内置辅助工具。以下是可用辅助工具的列表

  • 命令
  • 控制器
  • 持久性
  • 路由
  • 服务
  • 会话
  • 验证器
  • 单元/实体
  • 单元/函数

命令辅助工具

此辅助工具在command名称下提供。它作为Symfony Console Component的包装器。这也允许您使用其提供的API配置命令、构建命令输入并检索响应内容。以下是一个命令测试的完整实现示例

/**
 * @dataProvider provideDataForCommand
 */
public function testCommand(array $arguments, $content)
{
    $commandHelper = $this->getHelper('command');

    $commandHelper->setCommandName('ic:base:mail:flush-queue');
    $commandHelper->setMaxMemory(5242880); // Optional (default value: 5 * 1024 * 1024 KB)

    $input    = $commandHelper->getInput($arguments);
    $response = $commandHelper->run($input);

    $this->assertContains($content, $response);
}

/**
 * {@inheritdoc}
 */
public function provideDataForCommand()
{
    return array(
        array(array('--server' => 'mail.instaclick.com'), 'Flushed queue successfully'),
    );
}

控制器辅助工具

此辅助工具在controller名称下提供。此辅助工具的动机是使子请求能够在不调用主请求的情况下执行。它允许您模拟请求并检查返回内容。

重要提示:控制器辅助工具仍在开发中。它是将Symfony\Component\DomCrawler\Crawler作为单独方法连接到计划的一部分。

public function testViewAction()
{
    $controllerHelper = $this->getHelper('controller');
    $response         = $controllerHelper->render(
        'ICBaseGeographicalBundle:Map:view',
        array(
            'attributes' => array(
                'coordinate' => new Coordinate(-34.45, 45.56),
                'width'      => 640,
                'height'     => 480
            )
        )
    );

    $this->assertContains('-34.45', $response);
    $this->assertContains('45.56', $response);
    $this->assertContains('640', $response);
    $this->assertContains('480', $response);
}

持久性辅助工具

此辅助工具在persistence名称下提供。持久性辅助工具将引用键转换为引用对象,或将引用键列表转换为引用对象列表。

public function testFoo()
{
    $persistenceHelper = $this->getHelper('persistence');

    $credentialList = $persistenceHelper->transformToReference(
        array(
            'core.security.credential#admin',
            'core.security.credential#user',
        )
    );

    ...
}

路由辅助工具

此辅助工具在route名称下提供。路由辅助工具提供了一种从路由ID检索生成路由的方法。此外,如果路由未注册,则测试将标记为跳过。

/**
 * @dataProvider provideRouteData
 */
public function testRoute($routeId, $parameters)
{
    $routeHelper = $this->getHelper('route');

    $route = $routeHelper->getRoute($routeId, $parameters, $absolute = false);

    ...
}

服务辅助工具

此辅助工具在service名称下提供。每次您想要模拟服务并将其自动注入服务容器时,此辅助工具都适用于您。辅助工具包含一个执行此操作的方法:mock。它返回一个MockBuilder实例。

public function testFoo()
{
    $serviceHelper = $this->getHelper('service');

    $authenticationService = $serviceHelper->mock('core.security.authentication');

    // ...
}

会话辅助工具

此辅助工具在session名称下提供。会话辅助工具的编写基于一个简单的想法:允许您模拟控制器测试中的登录。当然,会话辅助工具还允许您检索实际的Symfony会话以正常定义/检查/删除条目。

pubic function testIndex()
{
    $sessionHelper = $this->getHelper('session');

    $credential = $this->getReferenceRepository()->getReference('core.security.credential#admin');

    // $sessionHelper->getSession() is also available
    $sessionHelper->authenticate($credential, 'secured_area');

    // ...
}

验证器辅助工具

此辅助工具在validator名称下提供。验证器辅助工具封装了比其他原生辅助工具更多的逻辑。它允许您测试成功和错误状态,因为它需要测试所需的内部元素模拟。

public function testSuccessValidate($value)
{
    $validatorHelper = $this->getHelper('validator');
    $serviceHelper   = $this->getHelper('service');

    $validatorHelper->setValidatorClass('IC\Bundle\Base\GeographicalBundle\Validator\Constraints\ValidLocationValidator');
    $validatorHelper->setConstraintClass('IC\Bundle\Base\GeographicalBundle\Validator\Constraints\ValidLocation');

    // Required mocking
    $geolocationService = $serviceHelper->mock('base.geographical.service.geolocation');
    $geolocationService->expects($this->any())
        ->method('convertLocationToCoordinate')
        ->will($this->returnValue($this->mockCoordinate()));

    $validatorHelper->getValidator()->setGeolocationService($geolocationService);

    // Testing
    $validatorHelper->success($value);
}

单元/实体辅助工具

此辅助工具在Unit/Entity名称下提供。单元/实体辅助工具有助于在没有setId()方法的情况下创建实体存根。

public function testFoo()
{
    $entityHelper = $this->getHelper('Unit/Entity');

    $entity = $entityHelper->createMock('IC\Bundle\Base\GeographicalBundle\Entity\Country', 'us');

    ...
}

单元/函数辅助工具

此辅助工具在Unit/Function名称下提供。单元/函数辅助工具有助于模拟内置PHP函数。注意:受测试的主题必须是命名空间类。

public function testFoo()
{
    $functionHelper = $this->getHelper('Unit/Function');

    // mock ftp_open() to return null (default)
    $functionHelper->mock('ftp_open');

    // mock ftp_open() to return true
    $functionHelper->mock('ftp_open', true);

    // mock ftp_open() with callable
    $functionHelper->mock('ftp_open', function () { return null; });

    // mock ftp_open() with a mock object; note: the method is always 'invoke'
    $fopenProxy = $functionHelper->createMock();
    $fopenProxy->expects($this->once())
               ->method('invoke')
               ->will($this->returnValue(true));

    $functionHelper->mock('ftp_open', $fopenProxy);

    ...
}

创建和注册自己的辅助工具

注册新辅助工具是重写受保护的静态方法initializeHelperList的必要条件

protected static function initializeHelperList()
{
    $helperList = parent::initializeHelperList();

    $helperList->set('my', 'IC\Bundle\Site\DemoBundle\Test\Helper\MyHelper');
    // Retrieve as: $myHelper = $this->getHelper('my');

    return $helperList;
}