单元测试-入门


14

我刚刚开始进行单元测试,但不确定我是否真的了解所有内容。我阅读了有关这方面的教程和书籍,但是我有两个快速问题:

  1. 我认为单元测试的目的是测试我们实际编写的代码。但是,在我看来,为了能够运行测试,我们必须更改原始代码,这时我们并不是在真正测试我们编写的代码,而是在测试我们编写的代码。

  2. 我们的大多数代码都依赖于外部资源。但是,在重构我们的代码后,即使它会破坏原始代码,我们的测试仍然可以正常运行,因为外部源只是我们测试用例内部的模型。它是否违反了单元测试的目的?

抱歉,我在这里听起来很蠢,但我认为有人可以启发我。

提前致谢。

Answers:


7

我的0.02 $ ...这有点主观,因此请加一点盐,但希望它能使您思考和/或引发一些对话:

  1. 对我来说,单元测试的主要目的是确保您编写的代码能够满足合同规定的要求,并能使您的代码能够满足实际情况。有了单元测试,您可以更好地确保当您(或将来的其他人)重构代码时,如果您具有适当的状态覆盖范围,那么代码的任何外部使用者都应保持不受影响。(至少在您不希望它们受到影响的程度上)。

    在大多数情况下,您应该能够编写既可以交付生产又可以进行单元测试的代码。一个好的开始可能是研究依赖注入模式和框架。(或您选择的语言/平台的其他哲学)。

  2. 外部实现可能会影响您的代码是正确的。但是,确保您的代码作为大型系统的一部分正确运行是集成测试的功能。(这也可以通过不同程度的努力实现自动化)。

    理想情况下,您的代码应仅依赖于任何第三方组件的API合同,这意味着只要您的模拟满足正确的API,单元测试就仍然可以提供价值。

    那就是说,我很容易承认,有时候我会放弃单元测试而只支持集成测试,但这只是在我的代码必须与文档较差的API与第三方组件进行如此大量交互的情况下。(即例外而不是规则)。


5
  1. 尝试先编写测试。这样,您将为代码的行为奠定坚实的基础,并且测试将成为代码所需行为的契约。因此,改变代码以通过测试变为“改变代码以履行由测试提出的合同”,而不是“改变代码以通过测试”。
  2. 好吧,请注意存根和模拟之间的区别。不受代码中任何更改影响的是存根(stub)的典型行为,而不是模拟。让我们从模拟的定义开始:

    模拟对象在测试条件下替换了真实对象,并允许在系统或单元测试中针对自身验证调用(交互)。

    -单元测试的艺术

基本上,您的模拟应该检查交互的必要行为。因此,如果重构后与数据库的交互失败,则使用模拟的测试也将失败。这当然是有局限性的,但是通过精心计划,您的模拟将不仅仅是“坐在那里”,而且不会“破坏单元测试的目的”。


1

提出一个好的问题绝不是愚蠢的。

我会解决您的问题。

  1. 单元测试的目的不是测试您已经编写的代码。它没有时间观念。仅在TDD中,您应该首先进行测试,但这并不严格适用于任何类型的单元测试。关键是要能够在类级别自动有效地测试您的程序。即使您需要更改代码,也要做到达该处所需的操作。而且,我告诉您一个秘密-这通常就是那样。
  2. 编写测试时,有两个主要选项可帮助确保测试正确:

    • 改变每个测试的输入
    • 编写一个测试用例,以确保您的程序正常运行,然后编写一个相应的测试用例,以确保您的程序无法正常运行

    这是一个例子:

    TEST(MyTest, TwoPlusTwoIsFour) {
        ASSERT_EQ(4, 2+2);
    }
    
    TEST(MyTest, TwoPlusThreeIsntFour) {
        ASSERT_NE(4, 2+3);
    }
    

    最重要的是,如果你是单元测试的逻辑里面你的代码(而不是第三方库),那么它是完全正常的,你不用担心其他的密码破译,而在这方面。本质上,您正在测试逻辑包装和使用第三方工具的方式,这是经典的测试方案。

在课程级别完成测试后,就可以继续测试您的课程(根据我的理解,中介者)与第三方库之间的集成。这些测试称为集成测试,不使用模拟,而是使用所有类的具体实现。它们速度稍慢,但仍然非常重要!


1

听起来您好像有一个单片应用程序,它可以完成void main()从数据库访问到输出生成的所有工作。在开始正确的单元测试之前,这里有几个步骤。

1)查找一段已编写/复制粘贴多次的代码。即使只是string fullName = firstName + " " + lastName。将其分解为一个方法,例如:

private static string GetFullName (firstName, lastName)
{
    return firstName + " " + lastName;
}

现在,您有一段可单元测试的代码,无论多么琐碎。为此编写一个单元测试。冲洗并重复此过程。最终您将获得大量逻辑分组的方法,并且能够从中提取许多类。这些类中的大多数将是可测试的。

另外,一旦提取了多个类,就可以从它们中提取接口并更新程序以与接口而不是对象本身对话。到那时,您可以使用模拟/存根框架(甚至是您自己手工制作的假东西),而根本无需更改程序。一旦将数据库查询提取到一个(或多个)类中,这将非常方便,因为现在您可以伪造查询返回的数据。


0

一些聪明的人说:“如果很难测试,那可能是不好的代码”。这就是为什么可以对代码进行单元测试来重写代码不是一件坏事。具有外部依赖关系的代码非常难以测试,它代表着代码的风险,应尽可能避免使用,并且集中于代码的特定集成区域fx。门面/网关类型类。这将使外部组件的更改更容易应对。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.