单元测试类,包含一个公共和多个私有方法

时间:2013-09-03 17:49:39

标签: c# unit-testing mocking moq

我在理解如何接近以下内容时遇到了一些麻烦,以便对课程进行单元测试。

测试对象是一个由1个公共方法组成的对象,一个接受类型A的对象列表并返回一个对象B(它是二进制流)。 由于生成的二进制流的性质变得很大,因此对于测试输出来说不是一个很好的比较。 该流是使用多个私有实例辅助方法构建的。

class Foo
{
    private BinaryStream mBinaryStream;
    public Foo() {}
    public BinaryStream Bar(List<Object> objects) {
        // perform magic to build and return the binary stream;
        // using several private instance helper methods.
        Magic(objects);
        MoreMagic(objects);
    }
    private void Magic(List<Object> objects) { /* work on mBinaryStream */ }
    private void MoreMagic(List<Object> objects) { /* work on mBinaryStream */ }
};

现在我知道我需要测试类的行为,因此需要测试Bar方法。 但是,将方法的输出与预定义结果进行比较是可撤消的(空间和时间方面)。 变化的数量太大(它们是极端情况)。

一个选择是将这些私有帮助器方法重构为(a)可以进行单元测试的单独类。然后可以将二进制流切割成更小的更好的可测试块,但是这里需要处理许多情况并且比较二进制结果将违反单元测试的快速时间。这是一个我宁愿不去的选择。

另一种选择是创建一个定义所有这些私有方法的接口,以便验证(使用模拟)是否调用了这些方法。然而,这意味着这些方法必须具有公共可见性,这也不是很好。验证方法调用可能就足以测试了。

另一种选择是从类继承(使私有保护)并尝试以这种方式进行测试。

我已经阅读了围绕此类问题的大部分主题,但它们似乎处理了可测试的良好结果。这与此挑战不同。

你将如何对这类课程进行单元测试?

3 个答案:

答案 0 :(得分:1)

从SOLID的角度来看,您的第一个选项(将功能提取到单独的类中)实际上是“正确的”选择。单元测试的主要要点之一(以及扩展的TDD)是促进小型单一责任类的创建。所以,这是我的主要建议。

那就是说,因为你反对那个解决方案,如果你想要做的就是验证某些事情被调用,并且按照特定的顺序调用它们,那么你可以利用Moq的功能。

首先,让BinaryStream成为可以模拟的注入项目。然后设置将针对该模拟进行的各种调用,然后对其进行mockStream.VerifyAll()调用 - 这将验证您为该模拟设置的所有内容都已被调用。

此外,您还可以设置模拟以进行回调。您可以使用此功能在测试中设置空字符串集合。然后,在模拟设置的回调中,添加一个字符串,标识调用该集合的函数的名称。然后在测试完成后,将该列表与包含您希望以正确顺序进行的调用的预填充列表进行比较,并执行EqualTo Assert。像这样:

public void MyTest()
{
    var expectedList = new List<string> { "SomeFunction", "AnotherFunction", ... };
    var actualList = new List<string>();
    mockStream.Setup(x => x.SomeFunction()).Callback(actualList.Add("SomeFunction"));
    ...
    systemUnderTest.Bar(...);
    Assert.That(actualList, Is.EqualTo(expectedList));
    mockStream.VerifyAll();
}

答案 1 :(得分:0)

嗯,你是如何处理私人方法的。测试流以获得正确的输出。就个人而言,我会使用一组非常有限的输入数据,并在单元测试中简单地运用代码。

我将所有可能的情景视为集成测试。

所以有一个带有输入和预期输出的文件(比如说xml)。运行它,使用输入调用方法,并将实际输出与预期的报告差异进行比较。因此,您可以将此作为签入的一部分,或者在部署到UAT或其他部分之前执行此操作。

答案 2 :(得分:0)

不要尝试测试私有方法 - 从消费者的角度来看它们不存在。将它们视为命名代码区域,只是为了使Bar方法更具可读性。您始终可以重构Bar方法 - 提取其他私有方法,重命名它们,甚至可以返回Bar。这是实现细节,不影响类行为。而班级行为正是你应该测试的。

那么,你班级的行为是什么?您班上的消费者期望是什么?这就是你应该在测试中定义和写下的内容(理想情况是在你通过之前)。从琐碎的情况开始。如果对象列表为空怎么办?定义行为,编写测试。如果列表包含单个对象怎么办?如果你的班级行为非常复杂,那么你的班级可能会做太多事情。尝试简化它并将一些“魔法”移动到依赖项。