2

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

被测对象是一个由 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)可以进行单元测试的单独类。然后可以将二进制流切成更小更好的可测试块,但这里还有很多情况需要处理,并且比较二进制结果将违背单元测试的快速时间。这是我宁愿不选择的选择。

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

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

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

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

4

3 回答 3

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();
}
于 2013-09-03T18:03:54.943 回答
0

好吧,您掌握了如何处理私有方法。测试流的正确输出。我个人会使用一组非常有限的输入数据,并在单元测试中简单地练习代码。

我将所有潜在场景视为集成测试。

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

于 2013-09-03T17:58:56.227 回答
0

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

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

于 2013-09-03T18:12:18.877 回答