PHPUnit测试依赖项

时间:2015-11-05 21:09:05

标签: laravel testing phpunit

以前曾提出类似的问题,但我不太明白答案。我的具体情况是我有一个单元测试,它通过REST API端点测试用户的注册。但是,用户注册取决于数据库中必须存在的一些记录,否则将失败。将这些记录插入数据库绝对是一个测试用例。所以我的问题是,我应该按照特定的顺序执行我的测试以便记录存在,还是应该在每个依赖它的测试用例中再次显式插入记录?

这可能有些不相关,但我使用的是Laravel 5,所以测试是在PHPUnit中完成的。

1 个答案:

答案 0 :(得分:2)

  

我应该按照特定的顺序执行我的测试才能获得   记录存在,或者我应该再次显式插入记录   每个依赖它的测试用例?

我认为这里的正确答案是你不应该这样做(但请继续阅读,后者可能仍然可以,但不完美)。

如果您说注册用户本身就是一个测试用例。很好,然后写下测试,让我们假设你接下来的测试。

创建测试以便按顺序运行

让我们处理运行创建这些行一次然后对它们运行多个测试的第一个选项。 我认为无论在什么情况下这都是一个非常有缺陷的方法。突然间,所有测试都相互依赖。 假设您在这些行上运行测试A,B和C.也许它甚至是现在没有人改变行的情况。但是你无法确定B中是否有任何改变数据的错误(绝不仅仅是一个错误,可能只是底层功能被改变了)。 现在你处于测试C可能通过的情况,但前提是B没有运行。这是一个完全不可接受的情况,特别是当反之亦然时,C只有在B跑时才会通过。 这可以说明在现实生活中你的应用程序抛出错误的全新安装,而你的开发设置包含一堆数据,所以测试也是如此,因为B在你的数据库中创建了一个特定的状态(也许你的开发中随机存在)数据库)。 然后你把它交给一些可怜的顾客,突然间"选项X"未设置,或初始管理员用户不存在或其他:) => 糟糕的计划

为每个依赖于它的测试运行安装程序

这是一个明显更好的计划。现在,您至少可以完全控制每个测试中的数据库状态,并且它们彼此独立运行。 它们的运行顺序不会影响结果 =>的

对于一部分测试,这也是一个相对标准的事情。只是将您的主UnittestCase类子类化,并根据该事物的那个函数子类进行所有测试,如下所示:

abstract class NeedsDbSetupTestCase extends MyAppMainTestCase {

  function setUp(){
     parent::setUp();
     $this->setupDb();
  }

  private function setupDb(){
    //add your rows and tables and such
  }
}

=> 可接受的想法

最佳方法

以上仍然存在一些缺点。例如,一旦它依赖于非常具体的数据库交互,它就不再是一个单元测试,这使得它在确切地指出问题时不那么有价值。不可否认,这在许多情况下更多是理论问题而不是实际问题:)

虽然是表现,但更有可能成为一个实际问题。您正在添加一堆数据库写入,一旦您的测试诉讼增长,可能需要运行数百次。在项目开始时,这可能意味着运行它而不是2s需要4秒:P ...一旦项目增长,你可能会发现自己因此而失去了很多时间。

您可能还面临的最后一个问题是,您的测试套装依赖于它所针对的数据库。也许它通过针对MySQL 5.5的运行并且对5.6的失败(我猜的学术示例:P)=>你可能会遇到各种奇怪的行为,测试是在本地传递但在CI和whatnot中失败(有些可能取决于你的设置)。

由于您对此更感兴趣,所以让我在这里概述处理此问题的正确方法:)

这样的情况总是会导致你遇到麻烦:

类用户{   私人$ id;

public function get_data(){    return make_a_sql_call_and_return_row_as_array(" SELECT properta1,propertyb FROM users WHERE id ="。$ this-> id);   }

}

现在要测试一些实际使用get_data()返回的其他方法,你需要db :)中的数据......或者你只是模拟你的User对象!

假设您在另一个使用该User对象的类中有一些方法。 你的测试看起来像这样:

// run this in the context of the class that sets up the db for you
$user = new User($user_id);
$this->assertTrue(some_method_or_function($user);

$ user所需要的就是返回数组[1,5]。而不是插入它然后使用User实例,只需创建模拟:

// this one doesn't do anything yet, returns null on every method.
$user = $this->getMockBuilder('User')->disableOriginalConstructor()->get_mock();
// now just make it return what you want it to return
$user->method('get_data')->willReturn(array(1,2));
// And run your test lightning fast without having ever touched the database but getting the same result :)
$this->assertTrue(some_method_or_function($user);

这种方法的另一个隐藏(但有价值的)好处是,设置模拟实际上会强制您了解每个类行为的详细信息,最后让您对应用程序有更详细的了解。 显而易见的缺点是,它(并非总是但经常)需要更多的工作来以这种方式对测试进行编码,这样做的好处可能不值得。 特别是在使用WordPress等其他框架以及代码所依赖的框架时,真正模拟所有数据库交互可能有些不可行,而现有库为代码实现数据库测试功能提供了更慢但更简单的事情:)

但总的来说,选项3是要走的路,选项一是错的,选项二可能是每个人在现实生活中最终做的事情:D