单元测试,集成测试还是功能测试?

时间:2019-04-11 15:58:06

标签: laravel unit-testing eloquent naming-conventions standards

一个简单的问题:您如何区分功能,单元和集成测试?

有很多不同意见,但是我正在特别尝试确定如何组织涉及模型关系的Laravel测试。这是一些需要测试的PHP代码的示例:

public function prices()
{
    return $this->hasMany(Prices::class);
}


public function getPriceAttribute($)
{
    return $this->prices()->first() * 2;
}

据我所知,测试说明(可以纠正我):

单元测试

  • 测试代码的最小部分
  • 不触摸数据库
  • 不与系统的任何其他部分进行交互

集成测试

  • 测试系统部分是否可以协同工作
  • 例如调用帮助程序功能的控制器,需要一起进行测试

功能测试

  • 黑盒测试
  • 例如调用api端点,查看它已返回正确的JSON响应

以下是我的问题:

  • 我的Laravel模型测试需要测试最小的代码单元-模型的计算访问器,这使其感觉像是单元测试
  • 但是,它在加载模型关系时会触及数据库
  • 感觉不像是集成测试,因为它仅涉及其他相关模型,而不涉及内部或外部服务
  • Laravel中的其他属性访问器测试如果不接触数据库或模型之间的关系,将属于单元测试
  • 将这些类型的测试分为集成测试意味着将单个模型针对其属性的测试分散在集成测试和单元测试之间

因此,如果没有模型之间的模拟关系,我的测试将属于哪里?

2 个答案:

答案 0 :(得分:2)

如果我正确地解释了您的原始问题,那么我认为这里的主要限制因素是:

因此,如果没有模型之间的模拟关系,我的测试将属于哪里?

如果不允许模拟,并且您需要触摸数据库,那么根据您/和Google的定义,它必须属于集成/中等大小测试:)


我认为获取价格属性功能的方式与DB分开。即使在模型中,价格也可能来自任何地方。现在,它是一个RDBMS,但是如果您的组织规模真的很大,又分裂成另一个服务怎么办?基本上,我相信getPriceAttributes的功能不同于属性的存储:

public function getPriceAttribute($)
{
    return $this->prices()->first() * 2;
}

如果您支持这种推理,它将创建支持单元测试的逻辑分隔。可以模拟prices()来返回0、1和许多(2)结果的集合。该测试可以作为单元测试来执行(测试执行速度要快几个数量级(即与本地数据库通话的时间可能是1ms与 可能是10s或100s的数量级)


我不熟悉php测试生态系统,但是一种实现方法可能是使用test specific subclass(不确定以下内容是否有效的PHP:p):

class PricedModel extends YourModel {
   function __construct($stub_prices_supporting_first) {
     $this->stub_prices = $stub_prices_supporting_first;
   }

   public function prices() {
     return $this->stub_prices;
   }

}

测试

function test_priced_model_0_prices() {
   p = new PricedModel(new Prices(array()));
   assert.equal(null, p.getPriceAttribute());
}

function test_priced_model_1_price() {
   p = new PricedModel(new Prices(array(1)));
   assert.equal(2, p.getPriceAttribute());
}

function test_priced_model_2_prices() {
   p = new PricedModel(new Prices(array(5, 1)));
   assert.equal(10, p.getPriceAttribute());
}

希望以上内容可以让您完全控制getPriceAttribute方法的输入,以支持直接的无IO单元测试。

- 同样,上面所有的单元测试都可以告诉您,您能够正确处理价格,如果您能够查询价格,则不会对价格产生任何反馈!

答案 1 :(得分:0)

测试的区别在于它们各自的目标:

  • 单元测试旨在发现那些可以在软件的孤立小部分中发现的错误。 (请注意,这并不是说您必须 隔离-只是意味着您将重点放在隔离的代码上。通常不需要足够的隔离和嘲笑来实现此目标:请考虑调用{{ 1}}函数-您几乎不需要模拟它,只需让您的被测系统调用原始系统即可。)

  • 集成测试旨在发现两个或多个组件交互中的错误,例如,对接口的相互误解。这些错误无法在隔离的软件中找到:如果隔离地测试代码,则也会基于对其他组件的理解(可能是错误的)编写测试。

  • 您所描述的功能测试将以发现其他错误为目标,这些错误到目前为止是其他测试无法检测到的。此类错误的一个例子可能是,该功能的旧版本已集成(当时是正确的,但缺少某些功能)。

尽管可能令人惊讶,但结论是,在单元测试中进行数据库访问并不是严格意义上的禁止。请考虑以下情形:您开始编写单元测试并模拟数据库访问。后来,您意识到自己可以变得更懒惰,只使用数据库而不进行模拟-否则将所有测试保持原样。您的测试没有更改,它们将像以前一样继续在隔离的代码中发现错误。它们现在运行起来可能会慢一些,并且设置可能会比模拟数据库更复杂。但是,测试套件的目标是相同的-带有和不带有模拟数据库。

此方案稍微简化了事情,因为可能存在只能用模拟进行的测试用例:例如,测试数据库以特定方式损坏且代码可以正确处理的情况。使用真实的数据库,实际上不可能建立这样的测试用例。