Php 具有高内聚性但逻辑相当复杂的测试类
例如,我有一个类,它获取原始数据,对其进行分析,并为报表返回数字。让我们称之为某个ReportDataProviderPhp 具有高内聚性但逻辑相当复杂的测试类,php,unit-testing,testing,Php,Unit Testing,Testing,例如,我有一个类,它获取原始数据,对其进行分析,并为报表返回数字。让我们称之为某个ReportDataProvider class SomeReportDataProvider { public function report(array $data) { $data = $this->prepare($data); $report = []; foreach ($data as $item) { i
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个方法。但是,将高内聚性类划分为更小的、紧密耦合的类,这些类做非常具体的事情,并且不在其他任何地方重用,这真的是一个好主意吗?而且,重构仍然会更加困难。对于其他开发人员来说,很难快速全面了解它的工作原理。我总是尝试尽可能多地拆分类,就像您在上一个选项中所说的那样,因为根据我的经验,这是简化测试的最佳方法,这本身就是一个非常有效的理由……
另外,我不同意你的观点——我认为这种方法使将来的重构更容易,也更容易理解,而不是一个包含所有东西的大型类 查看您的代码,我可以看到一组单独的“角色”:
ReporterInterface
,Reporter
,ReportDataPreparator
,ThisDoer
,ThatDoer
,ReportPostProcessor
(显然,您可以找到更好的名称:)
您可能希望在将来重用其中一些,但即使不是这样,而且所有这些都非常特定于报表,您也可以将它们放在单独的命名空间和文件夹中(如“报表模块”)。此报告模块有一个独特的API,即您的
ReporterInterface
,系统的所有其余部分只需关心该接口,而不管该报告程序是在后台使用私有方法、其他类还是整个系统-他们只需调用$reporter->report($data)
因此,从系统其余部分的角度来看,没有任何变化,您的报告服务仍然在一起,您的单元测试更容易编写和维护…那么,我们会得到一个依赖性太多的类吗?此外,我们是否应该在测试中模拟/存根所有这些文件?如果我们这样做,我们最终会得到很多模拟,安排它们需要很多时间,而根据很多人的说法,这并不是最好的测试方式。如果我们不模仿,那么仍然存在许多代码路径的问题……我想这是一个品味的问题,很大程度上取决于情况——就我个人而言,我更喜欢太多的DEP,而不是单个类中太多的功能(无论如何,你可以用一种不是所有的DEP都进入reporter类的方式来构建东西,而是在其他DEP中)。至于模拟,我的方法通常是模拟测试中的所有内容,但被测试的类。。。这是正确进行单元测试的唯一方法。。。如果您想测试整个“模块”协同工作的真实行为,您可以添加单独的集成测试,但对于单元测试,至少我是这么做的