2008-08-13 79 views
41

有没有办法让TestCase内部的测试以某种顺序运行?例如,我想将对象的生命周期从创建到使用破坏分开,但在运行其他测试之前,我需要确保先设置对象。以某种顺序运行PHPUnit测试

+1

如下答案描述,并使用设置()和拆卸()也是一个好主意,你可以添加@depends,但是测试只是从上到下运行... – Andrew 2015-06-10 15:30:28

+0

另外一个似乎没有被覆盖的用例:也许所有的测试都是原子性的,但有些测试是慢的。我希望尽快运行快速测试,以便它们可以快速失败,并且在我已经看到其他问题并且可以立即得到它们之后,任何慢速测试最后都会死掉。 – Kzqai 2016-03-19 21:31:41

回答

44

也许你的测试有一个设计问题。

通常每个测试不能依赖于任何其他测试,因此它们可以按任意顺序运行。每个测试都需要实例化并销毁它需要运行的所有东西,这将是一个完美的方法,你不应该在测试之间共享对象和状态。

你能更具体地说明为什么你需要同一个对象进行N次测试吗?

+33

这对我来说似乎不正确。单元测试的重点是测试整个单元。拥有一个单位的重点是把事物组合在一起,这些事物必须相互依赖。编写测试能够在没有上下文的情况下测试单个方法的测试类似于提倡对oo进行过程式编程,因为您主张单个函数不应该依赖于相同的数据。 – doliver 2013-05-04 20:35:42

+3

我不同意你的观点。实例化测试的输出是您的测试套件中的其他测试可以使用的有效对象。没有必要为每个测试实例化一个新的对象,特别是如果构造器很复杂的话。 – pedromanoel 2013-07-12 15:08:16

+6

如果构造函数很复杂,那么你做错了事,可能你的班级做得太多了。请阅读关于“单一责任模式(SRP)”的更多具体内容“SOLID”,也应该使用mock“测试”测试中的依赖项,请阅读“嘲笑,假货和存根”。 – 2013-07-13 22:15:36

1

如果需要以特定顺序运行,那么测试确实存在问题。每个测试应该完全独立于其他测试:它可以帮助您进行缺陷定位,并允许您获得可重复(因此可调试)的结果。

结账this site了解如何以避免这些问题的方式来分析测试的整个过程。

8

如果您希望测试共享各种帮助对象和设置,则可以使用setUp()tearDown()添加到sharedFixture属性。

118

PHPUnit通过@depends注释支持测试依赖关系。

这里是从那里测试将在满足依赖性的次序来运行该文件的实例,与每个相关的检验合格参数传递给下一个:

class StackTest extends PHPUnit_Framework_TestCase 
{ 
    public function testEmpty() 
    { 
     $stack = array(); 
     $this->assertEmpty($stack); 

     return $stack; 
    } 

    /** 
    * @depends testEmpty 
    */ 
    public function testPush(array $stack) 
    { 
     array_push($stack, 'foo'); 
     $this->assertEquals('foo', $stack[count($stack)-1]); 
     $this->assertNotEmpty($stack); 

     return $stack; 
    } 

    /** 
    * @depends testPush 
    */ 
    public function testPop(array $stack) 
    { 
     $this->assertEquals('foo', array_pop($stack)); 
     $this->assertEmpty($stack); 
    } 
} 

然而,重要的是要注意的是测试用未解决的依赖关系将会被执行(可取的,因为这会迅速引起注意失败的测试)而不是。所以,在使用依赖关系时要特别注意。

7

PHPUnit允许使用'@depends'注释来指定相关测试用例,并允许在相关测试用例之间传递参数。

2

在我看来,采取以下方案,我需要测试创建和销毁特定资源。

最初我有两种方法,a。 testCreateResource和b。 testDestroyResource

a。 testCreateResource

<?php 
$app->createResource('resource'); 
$this->assertTrue($app->hasResource('resource')); 
?> 

b。 testDestroyResource

<?php 
$app->destroyResource('resource'); 
$this->assertFalse($app->hasResource('resource')); 
?> 

我认为这是一个坏主意,因为testDestroyResource取决于testCreateResource。更好的做法是做

a。 testCreateResource

<?php 
$app->createResource('resource'); 
$this->assertTrue($app->hasResource('resource')); 
$app->deleteResource('resource'); 
?> 

b。testDestroyResource

<?php 
$app->createResource('resource'); 
$app->destroyResource('resource'); 
$this->assertFalse($app->hasResource('resource')); 
?> 
1

替代解决方案:(!) 使用静态函数在您的测试来创建可重用元素。举例来说(我用硒IDE来记录测试和PHPUnit的硒(github上)内运行的浏览器测试)

class LoginTest extends SeleniumClearTestCase 
{ 
    public function testAdminLogin() 
    { 
     self::adminLogin($this); 
    } 

    public function testLogout() 
    { 
     self::adminLogin($this); 
     self::logout($this); 
    } 

    public static function adminLogin($t) 
    { 
     self::login($t, '[email protected]', 'pAs$w0rd'); 
     $t->assertEquals('John Smith', $t->getText('css=span.hidden-xs')); 
    } 

    // @source LoginTest.se 
    public static function login($t, $login, $pass) 
    { 
     $t->open('/'); 
     $t->click("xpath=(//a[contains(text(),'Log In')])[2]"); 
     $t->waitForPageToLoad('30000'); 
     $t->type('name=email', $login); 
     $t->type('name=password', $pass); 
     $t->click("//button[@type='submit']"); 
     $t->waitForPageToLoad('30000'); 
    } 

    // @source LogoutTest.se 
    public static function logout($t) 
    { 
     $t->click('css=span.hidden-xs'); 
     $t->click('link=Logout'); 
     $t->waitForPageToLoad('30000'); 
     $t->assertEquals('PANEL', $t->getText("xpath=(//a[contains(text(),'Panel')])[2]")); 
    } 
} 

好了,现在,我可以用在其他测试这种可重复使用的元素:)例如:

class ChangeBlogTitleTest extends SeleniumClearTestCase 
{ 
    public function testAddBlogTitle() 
    { 
     self::addBlogTitle($this,'I like my boobies'); 
     self::cleanAddBlogTitle(); 
    } 

    public static function addBlogTitle($t,$title) { 
     LoginTest::adminLogin($t); 

     $t->click('link=ChangeTitle'); 
     ... 
     $t->type('name=blog-title', $title); 
     LoginTest::logout($t); 
     LoginTest::login($t, '[email protected]','hilton'); 
     $t->screenshot(); // take some photos :) 
     $t->assertEquals($title, $t->getText('...')); 
    } 

    public static function cleanAddBlogTitle() { 
     $lastTitle = BlogTitlesHistory::orderBy('id')->first(); 
     $lastTitle->delete(); 
    } 
  • 通过这种方式,你可以建立你的测试层次。
  • 您可以保留每个测试用例与其他测试用例完全独立的属性(如果您在每次测试后清理数据库)。
  • 而最重要的,如果举例来说,在未来的登陆变化的方式,你只能修改LoginTest类,你don'n需要在其他测试正确的登录部分(他们应该更新LoginTest后工作):)

当我运行测试我的脚本清理数据库广告开始。以上我使用我的SeleniumClearTestCase类(我做截图()和其他很好的功能),它是MigrationToSelenium2(从github,扩展到在Firefox中使用seleniumIDE + ff插件“Selenium IDE:PHP格式化程序”我的类LaravelTestCase(它是Illuminate \ Foundation \ Testing \ TestCase的副本,但不扩展PHPUnit_Framework_TestCase),它是PHPUnit_Extensions_Selenium2TestCase的扩展,当我们希望在测试结束时清理数据库时,设置laravel以访问雄辩。为了设置laravel雄辩,我还在SeleniumClearTestCase函数createApplication(它被称为setUp,我从laral test/TestCase中取得这个函数)

2

正确的答案是这是测试的正确配置文件。我有同样的问题,并通过创建必要的测试文件测试包中固定它下令:

phpunit.xml: 

<phpunit 
     colors="true" 
     bootstrap="./tests/bootstrap.php" 
     convertErrorsToExceptions="true" 
     convertNoticesToExceptions="true" 
     convertWarningsToExceptions="true" 
     strict="true" 
     stopOnError="false" 
     stopOnFailure="false" 
     stopOnIncomplete="false" 
     stopOnSkipped="false" 
     stopOnRisky="false" 
> 
    <testsuites> 
     <testsuite name="Your tests"> 
      <file>file1</file> //this will be run before file2 
      <file>file2</file> //this depends on file1 
     </testsuite> 
    </testsuites> 
</phpunit>