测试具有高内聚性但逻辑相当复杂的类



例如,我有一个类,它获取原始数据,对其进行分析,并为报告返回数字。让我们称之为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
}

因此,该类实际上只做一件事,即逐步处理某些报表的原始数据。它不大,很可能有4-6种方法,有5-10行。它的所有方法都是紧密相关的,并且有一个目的,所以我想说它有很高的内聚性。但是测试这门课的最好方法是什么?

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

我可以公开大多数(可能是所有)方法,并对它们进行隔离测试,但随后我会破坏类和算法的封装,并测试其实现细节。任何重构都会更困难,更可能对行为进行不必要的更改。

作为最后一种选择,我可以将它分解为小类,很可能有1或2个方法。但是,把高度内聚的类分解成更小的、紧密耦合的类真的是个好主意吗?这些类做着非常具体的事情,不会在其他地方重复使用?此外,重构仍然会更加困难。对于其他开发人员来说,可能更难快速全面了解它的工作原理。

我总是尽可能多地拆分类,就像你在最后一个选项中所说的那样,因为根据我的经验,这是简化测试的最佳方式,这本身就是一个非常合理的原因
另外,我不同意你的看法-我认为这种方法使将来重构更容易,更容易理解,而不是一个所有东西都在一起的大类。。。

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

您可能希望在将来重用其中的一些,但即使不是这样,并且所有这些都非常特定于报告,您也可以将它们放在一个单独的命名空间和文件夹中(就像"报告模块")
此报告模块有一个唯一的API,即您的ReporterInterface,而系统的所有其他部分只需要关心该接口,无论该报告程序是使用私有方法、其他类还是后台的整个系统-它们只需要调用$reporter->report($data)。。。

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

最新更新