如何对不适合拆分为新类的私有方法进行单元测试?

3

大致而言,我有一个实现了线程的类,只有一个公共方法run。该方法进入一个循环,充当分发器以逐个处理网络消息,如下所示:

class WorkerThread {

  public:

    void run() {
      while (!b_shutdown) {
        message = getNextMessage();
        switch(message.type) {
          case WRITE:
            write();
          case READ:
            read();
          // ...
          // more cases to handle
        }
      }
    }


  private:

    void write() { /* logic to test */ }
    void read() { /* logic to test */ }
    // more private methods with logic that needs testing

    // some member variables
};

因此,重点是我真的不想:
  1. 将私有方法提取到另一个类中,因为从语义上讲,它们是 WorkerThread 的一部分。
  2. 将这些方法公开,因为它们在类外部没有被使用。
  3. 跳过测试,因为这些方法实现了主要的逻辑。
但是,私有方法如何优雅地进行测试呢?
注意:
  1. 可能需要更多的公共方法来处理线程的启动和终止,但这不是本文关注的问题。
  2. 我认为这个问题不仅限于 c++,所以我也打了更流行的静态类型语言 Java 的标签,以引起更多的关注:P

1
在构建单元测试时,您可以将部分单元测试框架设置为被测试类的友元。 - Richard Critten
1
所以我还给它打上了更受欢迎的静态类型Java标签,以便引起更多关注。通常也会有更多的踩。 - Jarod42
“因为从语义上讲,它们是WorkerThread的一部分”,对我来说似乎不是这样,WorkerThread只是一个消息分发器。 - Jarod42
@RichardCritten 是的,但我的意思是问一个关于软件设计的一般性问题,而不是关于C ++特定的问题。 - wlnirvana
使用 TDD 可以消除这个问题,因为它可以防止你编写无法测试的私有方法。最终你会通过调用它们的公共接口来测试它们。使用 TDD,你很可能会在编写测试后重构公共接口,将代码移动到可证明的方法中。 - MikeJ
2个回答

10

简单来说:不要这样做。只测试公共接口,不测试其他内容。最终,这是唯一重要的事情。

如果您想确定它是否正常工作,则应通过可以进行模拟测试的IoInterface访问读/写功能。


这有一定道理,但也暗示着在面向对象编程中,所有私有方法都可以被提取出来成为另一个类(比如 IoInterface)的公共方法,并通过该类的私有实例进行访问。这种方式合理吗? - wlnirvana
1
很好的简短/精确回答。你留下了一些细节,让我可以写另一个答案;感谢你;-) - GhostCat
@wlnirvana:每个类应该有一个单一的变更原因或单一的职责。WorkerThread似乎负责多线程,不应该负责读写操作。请参考GhostCat的出色答案以获取详细信息。 - mattideluxe

3

引用问题:

我真的不想将私有方法提取到另一个类中,因为从语义上讲,它们是这个WorkerThread的一部分。

这是错误的。您正在违反单一职责原则

提供“多线程支持”和“执行实际工作”是两个不同的事情。您不应该强制执行“执行实际工作”是一个私有实现细节。

在这种意义上:您为自己设定的规则实际上导致您编写了难以测试的代码。因此,与其“解决”糟糕的设计症状-最好退后一步并修复您的设计。

换句话说:

  • 重新设计您的工作线程,使其能够运行任何类型的工作
  • 分离关注点,并将“这种工作”放入专门的类中
通过这样做,不仅可以改善你的设计,而且还可以使测试变得更加容易:因为现在,你可以单元测试“执行实际工作”而没有任何多线程复杂性。你只需断言顺序完成时的结果即可。然后你测试多线程部分 - 单独测试;而不用担心实际工作。最后,一切都很好地结合在一起。
与其让你担心如何测试内部实现细节。而这是不必要的; 另一个答案在这方面是完全正确的!

“工作线程能够运行任何类型的工作” 真的让我受益匪浅。 - wlnirvana
顺便问一下,通常认为哪个是更好的设计,并解释为什么——拥有一个IoInterface的私有实例或从工作线程和IoInterface都进行多重继承? - wlnirvana
那部分可能“太主观”了。我的看法是:我更喜欢组合而不是继承...继承总是导致非常紧密的耦合;因此,就我个人而言,我尽量避免使用它。你不是为了节省编码工作而继承;而是因为某个类A实际上是某个B。意思是:面向对象编程是关于“A是一个B”...因为你的模型告诉你这是正确的事情。 - GhostCat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接