【问题标题】:Using TDD: "top down" vs. "bottom up"使用 TDD:“自上而下”与“自下而上”
【发布时间】:2011-01-01 22:25:54
【问题描述】:

由于我是 TDD 新手,我目前正在开发一个小型 C# 控制台应用程序以进行练习(因为熟能生巧,对吧?)。我首先对如何组织应用程序(按类)进行了简单的草图绘制,然后开始逐个开发我可以识别的所有域类(当然是先测试)。

最后,必须将这些类集成在一起以使应用程序可运行,即将必要的代码放在调用必要逻辑的 Main 方法中。但是,我不知道如何以“测试优先”的方式完成最后一个集成步骤。

如果我使用“自上而下”的方法,我想我不会遇到这些问题。问题是:我该怎么做?我应该从测试 Main() 方法开始吗?

如果有人能给我一些建议,将不胜感激。

【问题讨论】:

  • 您是否有任何高级测试用例,如果通过将确认应用程序正在运行并且问题已解决?证明整个事情有效的东西(当然,将 UI 排除在外)。 (我也是 TDD 新手。)
  • 我没有任何高级测试用例,也许这就是缺少的。唯一的问题是:如此高水平的测试会是什么样子?该应用程序仅在控制台中生成文本。我应该断言那里写的是什么吗?
  • 当您说“集成”时,这并不意味着您要测试对象的连接是否正确,而是所有类一起按预期工作。现在,大多数人建议您应该有一些测试用例来告诉您是否已完成工作。正如马库斯所指出的,您在主要功能中所拥有的可能是一个测试,一个积极的测试。您会看到,主要方法是在测试驱动应用程序中编写的最后一种方法。我建议你看看验收测试。这本书你可能会感兴趣:growing-object-oriented-software.com
  • 是的,澄清一下,我写了“集成”,但意思是“连接”。我现在实际上正在阅读那本书,他们似乎建议您应该按照您的描述设置验收测试。我想这意味着我应该测试实际打印到控制台的内容,例如错误信息?

标签: tdd


【解决方案1】:

“自上而下”是already used in computing to describe an analysis technique。我建议改用“由外而内”一词。

Outside-in 是 BDD 中的一个术语,我们认识到一个系统通常有多个用户界面。用户可以是其他系统,也可以是人。 BDD 方法类似于 TDD 方法。它可能对你有帮助,所以我将简要描述一下。

在 BDD 中,我们从一个场景开始——通常是用户使用系统的简单示例。围绕场景的对话有助于我们了解系统应该真正做什么。我们编写了一个用户界面,如果需要,我们可以根据该用户界面自动执行场景。

在编写用户界面时,我们会尽可能地精简它。用户界面将使用另一个类——控制器、视图模型等——我们可以为其定义一个 API。

在这个阶段,API 要么是一个空类,要么是一个(程序)接口。现在我们可以编写用户界面如何使用此控制器的示例,并展示控制器如何传递价值。

这些示例还显示了控制器职责的范围,以及它如何将其职责委托给其他类,如存储库、服务等。我们可以使用模拟来表达这种委托。然后我们编写该类以使示例(单元测试)工作。我们只编写了足够的示例以使我们的系统级场景通过。

我发现修改模拟示例很常见,因为模拟的接口一开始只是猜测,然后随着类的编写而更加全面地出现。这将帮助我们定义下一层接口或 API,我们将为此描述更多示例,依此类推,直到不再需要模拟并且第一个场景通过。

随着我们描述的场景越来越多,我们会在类中创建不同的行为,并且可以重构以消除不同场景和用户界面需要相似行为的重复。

通过以一种由外而内的方式执行此操作,我们可以获得尽可能多的关于 API 应该是什么的信息,并尽快重新设计这些 API。这符合Real Options (never commit early unless you know why) 的原则。我们不会创建任何我们不使用的东西,并且 API 本身是为可用性而设计的,而不是为了易于编写。代码倾向于使用领域语言而不是编程语言以更自然的风格编写,使其更具可读性。由于代码的阅读量大约是编写量的 10 倍,这也有助于使其可维护。

因此,我会使用由外而内的方法,而不是自下而上的智能猜测。我的经验是,它会产生更简单、更强大的解耦、更易读和更可维护的代码。

【讨论】:

  • 这很有帮助,谢谢!听起来我应该使用覆盖验收测试来测试我的应用程序。
  • 我发现“测试”这个词往往会让人们想到将事情固定下来,阻止事情被破坏,确保事情正常等等。相反,试着考虑描述一些例子如何工作。您正在帮助其他人了解您的代码的价值以及行为如何传递价值,以便他们可以安全地更改它。这就是为什么我(和其他 BDDers)使用“场景”和“示例”这两个词来代替。希望这是有道理的。只有在编写代码后才成为测试。
  • 这是一个很好的答案,非常感谢。信息量很大。 +1
【解决方案2】:

如果你把东西移出 main(),你能不测试那个函数吗?

这样做是有意义的,因为您可能想使用不同的 cmd-args 运行并且您想测试它们。

【讨论】:

  • 是的,我可以这样做,这可能是目前最容易做的事情。但是,一开始,我不应该从一些更高级别的测试开始以测试驱动应用程序吗?感觉就是这样,但也许我做得太多了。
  • 好吧,在文件中指定控制台的输入/输出是一种方法,但我认为这对回归测试更有效,但是 YMMV。 Mercurial DVCS 项目在他们的套件中正是这样做的,它对他们来说工作得很好,但是他们有很好的命令来检查任何操作的结果。
【解决方案3】:

您也可以测试您的控制台应用程序。我发现它很难,看看这个例子:

[TestMethod]
public void ValidateConsoleOutput()
{
    using (StringWriter sw = new StringWriter())
    {
        Console.SetOut(sw);
        ConsoleUser cu = new ConsoleUser();
        cu.DoWork();
        string expected = string.Format("Ploeh{0}", Environment.NewLine);
        Assert.AreEqual<string>(expected, sw.ToString());
    }
}

你可以看看full post here

【讨论】:

    【解决方案4】:

    “自下而上”可以为您节省大量工作,因为您已经拥有低级课程(经过测试),并且可以在更高级别的测试中使用它们。在相反的情况下,您必须编写很多(可能很复杂)模型。 “自上而下”通常也不会像预期的那样工作:您想出一些不错的高级模型,测试并实现它,然后向下移动,您会意识到您的某些假设是错误的(即使对于有经验的人来说,这也是非常典型的情况程序员)。当然,模式在这里会有所帮助,但它也不是灵丹妙药。


    要获得完整的测试覆盖率,无论如何您都需要测试 Main。我不确定在所有情况下都值得付出努力。只需将所有逻辑从 Main 移动到单独的函数(正如 Marcus Lindblom 建议的那样),对其进行测试并让 Main 将命令行参数传递给您的函数。


    控制台输出是一种最简单的测试能力,如果您使用 TDD,它可以仅用于输出最终结果,而无需任何诊断和调试消息。所以让你的顶级函数返回可靠的结果,测试它并在 Main 中输出它

    【讨论】:

      【解决方案5】:

      我推荐“自上而下”(我称之为高级测试)。我写过:

      http://www.hardcoded.net/articles/high-level-testing.htm

      所以是的,你应该直接测试你的控制台输出。当然,最初设置测试以便直接测试控制台输出很容易,但如果您创建适当的帮助代码,您的下一个“高级”测试将更容易编写。通过这样做,您可以赋予自己无限的重构潜力。使用“自下而上”,您的初始类关系非常僵化,因为更改关系意味着更改测试(大量工作且危险)。

      【讨论】:

        【解决方案6】:

        MSDN magazine of December 中有一篇有趣的文章,它描述了“BDD 循环如何将传统的测试驱动开发 (TDD) 循环与驱动单元级实现的功能级测试相结合”。细节可能过于针对技术,但想法和流程大纲听起来与您的问题相关。

        【讨论】:

          【解决方案7】:

          main最后应该很简单。我不知道它在 c# 中的样子,但在 c++ 中它应该是这样的:

          #include "something"
          
          int main( int argc, char *argv[])
          {
            return TaskClass::Run( argc, argv );
          }
          

          将已创建的对象传递给类的构造函数,但在单元测试中传递模拟对象。

          有关 TDD 的更多信息,请查看these screencasts。他们解释了如何进行敏捷开发,但也讨论了如何进行 TDD,并附有 c# 中的示例。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-10-05
            • 1970-01-01
            • 2011-06-23
            • 2023-03-23
            • 2012-04-26
            • 2021-10-30
            • 1970-01-01
            • 2013-09-15
            相关资源
            最近更新 更多