【问题标题】:Hard-coded vs Soft-coded expected values for Integration Testing集成测试的硬编码与软编码期望值
【发布时间】:2019-02-18 13:06:38
【问题描述】:

我知道在单元测试中,硬编码是首选,因为我们不想编写尽可能多的代码。

但是,对于集成测试,相同的原则是否仍然适用?对于上下文,这是一个常见的场景:

  • TodoController
  • TodoService
  • Serializer/Transformer/随便你怎么称呼它

以下是关于这两种方法的假想代码:

  • 硬编码
// Arrange
$note = new Note('note123', 'John', 'example message');

$this->noteRepository->save($note);

// Act
$response = $this->json('GET', '/api/notes');

// Assert
$response->seeJsonEquals([
   'id' => 'note123',
   'author' => 'John',
   'message' => 'example message'
]);

  • 软编码(计算)
// Arrange
$note = new Note('note123', 'John', 'example message');

$this->noteRepository->save($note);

// Act
$response = $this->json('GET', '/api/notes');

// Assert
$noteSerializer = new NoteSerializer();

$response->seeJsonEquals($noteSerializer->serialize($note));

软编码方法的唯一问题是,如果 Serializer 是问题所在,测试仍然会通过,因为控制器和预期值都使用它。

但是,我们可以通过为 Serializer 创建另一个测试来解决这个问题。

我们可能有很多小测试,但我认为与硬编码相比,它可以节省大量时间。如果我们改变我们的响应结构,硬编码的测试也需要一些改变,但软编码的测试只需要在它自己的测试中改变。

我可能遗漏了一些东西,但我已经尝试用谷歌搜索它,我看到的都是关于单元测试的,所以我想问问是否同样的原则也适用于集成测试

【问题讨论】:

    标签: testing tdd integration-testing


    【解决方案1】:

    TL;DR:是的,让集成测试假设其他测试策略分担检测错误的责任是非常有意义的。

    我想你会发现这里有两种不同的想法,它们混合在一起。

    一个问题是独立验证。您可以运行许多测试来证明给定解决方案在内部是一致的,但这并不等同于证明给定解决方案是正确的。后者通常需要向测试对象查询数据,然后进行独立评估。

    UltimateAnswer lifeTheUniverseAndEverything = deepThought()
    
    // Compare this
    assertEquals(new UltimateAnswer(42), lifeTheUniverseAndEverything);
    
    // to this
    assertEquals(42, lifeTheUniverseAndEverything.toInt());
    

    什么是独立的?我认为这是一条模糊的线——如果我们有足够的测试来对 UltimateAnswer::equals 有任意数量的 9 置信度,那么将验证视为独立的可能会很好。另一方面,通过使用与域无关的原语“独立地”验证事情是否有效,我已经被烧毁了至少两次,只是发现我实际上是在执行 dependent 验证,并且测试失败了捕捉我期望的错误。

    第二个问题是过度拟合——通常情况下,许多可区分的行为可能都令人满意。示例:List.shuffle() 的结果应该是什么?如果测试旨在描述您的要求,那么它们将比记录示例行为的测试更宽容。

    当您的主要活动是重构时,严格拟合的测试非常棒,并且您正在尝试验证您所做的更改确实保留了系统的精确行为。当测试一个新系统时,它们可能会很糟糕,其行为的核心偏差会随处可见(考虑在日期格式要求更改后验证输出字符串的测试)。

    在我看来,对于“集成测试”和“单元测试”而言,这些问题都没有特别不同。诚然,问题的一部分在于,从来没有特别清楚其他人对这些想法的定义。

    在大多数情况下,不同类型的测试有不同的权衡取舍。我们希望我们的验证具有成本效益。所以我们可能会有一个分层的测试策略,我们实施的检查类型取决于上下文。

    【讨论】:

    • “成本效益”测试是这个答案的主要内容。正如刚刚在 OP 中所述,如果响应结构发生更改,与重构 NoteSerializer 并对其进行测试相比,硬编码断言将花费一些时间来重构。
    【解决方案2】:

    在您的特定示例中,使用 NoteSerializer 没有意义,因为您的断言使用与实现相同的代码构建。

    你认为这个测试有价值吗?

    // Arrange
    $original = 42;
    $expected = $original + 100;
    
    // Act
    $actual = $original + 100;
    
    // Assert
    this-> assertEquals($expected, $actual);
    

    使用NoteSerializer 构建预期值的问题是,正如您已经注意到的,如果序列化程序被破坏 - 测试将保持绿色。

    相反,您可以将收到的响应反序列化为一个类,并将其与原始 $note 进行比较

    【讨论】:

    • 正如 OP 中所述,NoteSerializer 也将有自己的测试。
    • @doesnotmatter, NoteSerializer 是实现细节。测试不应该关心/知道序列化是如何发生的。
    • 我明白了。在 VoiceOfUnreason 的回答中,以成本效益为代价的想法怎么样?
    • @doesnotmatter,权衡在不同情况下是不同的。在 OP 情况下,我不会担心序列化,因为我最关心的是返回什么值。如果框架允许,我会将序列化移动到中间件的某个位置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-09
    • 1970-01-01
    相关资源
    最近更新 更多