“单元测试只有一件事”是指单元的一个特征还是一个整体场景?

时间:2015-03-12 20:37:12

标签: unit-testing tdd automated-tests

当人们说“只测试一件事”时。这是否意味着一次测试一个功能或一次测试一个场景?

method() {
    //setup data
    def data = new Data()
    //send external webservice call 
    def success = service.webserviceCall(data)
    //persist
    if (success) {
        data.save()
    }
}

基于该示例,我们是否按方法的特征进行测试:

testA() //test if service.webserviceCall is called properly, so assert if called once with the right parameter
testB() //test if service.webserviceCall succeeds, assert that it should save the data
testC() //test if service.webserviceCall fails, assert that it should not save the data

按方案:

testA() //test if service.webserviceCall succeeds, so assert if service is called once with the right parameter, and assert that the data should be saved
testB() //test if service.webserviceCall fails, so again assert if service is called once with the right parameter, then assert that it should not save the data

我不确定这是否是一个主观话题,但我正在尝试按功能方法。我从Roy Osherove的博客中得到了这个想法,但我不确定我是否理解它是正确的。

有人提到隔离错误会更容易,但我不确定它是否过度杀伤。复杂的方法往往会有很多测试。

(请原谅我对有关功能/场景的措辞,我不确定如何说出来)

4 个答案:

答案 0 :(得分:1)

你说得对,这是一个主观的话题。 想想你希望这种方法如何表现,而不仅仅是它当前如何实现。否则,您的测试将只反映生产代码,并且每次实施更改时都会中断。 根据提供的有限上下文,我将编写以下(单独)测试:

  • 是否使用预期数据调用了webservice命令?
  • 如果命令成功返回,是否保存了数据?不要在此处过度指定为您的webservice调用提供的参数,因为之前的测试涵盖了这一点。
  • 如果在命令返回失败时不保存数据很重要,我会为此编写第三个测试。如果它不重要,我甚至都不会打扰。

你可能听过这句格言“每次测试一个断言”。一般来说这是一个很好的建议,因为一旦单个断言失败,测试就会停止执行。所有断言都没有被执行。通过在多个测试中拆分断言,当出现问题时,您将收到更多反馈。当测试变为红色时,您确切地知道所有失败的断言,并且不必运行-fix断言失败,运行测试,修复下一个断言失败,重复循环。

因此,在您提出的术语中,我的方法也是为该方法的每个特征编写一个测试。

Sidenote :您在方法本身中构造数据对象并调用该对象的save方法。您如何感觉数据在测试中保存?

答案 1 :(得分:1)

我理解如下: "单元测试一件事" =="单元测试一种行为" (毕竟,这是客户想要的行为!)

我建议你一次接近你的测试"一个功能"。我同意你的意思,你引用了这种方法,它更容易隔离错误"。 Roy Osherove确实知道他在谈论什么,特别是谈到TDD。

根据我的经验,我喜欢关注我试图测试的行为(我在这里并不是特别指BDD)。基本上我会测试我期望从这段代码中的每个行为。你说你正在嘲笑依赖项(webservice和数据存储),所以我仍然将它作为一个单元测试,并带有以下预期行为:

  • 对此方法的调用将导致对Web服务的特定调用
  • 成功的Web服务调用将导致数据被保存
  • 不成功的Web服务调用将导致数据未保存

对这三种行为进行测试将帮助您立即隔离代码中的任何问题。

您的测试也应该不依赖于为实现该行为而编写的实际代码。例如,如果我的实现调用了我的类内部的一些装饰器,而这些装饰器又正确调用了webservice,那么这应该与我的测试无关。我的测试应该只关注类本身的外部依赖关系和公共接口。 如果我为了测试它的特定实现而暴露了我的类的内部方法(或实现细节,例如上面提到的装饰器),那么我已经创建了在实现发生变化时将会失败的脆弱测试。

总之,我建议您的测试应该锁定类的行为并隔离失败以识别行为单位'那是失败的。

答案 2 :(得分:0)

单元测试通常是在没有调用数据库或文件系统的情况下完成的测试,甚至也不会调用Web服务。单元测试的想法是,如果您没有任何互联网连接,您应该能够进行单元测试。所以说,如果方法调用web服务或调用数据库,那么基本上你应该模拟来自外部系统的响应。您应该仅测试该工作单元。正如上面提到的prgmtc关于你应该如何断言每个方法的一个断言是要走的路。

其次,如果您正在调用真正的Web服务或数据库等,那么根据您要测试的内容,考虑将这些测试称为集成测试或集成测试。

答案 3 :(得分:0)

在我看来,为了充分利用TDD,您希望首先进行测试开发。看看叔叔Bobs 3 Rules of TDD

如果严格遵循这些规则,最终会编写通常只有一个断言语句的测试。实际上,您经常会发现最终会出现一些断言语句,这些语句充当单个逻辑断言,因为它通常有助于理解单元测试本身。

这是一个例子

        [Test]
    public void ValidateBankAccount_GivenInvalidAccountType_ShouldReturnValidationFailure()
    {
        //---------------Set up test pack-------------------
        const string validBankAccount = "99999999999";
        const string validBranchCode = "222222";
        const string invalidAccountType = "99";
        const string invalidAccoutTypeResult = "3";
        var bankAccountValidation = Substitute.For<IBankAccountValidation>();
        bankAccountValidation.ValidateBankAccount(validBankAccount, validBranchCode, invalidAccountType)
            .Returns(invalidAccoutTypeResult);
        var service = new BankAccountCheckingService(bankAccountValidation);
        //---------------Assert Precondition----------------

        //---------------Execute Test ----------------------
        var result = service.ValidateBankAccount(validBankAccount, validBranchCode, invalidAccountType);
        //---------------Test Result -----------------------
        Assert.IsFalse(result.IsValid);
        Assert.AreEqual("Invalid account type", result.Message);
    }

从服务返回的ValidationResult类

    public interface IValidationResult
{
    bool IsValid { get; }
    string Message { get; }
}

public class ValidationResult : IValidationResult
{
    public static IValidationResult Success()
    {
        return new ValidationResult(true,"");
    }

    public static IValidationResult Failure(string message)
    {
        return new ValidationResult(false, message);
    }

    public ValidationResult(bool isValid, string message)
    {
        Message = message;
        IsValid = isValid;
    }

    public bool IsValid { get; private set; }
    public string Message { get; private set; }
}

注意我会对ValidationResult类本身进行单元测试,但在上面的测试中,我觉得它更清晰地包括两个Asserts。