是否可以介绍仅在单元测试期间使用的方法?


12

最近,我正在开发一种工厂方法。该方法是创建一个普通对象或包装在装饰器中的对象。装饰对象可以是全部扩展StrategyClass的几种类型之一。

在测试中,我想检查返回对象的类是否符合预期。当普通对象os返回时,这很容易,但是将其包装在装饰器中怎么办?

我使用PHP编写代码,因此可以ext/Reflection用来找出一类包装对象,但是在我看来,这似乎使事情变得过于复杂,并且有点像TDD的规则。

相反,我决定引入getClassName()从StrategyClass调用时将返回对象的类名的方法。但是,当从装饰器调用时,它将返回装饰对象中相同方法返回的值。

一些代码使其更清晰:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

和一个PHPUnit测试

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

我的意思是:可以引入这样的方法,除了使单元测试更容易之外,没有其他用途吗?不知何故我觉得不对。


可能,这个问题将为您的问题提供更多的见解,programmers.stackexchange.com / questions / 231237 /…,我相信这取决于用法以及方法是否将极大地帮助您开发任何应用程序进行单元测试和调试。 。
克莱门特马克- AABA

Answers:


13

我的想法是不,您不应因为可测试性就需要执行任何操作。人们做出的许多决定都对可测试性有所帮助,而可测试性甚至可能是主要优点,但它应该是其他优点的良好设计决策。这确实意味着某些所需的属性不可测试。另一个例子是当您需要了解某个例程的效率时,例如您的Hashmap是否使用均匀分布的哈希值范围-外部接口中没有任何内容可以告诉您。

而不是思考“我是否获得了正确的策略课程”,而是思考“我所获得的课程是否执行了本规范试图测试的内容?” 当您可以测试内部管道但不必这样做时,这很好,只需测试一下旋钮,看看是否有热水或冷水即可。


+1 OP描述的是“透明盒”单元测试,而不是TDD功能测试
Steven A. Lowe11年

1
我看到的是重点,尽管当我想测试工厂方法是否在工作时,我有点不愿意添加对StrategyClass算法的测试。这种中断隔离恕我直言。我要避免的另一个原因是这些特定的类在数据库上运行,因此测试它们需要附加的模拟/存根。
Mchl 2011年

另一方面,针对这个问题:programmers.stackexchange.com/questions/86656/…当我们将“ TDD测试”与“单元测试”区分开时,这将变得非常好(尽管数据库仍然很:P)
Mchl

如果添加方法,则它们将成为与用户签订合同的一部分。最后,您将得到编码人员,这些编码人员将调用仅测试功能并根据结果进行分支。通常,我更喜欢公开尽可能少的课程。
BillThor

5

我的看法是-有时您必须对源代码进行一些重做以使其更具可测试性。这不是理想的选择,也不应该成为借口使仅用于测试的功能杂乱无章的借口,因此进行审核通常是关键。您也不想遇到代码用户突然使用测试接口功能与对象进行正常交互的情况。

我处理此问题的首选方式(我很抱歉,因为我主要使用C风格的语言编写代码,所以我无法显示如何在PHP中执行此操作)是以无法提供“测试”功能的方式提供这些功能通过对象本身暴露于外界,但是可以被派生对象访问。出于测试目的,我然后派生一个类,该类将处理与实际要测试的对象的交互,并让单元测试使用该特定对象。一个C ++示例如下所示:

生产类型类别:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

测试助手类:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

这样,至少您不必在主对象中公开“测试”类型的函数。


一个有趣的方法。我需要看看如何适应这一点
Mchl 2011年

3

几个月前,当我把刚购买的洗碗机放进去时,软管里流出了很多水,我意识到这可能是由于在出厂时已经对其进行了正确的测试。仅在装配线上进行测试的情况下,经常会在机器上看到安装孔和东西的情况并不少见。

测试很重要,如果需要的话,只需添加一些内容即可。

但是请尝试一些替代方法。您基于反射的选项并不是很糟糕。您可能需要一个受保护的虚拟访问器来访问所需的虚拟访问器,并创建一个派生类进行测试和声明。也许您可以拆分您的类并直接测试生成的叶类。将测试方法与源代码中的编译器变量一起隐藏也是一种选择(我几乎不了解PHP,不确定在PHP中是否可行)。

在您的上下文中,您可以决定不测试装饰器中的正确组成,而是测试装饰应具有的预期行为。这也许会把更多的注意力放在预期的系统行为上,而不是技术规范上(从功能的角度来看,装饰器模式会给您带来什么?)。


1

我绝对是TDD的新手,但这似乎取决于所添加的方法。据我对TDD的了解,您的测试应该在某种程度上“推动” API的创建。

可以的时候:

  • 只要该方法不会破坏封装并达到与对象职责相符的目的。

如果不正常,请执行以下操作:

  • 如果该方法似乎永远都不会有用,或者相对于其他接口方法没有意义,那么它可能是内含的或令人困惑的。到那时,这将使我对该对象的理解陷入混乱。
  • 杰里米(Jeremy)的例子:“ ...当您需要了解某个例程的效率时,例如,您的Hashmap是否使用均匀分布的哈希值范围-外部接口中没有任何内容可以告诉您。”
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.