具有一个公共方法和多个私有方法的单元测试类

本文关键字:有方法 单元 测试类 单元测试 有一个 方法 具有一 | 更新日期: 2023-09-27 18:30:13

我在理解如何处理以下内容以对类进行单元测试时遇到了一些困难。

测试中的对象是一个由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方法。然而,将方法的输出与预定义的结果进行比较是不可撤销的(无论是在空间上还是在时间上)。变化的数量太大了(它们都是角落的情况)。

一种选择是将这些私有助手方法重构为一个可以进行单元测试的独立类。然后,二进制流可以被分割成更小、更易于测试的块,但也有很多情况需要处理,比较二进制结果将挑战单元测试的快速时间。这是一个我宁愿不去的选择。

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

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

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

您将如何对此类进行单元测试?

具有一个公共方法和多个私有方法的单元测试类

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

也就是说,由于你相当反对该解决方案,如果你想做的是验证某些东西是否被调用,以及它们是否按特定顺序调用,那么你可以利用Moq的功能。

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

此外,您还可以设置一个mock来进行回调。这样做可以在测试中设置一个空字符串集合。然后,在mock设置的回调中,添加一个字符串,标识被调用到集合的函数的名称。然后,在测试完成后,将该列表与预先填充的列表进行比较,该列表包含按正确顺序进行的调用,然后执行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();
}

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

我认为所有潜在的场景都是集成测试。

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

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

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