C# 如何先编写单元测试,然后编写代码?

C# 如何先编写单元测试,然后编写代码?,c#,unit-testing,nunit,moq,C#,Unit Testing,Nunit,Moq,我不熟悉单元测试,并且已经读了好几遍,我们应该先编写单元测试,然后再编写实际代码。到目前为止,我正在编写我的方法,然后对代码进行单元测试 如果您先编写测试… 您倾向于编写适合测试的代码。这鼓励了 “解决问题的最简单的事情”式的开发和维护 你专注于解决问题,而不是解决元问题 如果您先编写代码… 您可能会试图编写测试来适应代码。实际上 这就相当于写一个问题来符合你的答案,也就是 这是一种倒退,并且经常会导致测试失败 价值较低 听起来不错。然而,在代码就位之前,我如何编写单元测试呢? 我真的接受了这个

我不熟悉单元测试,并且已经读了好几遍,我们应该先编写单元测试,然后再编写实际代码。到目前为止,我正在编写我的方法,然后对代码进行单元测试

如果您先编写测试…

您倾向于编写适合测试的代码。这鼓励了 “解决问题的最简单的事情”式的开发和维护 你专注于解决问题,而不是解决元问题

如果您先编写代码…

您可能会试图编写测试来适应代码。实际上 这就相当于写一个问题来符合你的答案,也就是 这是一种倒退,并且经常会导致测试失败 价值较低

听起来不错。然而,在代码就位之前,我如何编写单元测试呢? 我真的接受了这个建议吗?这是否意味着我应该准备好我的POCO类和接口,然后编写单元测试


有谁能用一个简单的例子,比如说加两个数字来解释这是怎么做到的

当您在编写代码之前首先创建测试时,您会发现创建代码更容易、更快。创建单元测试和创建一些代码以使其通过所需的综合时间与直接编写代码所需的时间大致相同。但是,如果您已经有了单元测试,那么您不需要在代码完成后创建它们,这样您现在就可以节省一些时间,以后也可以节省很多时间

创建一个单元测试有助于开发人员真正考虑需要做的事情。需求通过测试牢牢地固定下来。对于以可执行代码形式编写的规范,不能有任何误解

您将创建的代码简单明了,只实现了您想要的功能。其他开发人员可以通过浏览测试来了解如何使用此新代码。结果未定义的输入将明显不在测试套件中


这对系统设计也有好处。对某些软件系统进行单元测试通常非常困难。这些系统通常先构建代码,然后测试,通常由完全不同的团队完成。通过首先创建测试,您的设计将受到测试对您的客户有价值的一切的愿望的影响。您的设计将通过更易于测试来反映这一点。

它非常简单。红色,绿色,重构

红色表示-您的代码已完全损坏。语法高亮显示为红色,测试未通过。为什么?您还没有编写任何代码

绿色意味着——您的应用程序构建并通过测试。您已经添加了所需的代码

重构意味着清理它并确保测试通过

您可以从编写类似以下内容的测试开始:

[TestMethod]
public void Can_Create_MathClass() {
    var math = new MathClass();
    Assert.IsNotNull(math);
}
这将失败(红色)。你怎么修理它?创建类

public class MathClass {
}
就这样。它现在通过(绿色)。下一个测试:

[TestMethod]
public void Can_Add_Two_Numbers() {
    var math = new MathClass();
    var result = math.Add(1, 2);
    Assert.AreEqual(3, result);
}
这也会失败(红色)。创建
Add
方法:

public class MathClass {
    public int Add(int a, int b) {
        return a + b;
    }
}
运行测试。这将通过(绿色

重构是一个清理代码的问题。这还意味着您可以删除冗余测试。我们知道我们现在有了
MathClass
。。因此,您可以完全删除
cancreate\umathclass
测试。一旦完成了。。您已通过重构,可以继续


重要的是要记住重构步骤不仅仅意味着你的普通代码。这也意味着测试。你不能让你的测试随着时间的推移而恶化。您必须在重构步骤中包含它们。

让我们举一个稍微高级一点的例子:您想要编写一个从序列中返回最大数字的方法

首先,为要测试的方法编写一个或多个单元测试:

int[] testSeq1 = {1, 4, 8, 120, 34, 56, -1, 3, -13};

Assert.That(MaxOf(testSeq1) == 120);
再重复一些序列。还包括一个null参数、一个包含一个元素的序列和一个空序列,并决定一个空序列或null参数是否应该引发异常(如果是这种情况,请确保单元测试预期空序列会出现异常)

在此过程中,您需要确定方法的名称及其参数的类型

此时,它将无法编译

然后为该方法编写一个存根:

public int MaxOf(IEnumerable<int> sequence)
{
    return 0;
}
public int MaxOf(IEnumerable序列)
{
返回0;
}
此时它可以编译,但单元测试失败

然后实现
MaxOf()
,这样这些单元测试现在就通过了

这样做可以确保您立即关注方法的可用性,因为您尝试做的第一件事就是使用它——甚至在开始编写它之前。此时,您可能会根据使用模式决定稍微更改方法的声明


一个真实的例子将应用这种方法来使用整个类,而不仅仅是一个方法。为了简洁起见,我在上面的示例中省略了类。

在编写任何代码之前编写单元测试是可能的-Visual Studio确实具有从单元测试中编写的代码生成方法存根的功能。这样做还可以帮助理解对象需要支持的方法—有时这有助于以后的增强(如果您有一个磁盘保存,您也可以重载以保存到
,这更易于测试,并有助于在以后需要时通过网络进行假脱机)

编写测试首先迫使您在实际实现类/方法/算法之前考虑它应该做什么。这就像说“这就是我想要发生的!我以后会担心如何发生。”需要考虑的一件事是,当您已经有了一个设计,并且正在为其开发特定组件时,TDD是很好的。这通常发生在原型之后。如果你正在制作原型或进行RAD,TDD将成为阻碍。@lazyberezovsky是一个很好的例子。谢谢。我强烈建议你一旦掌握了基本知识,我的意思是