由于我是TDD新手,我目前正在开发一个小型C#控制台应用程序以便练习(因为练习很完美,对吧?)。我首先简单地描述了如何组织应用程序(按类),并开始逐个开发我可以识别的所有域类(当然先测试)。
最后,为了使应用程序可运行,必须将类集成在一起,即在Main方法中放置必要的代码,调用必要的逻辑。但是,我不知道如何以“先测试”的方式完成最后的整合步骤。
如果我使用“自上而下”的方法,我想我不会遇到这些问题。问题是:我该怎么做?我应该通过测试Main()方法开始吗?
如果有人能给我一些指示,我们将非常感激。
答案 0 :(得分:31)
“自上而下”是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倍,因此这也有助于使代码具有可维护性。
出于这个原因,我使用了一种从外到内的方法,而不是自下而上的智能猜测。我的经验是,它可以实现更简单,更强大的解耦,更易读和更易维护的代码。
答案 1 :(得分:2)
如果你把东西移出main(),你能不测试那个功能吗?
这样做是有意义的,因为您可能希望使用不同的cmd-args运行,并且您想要测试它们。
答案 2 :(得分:0)
您也可以测试您的控制台应用程序。我觉得很难,看看这个例子:
[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。
答案 3 :(得分:0)
我建议“自上而下”(我称之为高级测试)。我写了这篇文章:
http://www.hardcoded.net/articles/high-level-testing.htm
是的,你应该直接测试你的控制台输出。当然,最初设置测试是一件很痛苦的事情,因此很容易直接测试控制台输出,但是如果你创建了合适的帮助代码,你的下一个“高级”测试将更容易编写。通过这样做,您可以使自己获得无限的重新分解潜力。使用“自下而上”,您的初始阶级关系非常严格,因为更改关系意味着更改测试(大量工作和危险)。
答案 4 :(得分:0)
MSDN magazine of December中有一篇有趣的文章描述了“BDD循环如何利用驱动单元级实现的功能级测试来包装传统的测试驱动开发(TDD)周期”。细节可能过于技术性,但想法和流程概述与您的问题相关。
答案 5 :(得分:0)
主要应该是非常简单的。我不知道它在c#中是怎么样的,但在c ++中应该看起来像这样:
#include "something"
int main( int argc, char *argv[])
{
return TaskClass::Run( argc, argv );
}
将已创建的对象传递给类的构造函数,但在单元测试中传递模拟对象。
有关TDD的更多信息,请查看these screencasts。他们解释了如何进行敏捷开发,但它也讨论了如何使用c#中的示例进行TDD。
答案 6 :(得分:-1)
“自下而上”可以为您节省大量工作,因为您已经拥有低级别课程(经过测试),您可以在更高级别的测试中使用它们。在相反的情况下,你必须编写很多(可能很复杂的)模型。 “自上而下”通常也不会像预期的那样工作:你想出一些不错的高级模型,测试并实现它,然后向下移动你意识到你的一些假设是错误的(即使对于有经验的人也是如此程序员)。当然,模式在这里有所帮助,但它也不是银弹。
要获得完整的测试覆盖率,您无论如何都需要测试您的Main。我不确定在所有情况下都值得努力。只需将所有逻辑从Main移动到单独的函数(如Marcus Lindblom建议的那样),测试它并让Main将命令行参数传递给您的函数。
控制台输出是最简单的测试能力,如果使用TDD,它只能用于输出没有任何诊断和调试消息的最终结果。所以让你的顶级函数返回实体结果,测试它并在Main
中输出它