PHPUnit试验分离

PHPUnit试验分离,php,unit-testing,symfony,Php,Unit Testing,Symfony,我将Symfony 2.8(最新版本)用于一个web应用程序,其中可以单独使用/重用的应用程序的每个部分都是自己的捆绑包。例如,有一个NewsBundle、GalleryBundle、ContactBundle、AdminBundle(这是一个特例-它只是EasyAdminBundle收集特定bundle提供的特征的包装包)、UserBundle(存储用户实体和模板的FOSUserBundle的子bundle) 我的问题是,单元测试的最佳结构是什么 让我再解释一下:在我的UserBundle中,

我将Symfony 2.8(最新版本)用于一个web应用程序,其中可以单独使用/重用的应用程序的每个部分都是自己的捆绑包。例如,有一个NewsBundle、GalleryBundle、ContactBundle、AdminBundle(这是一个特例-它只是EasyAdminBundle收集特定bundle提供的特征的包装包)、UserBundle(存储用户实体和模板的FOSUserBundle的子bundle)

我的问题是,单元测试的最佳结构是什么

让我再解释一下:在我的UserBundle中,我想对我的FOSUserBundle实现进行测试。我有一个测试登录页面(通过HTTP状态代码)、登录失败(通过错误消息)、登录成功(通过特定代码元素)、记住我(通过Cookie)、注销(通过页面内容)的方法


既然您的问题更具体了,我将提供一个带有一些解释的答案。你在第一次测试中所做的可能会起作用,但不是你应该测试的方式。与其说它是最佳实践,不如说它是您绕过单元测试的想法,对照单个工作单元检查假设。您的测试有几个“单元”的工作要测试,它们都应该在单独的测试中

以下是前两种情况下更合适测试的简明示例:

public function testLoginForm()
{
    $client     = self::createClient();
    $crawler    = $client->request('GET', '/admin/login');

    $this->assertTrue($client->getResponse()->isSuccessful());
    $this->assertEquals(1, $crawler->filter('form[action="/admin/login_check"]')->count());
    $this->assertEquals(1, $crawler->filter('input[name="_username"]')->count());
    $this->assertEquals(1, $crawler->filter('input[name="_password"]')->count());
    $this->assertEquals(1, $crawler->filter('input[type="submit"]')->count());
}

public function testLoginFailure()
{
    $client     = self::createClient();
    $crawler    = $client->request('GET', '/admin/login');
    $form       = $crawler->selectButton('_submit')->form();

    $form['_username'] = 'test';
    $form['_password'] = '123';

    $crawler = $client->submit($form);

    $this->assertEquals(1, $crawler->filter('div[class="alert alert-error"]')->count());
}
这里有几件事

  • 您担心代码重复和额外的代码行,但我刚刚创建了两个单独的测试,根本没有增加行数。我能够删除
    followRedirects()
    调用,因为它不适用于那些测试,我通过简单地重新创建客户端和爬虫程序消除了两行克隆,这就不那么容易混淆了
  • 对于您的代码,只有一个单元测试,但如果该测试失败,可能是由于多种不同的原因-登录失败、登录成功等。因此,如果该测试失败,您必须筛选错误消息并找出系统的哪个部分失败。通过分离测试,当测试失败时,您只需通过测试的名称就可以知道哪里出了问题
  • 您可以通过分离测试来消除一些多余的代码注释:
    //设置错误的用户数据
    不再需要,因为测试本身被称为
    testLoginFailure()
  • 它不仅是单元测试的最佳实践,而且在使用
    WebTestCase
    时还有另一个警告,即您希望所有测试都被隔离。我试图创建一个整个类都可以使用的静态
    $client
    变量,认为如果只实例化一个实例,可以节省内存/时间,但这会在开始运行多个测试时导致不可预测的行为。您希望您的测试单独进行

    如果您确实试图消除冗余代码,还可以使用这些函数,在每个请求之前实例化
    $this->client
    $this->crawler

    use Symfony\Bundle\FrameworkBundle\Client;
    use Symfony\Component\DomCrawler\Crawler;
    
    /*
     * @var Client
     */
    private $client;
    
    /*
     * @var Crawler
     */
    private $crawler;
    
    /*
     * {@inheritDoc}
     */
    protected function setUp()
    {
        $this->client   = self::createClient();
        $this->crawler  = $this->client->request('GET', '/admin/login');
    }
    
    /*
     * {@inheritDoc}
     */
    protected function tearDown()
    {
        unset($this->client);
        unset($this->crawler);
    }
    
    …但随后您将创建类级代码来声明这些变量,实例化它们,并将它们分解。您还添加了很多额外的代码,这是您最初试图避免的。此外,您的整个测试类现在是僵化和不灵活的,因为您永远不能请求登录页面以外的页面。此外,PHPUnit本身声明:

    测试用例对象的垃圾收集是不可预测的

    如果您不记得手动清理您的测试,那么上面的声明是针对您的。因此,除了我上面描述的其他原因之外,您可能还会因为这些原因遇到意外行为

    至于第二个问题,当然可以提供帮助函数或扩展现有的
    *TestCase
    类。Symfony文档甚至为此提供了一个示例。您可以将其放在一个单独的测试类中,就像他们的文档一样,或者您可以创建自己的
    MyBaseTestCase
    类,其中包含该函数


    TL;DR如果重复使用大量相同的设置,请不要尝试巧妙地处理测试/测试用例,分离测试,并创建辅助函数或基本测试用例类进行扩展。

    为什么评级这么差?我该怎么做才能让我的问题变得更好呢?因为这个问题非常广泛,大部分是基于观点的,而且没有明确的答案。您可能需要一个更具体的问题,其中包含一些您尝试过的代码示例。我已经添加了当前的代码,并稍微更新了文本。您是对的,这可能是基于观点的,但我没有找到关于如何管理代码的“最佳实践”的内容。这将有助于我从社区体验中受益,因为这里有许多专业的开发人员。谢谢你的帮助!我担心我可以改进我的问题,以得到这样一个专业且有用的答案:)没问题-当您给出您尝试的代码示例时,审阅者更容易准确地了解您的意思并帮助您。
    use Symfony\Bundle\FrameworkBundle\Client;
    use Symfony\Component\DomCrawler\Crawler;
    
    /*
     * @var Client
     */
    private $client;
    
    /*
     * @var Crawler
     */
    private $crawler;
    
    /*
     * {@inheritDoc}
     */
    protected function setUp()
    {
        $this->client   = self::createClient();
        $this->crawler  = $this->client->request('GET', '/admin/login');
    }
    
    /*
     * {@inheritDoc}
     */
    protected function tearDown()
    {
        unset($this->client);
        unset($this->crawler);
    }