创建和运行测试

时间:2012-02-16 20:41:46

标签: c# .net visual-studio-2010 unit-testing

我使用单元测试向导在我的解决方案中创建了一个单元测试:我选择了该类及其所有方法和属性,然后向导创建了一个新文件,其中包含每个类的测试方法测试方法。

显然,测试应该是可靠的,也就是说,只有当模块不能按预期工作而不是因为测试结构不合理时,测试才会失败。所以第一个问题是:应该遵循什么指导方针来构建测试?

在创建我的第一个测试时,我正在尝试编写简单的测试用例,从而减少出错的可能性:例如,要测试一个集合,你需要为它添加一些元素,所以我创建了一个像在我的测试单元内:

// This is not a test method, but a support method for the test methods.
private void AddSomeElements(ICollection c, int count)
{
    Random rand = new Random();
    ...
    for(int i=0; i < count; i++)
        c.Add(...);
}

因此,例如,Count属性的测试可能是:

/// <summary>
///A test for Count
///</summary>
[TestMethod()]
public void CountTest()
{
    HostsCache target = new HostsCache();
    AddSomeElements(target, 100);
    int actual;
    actual = target.Count;
    Assert.AreEqual<int>(100, actual);
 }

这种做法是否正确? 假设Add方法返回一个值(例如bool值):在这种情况下,上面的私有方法是否也会返回此值?

5 个答案:

答案 0 :(得分:5)

我在编写测试时遇到的最大错误之一是忘记将测试视为一流代码。也就是说,将它们考虑在内,以便随着应用程序(以及扩展,测试)的发展,它们易于维护和修改。

我看到的第二大错误是在每个测试中覆盖了太多代码。理想情况下,每次测试都应该只测试几行代码。绝对没有比单个方法中包含的代码更多的代码。

所以,如果你的例子中的AddSomeElements返回某些东西是有意义的,那么让它返回一些东西。如果没有,请不要。

记住两件事:

  1. 您的测试是一流的代码
  2. 每项测试只应测试一小部分功能。

答案 1 :(得分:3)

您应该在添加和删除元素时执行一些简单的测试。

辅助函数没有任何问题,它添加了一定数量的随机元素,然后将Count属性验证为您的测试。我不打算从它返回任何其他值 - 如果Add失败返回false,那么辅助函数应抛出一个异常,以便清楚发生错误的位置。

布尔返回值应单独测试。

例如 1)创建一个集合并添加一个元素,然后断言该集合确实包含一个元素。 2)创建一个集合并添加一个元素,断言你输入的元素与你得到的元素相同 3)如果add方法返回一个bool,那么检查添加返回true和添加返回false的方案应该是另外两个测试用例。

答案 2 :(得分:3)

这是一个相当开放的问题,但这里有一些自以为是的建议可以帮助你。

每个测试通常有三个部分。

a)排列 - 设置值以发送到您要测试的方法,创建可验证且可重现的环境。
  b)法案 - 致电您的方法或任何您的测试   c)断言 - 确保你的方法按照预期的方式进行,这意味着它返回了正确的值或产生了正确的副作用。

作为一个品味问题,我提出了三条评论来描述这些部分//安排//行动//断言

如果您的方法未设置为“可测试”,则排列有时会很棘手。开始研究依赖注入的丰富信息。一旦你使你的东西可测试,有一些工具,如Moq,或Typemock,或Rhino Mocks可以提供帮助。

另一方面,我看到你有一种方法可以将随机数据加载到集合中。测试中很少有任何随机性。如果没有时间改变,你不希望你的测试通过一天并且失败。使用您选择的常量填充数据并保持这种方式。要测试不同的数据,请加载不同的数据。不要让测试为你做。

我刚刚提到了几点。有很多关于如何测试代码的信息。最好的方法就是开始编写测试,并随时学习。测试总比没有测试好,好的测试比不好的测试好。

答案 3 :(得分:3)

规则1:不要试图煮沸海洋。一次只测试一小部分代码。对于单元测试,尝试最小化外部依赖性和交互。这包括不花时间验证核心操作系统是否正常工作,以及.NET框架提供的核心类。测试您的代码是否正确地与这些外部事物进行交互是很好的,但请记住,您的重点是测试您编写的代码,而不是测试代码使用的其他所有代码。

确保所有部件协同工作是集成测试的工作,这是一种与单元测试完全不同的测试方案。集成测试通常需要不同的工具和不同的单元测试策略,最终与单元测试有不同的目标。

如果您正在编写自己的标准ICollection接口实现,那么您应该对您的集合实现进行单元测试。否则,您不需要测试在.NET提供的库存集合类上调用ICollection.Add()就可以执行它应该执行的操作。

如果您的类在内部使用集合并公开操作该集合内容的方法,则一种方法是仅使用类的公共方法来使类更改其内部状态,并仅使用公共方法看到那个动作的效果。

然而,在许多情况下,内部状态可能无法通过公共方法完全暴露。你可以戳对象,但你无法真正看到里面发生的事情。抽象对于系统设计是有益的,因为它隐藏了消费者的实现细节,因此可以根据需要在将来更改细节,同时保持公共接口和语义契约相同。隐藏内部细节有利于系统弹性和使用寿命,但会给测试带来障碍。

在这种情况下,为类启用某种钩子或接口以允许测试看一下内部状态是很有用的。这确实使您的测试更紧密地绑定到实现细节(更改代码,您也必须更改测试),但它也使您的测试能够对公共操作是否具有预期效果进行更详细的评估关于对象的状态,特别是当对象故意模糊内部状态时。

此类内部访问挂钩的一个示例是.NET中的InternalsVisibleTo属性。您在生产程序集中声明,您的测试程序集可以访问以“内部”可见性声明的类成员。您可以自动声明为私有的东西可以被轻推到内部,以使其对您的单元测试可见。数据仍然受到普通客户的保护。

您应该查看的另一个术语是在测试中使用“模拟”类。模拟是一个假类,它提供了被测试代码所需的接口或类型的最小实现,并用于隔离您感兴趣的代码,以及您当前不感兴趣的外部代码。

例如,如果您正在测试的代码调用Web服务,则很难完全测试Web服务调用失败的所有方式 - 超时,连接被拒绝,DNS解析失败等。交换模拟Web服务接口允许您的测试套件操纵您的代码所依赖的数据提供程序。您不是在测试数据提供程序,而是要测试代码如何响应提供程序返回的错误,错误数据和良好数据。因此,在测试套件中模拟提供程序并自己控制数据。

答案 4 :(得分:2)

我最初误解了你的问题,道歉。

创建一个TestHelpers类或项目以包含可重用的代码以帮助您编写测试是完全合理的。例如,如果您正在运行多组测试数据,则可以创建一些方法来获取GetSampleTestDataForScenarioX,其中“ScenarioX”是您的方案的描述性名称。

单元测试通常应该包含很少的分支或循环方式。我遵循安排/行动/断言测试模型,在那里你开始安排测试(实例化对象,设置测试条件等),然后根据测试数据,然后断言我的条件满足。