【问题标题】:Testing class with high cohesion but reasonably complex logic具有高内聚性但逻辑相当复杂的测试类
【发布时间】:2017-10-13 09:57:50
【问题描述】:

例如,我有一个类可以获取原始数据,对其进行分析并返回报告的数字。我们称之为 SomeReportDataProvider

class SomeReportDataProvider
{
    public function report(array $data)
    {
        $data = $this->prepare($data);

        $report = [];
        foreach ($data as $item) {
            if ($row->somefield == 'somevalue') {
                $report = $this->doThis($report, $row);
            } else {
                $report = $this->doThat($report, $row);
            }
        }

        // ... do something else

        $report = $this->postProcess($report);

        return $report;
    }

    protected function doThis($item)
    {
        // ... do some math, probably call some other methods
    }

    protected function doThat($item)
    {
        // ... do other math
    }

    // ... and so on
}

所以这个类实际上只做一件事,逐步处理一些报告的原始数据。它并不大,很可能是 5-10 行的 4-6 个方法。它的所有方法都紧密相关并且服务于一个目的,所以我会说它具有很高的内聚性。但是测试课程的最佳方法是什么?

如果我尝试以“测试行为,而不是实现”的心态来处理它,我应该只测试它的单一公共方法。这种方法的优点是以后可以很容易地重构类,甚至无需接触测试,而且我确信它仍然表现出完全相同的行为。但也很难通过单一方法覆盖所有情况,可能的代码路径太多。

我可以公开大多数(可能是所有)方法并单独测试它们,但随后我将打破类和算法的封装并测试它的实现细节。任何重构都会变得更加困难,并且更有可能发生不必要的行为变化。

作为最后一个选项,我可以将它分成小类,很可能有 1 或 2 个方法。但是,将高度内聚的类分解成更小的、紧密耦合的类真的是一个好主意吗?这些类做非常具体的事情并且不会在其他任何地方重用?而且它仍然会更难重构。其他开发人员可能更难以快速了解其工作原理。

【问题讨论】:

    标签: php unit-testing testing


    【解决方案1】:

    我总是尝试尽可能地拆分课程,就像您在最后一个选项中所说的那样,因为根据我的经验,这是简化测试的最佳方法,这本身就是一个非常正当的理由... 另外,我不同意你的观点——我认为这种方法更容易在未来重构,也更容易理解,而不是一个把所有东西放在一起的大类......

    查看您的代码,我可以看到一堆单独的“角色”:ReporterInterfaceReporterReportDataPreparatorThisDoerThatDoerReportPostProcessor(显然您可以找到更好的名称: )

    您可能希望将来重用其中的一些,但即使不是这种情况,而且所有这些都非常特定于报告,您也可以将它们放在单独的命名空间和文件夹中(如“报告模块”) .
    这个报告模块有一个独特的API,就是你的ReporterInterface,你系统的所有其他部分只需要关心那个接口,不管那个报告者在后台使用私有方法、其他类还是整个系统——他们只需要打电话给$reporter->report($data)...

    因此,从系统其余部分的角度来看,没有任何变化,您的报告服务仍然在一起,并且您的单元测试更易于编写和维护......

    【讨论】:

    • 那我们不会得到一个依赖太多的类吗?另外,我们应该在测试中模拟/存根所有这些吗?如果我们这样做,我们最终会得到很多模拟,安排它们需要很多时间,而不是根据很多人的最佳测试方式。如果我们不mock,那么还是有很多代码路径的问题……
    • 我想这是一个品味问题,很大程度上取决于具体情况——我个人更喜欢在一个类中使用太多的 dep 而不是太多的功能(无论如何,你可以以某种方式构建事物不是所有的部门都进入记者班,而是在其他部门)。至于模拟,我的方法通常是在测试中模拟 everything 但正在测试的类......恕我直言,这是正确 unit 测试事物的唯一方法......如果您想测试整个“模块”协同工作的真实行为,您可以添加单独的集成测试,但对于单元测试,我至少会这样做
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多