单元测试和静态方法

问题描述 投票:44回答:3

阅读和拾掇单元测试,尝试着去理解。下面的职位 上,解释了静态函数调用的艰辛。

我不清楚这个问题。 我一直以为静态函数是类中实用函数的一种很好的舍弃方式。例如,我经常使用静态函数调用来初始化,即。

Init::loadConfig('settings.php');
Init::setErrorHandler(APP_MODE); 
Init::loggingMode(APP_MODE);

// start loading app related objects ..
$app = new App();

/ 读了这篇文章后,我现在的目标是用这个来代替... ...

$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
 // etc ...

但是,我为这个类写的几十条测试都是一样的。我什么都没改,他们还是全部通过。我是不是做错了什么?

帖子的作者说了以下几点。

静态方法的基本问题是它们是程序化的代码。我不知道如何对程序化代码进行单元测试。单元测试的前提是,我可以孤立地实例化我的应用程序的一个片段。在实例化的过程中,我用mocksfriendlies对依赖关系进行了布线,它取代了真正的依赖关系。对于程序化编程,没有什么需要 "布线 "的,因为没有对象,代码和数据是分开的。

现在,我从帖子中了解到静态方法会产生依赖关系,但没有直观地掌握为什么不能像普通方法一样方便地测试静态方法的返回值?

我将会避免使用静态方法,但我希望知道什么时候静态方法是有用的,如果有的话。从这个帖子来看,静态方法似乎和全局变量一样邪恶,应该尽量避免使用。

如果有任何关于这个主题的额外信息或链接,我们将非常感激。

php unit-testing phpunit
3个回答
53
投票

静态方法本身并不比实例方法更难测试。当一个方法--静态方法或其他方法--调用了 其他 静态方法,因为你无法隔离被测试的方法。这里是一个典型的方法例子,它可能很难测试。

public function findUser($id) {
    Assert::validIdentifier($id);
    Log::debug("Looking for user $id");  // writes to a file
    Database::connect();                 // needs user, password, database info and a database
    return Database::query(...);         // needs a user table with data
}

你想用这个方法测试什么?

  • 传递除正整数以外的任何东西都会抛出 InvalidIdentifierException.
  • Database::query() 收到正确的标识符。
  • 当找到匹配的用户时,将返回一个匹配的用户。null 当不。

这些要求很简单,但你还必须设置日志,连接数据库,加载数据等。在 Database 类应该只负责测试它是否可以连接和查询。该 Log 类应该对日志记录做同样的处理。findUser() 不应该处理这些,但它必须处理,因为它依赖于它们。

如果上面的方法对实例方法进行调用,而不是在 DatabaseLog 实例,测试可以用针对当前测试的脚本返回值来传递模拟对象。

function testFindUserReturnsNullWhenNotFound() {
    $log = $this->getMock('Log');  // ignore all logging calls
    $database = $this->getMock('Database', array('connect', 'query');
    $database->expects($this->once())->method('connect');
    $database->expects($this->once())->method('query')
             ->with('<query string>', 5)
             ->will($this->returnValue(null));
    $dao = new UserDao($log, $database);
    self::assertNull($dao->findUser(5));
}

上述测试将失败,如果 findUser() 漏叫 connect(),传递错误的值 $id (5 以上),或返回除 null. 美中不足的是,不涉及数据库,使得测试快速而稳健,这意味着它不会因为与测试无关的原因而失败,如网络故障或坏的样本数据。它允许你专注于真正重要的东西:包含在测试中的功能。findUser().


22
投票

Sebastian Bergmann同意Misko Hevery的观点,并经常引用他的话。

单元测试需要接缝,接缝是我们阻止正常代码路径执行的地方,也是我们实现被测类隔离的方法。缝隙是通过多态来实现的,我们重写implement类接口,然后对被测类进行不同的布线,以控制执行流程。对于静态方法,没有什么可以重写的。是的,静态方法很容易调用,但是如果静态方法调用了另一个静态方法,就没有办法重写被调用的方法依赖性。

静态方法的主要问题是它们引入了耦合,通常是通过将依赖关系硬编码到你的消费代码中,这使得在你的单元测试中很难用存根或mock替换它们。这违反了 开放-封闭原则依赖性反转原则,其中两个 固体原则.

你说的很对 静电被认为是有害的. 避免他们。

请查看链接以获取更多信息。

更新:请注意,虽然静电仍然被认为是有害的。自 PHPUnit 4.0 起,静态方法的存根和模拟功能被移除。


1
投票

在测试静态方法时,我没有看到任何问题(至少在非静态方法中没有问题)。

  • Mock对象是通过依赖注入传递给被测类的。
  • Mock静态方法 可以通过使用合适的自动加载器或操作的 include_path.
  • 晚期静态绑定处理方法调用同一类中的静态方法。
© www.soinside.com 2019 - 2024. All rights reserved.