【问题标题】:How to refactor tests in tdd?如何在 tdd 中重构测试?
【发布时间】:2015-06-01 07:51:41
【问题描述】:

我正在执行这个 TDD kata 练习:http://osherove.com/tdd-kata-1

我生成了以下代码(本练习中从 1 到 5 的点 - 我对其进行了单元测试):

public class StringCalculator
{
    private readonly string[] _defaultSeparators = { ",", "\n" };

    public int Add(string numbers)
    {
        // Parser section (string to list of ints)
        var separators = _defaultSeparators;

        var isSeparatorDefinitionSpecified = numbers.StartsWith("//");
        if (isSeparatorDefinitionSpecified)
        {
            var endOfSeparatorDefinition = numbers.IndexOf('\n');

            var separator = numbers.Substring(2, endOfSeparatorDefinition - 2);

            numbers = numbers.Substring(endOfSeparatorDefinition);
            separators = new[] { separator };
        }

        var numbersArray = numbers.Split(separators, StringSplitOptions.RemoveEmptyEntries);
        var numbersArrayAsInts = numbersArray.Select(int.Parse).ToArray();

        // Validator section
        var negativeNumbers = numbersArrayAsInts.Where(c => c < 0).ToArray();
        if (negativeNumbers.Any())
        {
            throw new Exception(string.Format("negatives not allowed ({0})", string.Join(", ", negativeNumbers)));
        }

        return numbersArrayAsInts.Sum();
    }
}

现在我想将代码重构为这样的:

public int Add(string numbers)
{
    var numbersAsInts = CalculatorNumbersParser.Parse(numbers);

    CalculatorNumbersValidator.Validate(numbersAsInts);

    return numbersAsInts.Sum();
}

我应该如何规划重构以正确重构我的代码和单元测试?

我认为我应该将部分测试移至新创建的实现类测试(CalculatorNumbersParserTests 和 CalculatorNumbersValidatorTests),更改一些现有测试并添加用于 Parse 和 Validate 方法执行的测试。

但是在不破坏测试的情况下正确的方法是什么?

【问题讨论】:

  • 只需将代码移动到通过 StringCalculator(公共)类型的测试间接测试的内部类中。或者,这两者也可以实现为同一类型的私有方法。

标签: unit-testing tdd


【解决方案1】:

我会提醒不要移动测试,因为如果您这样做,那么您的测试会与实现相关联,这意味着它们非常脆弱,因此您每次想要更改实现时都必须更改测试。当您拥有庞大的代码库时,这很快就会变得昂贵,并且可能成为进行更改的阻碍因素。

您现有的测试应该指定字符串计算器的行为,因此您可以将您的实现重构为任何东西,只要您保持所需的行为。

我倾向于将单元视为“行为单元”,并且可能需要几个类来实现这一点。

如果您将某些类放在不同的程序集中,情况可能会发生变化,此时您可能希望在新程序集旁边进行一些新测试,以确保这些组件的行为不会意外更改,但在这种情况我怀疑你会这样做。

如果您开始在多个地方重用类,情况也可能会发生变化,此时您可能需要单独的测试来指定类的行为,而与它们在这些地方的使用无关。

【讨论】:

  • 你的意思是我应该在我的测试中实例化 CalculatorNumbersParser 和 CalculatorNumbersValidator (不是模拟)?当我的单元测试更改为集成测试时,在 TDD 中可以这样做吗?
  • 这取决于您认为什么是“单位”。我将“单元”视为功能或行为的单元,它可能涉及多个类。如果您按照您的建议进行操作(对每个班级进行独立测试),根据我的经验,您将进行非常脆弱的测试,每次更改代码时都需要花费大量精力进行更改。在'Art of unit testing' 中,定义是A unit of work can span a single method, a whole class or multiple classes working together to achieve one single logical purpose that can be verified.
  • @czesio:或者您可以将此功能移至私有方法或私有类。您需要考虑它们的(此类)存在 在计算器类之外是否有意义;即它们代表独立的功能。对于这个 kata 练习,这样的重构可能太牵强了。
【解决方案2】:

我认为@Sam Holder 涵盖了大部分内容。我要补充的一件事是,当您重构代码时,您应该将您创建的任何不会为其编写特定测试的类标记为internal(我假设您正在使用.net ),以便它们在包含它们的程序集之外不可见。

我倾向于认为public 类应该单独进行测试,因为它们可以很容易地被引用程序集的其他代码实例化。而另一方面,internal 类可以被认为是实现细节,通常可以通过程序集公共接口进行测试。当然也有例外,这取决于你在做什么/代码的复杂性等,但这是我的一般规则。

【讨论】:

  • 这是一个很好的一般规则,您可以使用InternalsVisibleTo 属性来确保您的测试在必要时仍然可以看到这些类(不应该,但有时...)
猜你喜欢
  • 1970-01-01
  • 2011-09-14
  • 1970-01-01
  • 2011-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-18
  • 1970-01-01
相关资源
最近更新 更多