单元测试和静态方法

时间:2011-05-11 08:00:00

标签: php unit-testing phpunit

阅读并接受单元测试,尝试理解the following post,这解释了静态函数调用的艰辛。

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

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 ...

但是,我为这堂课写的几十个测试是一样的。我什么都没改变,他们仍然都过去了。我做错了吗?

该帖子的作者说明如下:

  

静态方法的基本问题是它们是程序代码。我不知道如何对程序代码进行单元测试。单元测试假设我可以单独实例化我的应用程序。在实例化期间,我使用mocks / friendlies连接依赖项,这取代了真正的依赖项。通过程序编程,没有任何东西可以“连线”,因为没有对象,代码和数据是分开的。

现在,我从帖子中了解到静态方法创建了依赖关系,但是没有直观地理解为什么人们不能像常规方法那样容易地测试静态方法的返回值?

我将避免使用静态方法,但我想知道WHEN静态方法是否有用,如果有的话。从这篇文章来看,静态方法似乎与全局变量一样邪恶,应尽可能避免。

非常感谢有关该主题的任何其他信息或链接。

3 个答案:

答案 0 :(得分:51)

静态方法本身并不比实例方法更难测试。当方法(静态或其他方法)调用其他静态方法时会出现问题,因为您无法隔离正在测试的方法。以下是一个难以测试的典型示例方法:

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(),传递$id5以上的错误值)或返回{{1}以外的任何内容,则上述测试将失败}}。美妙之处在于不涉及数据库,使测试快速而稳健,这意味着它不会因与网络故障或样本数据不良等测试无关的原因而失败。它允许您专注于真正重要的事项:null中包含的功能。

答案 1 :(得分:19)

Sebastian Bergmann同意Misko Hevery并经常引用他的话:

  

单元测试需要接缝,接缝是我们阻止正常代码路径执行的地方,也是我们如何实现被测试类的隔离。接缝通过多态实现,我们覆盖/实现类/接口,然后以不同方式连接测试类以控制执行流。使用静态方法无需覆盖。是的,静态方法很容易调用,但如果静态方法调用另一个静态方法,则无法覆盖被调用的方法依赖项。

静态方法的主要问题是它们引入耦合,通常是通过将依赖项硬编码到消耗代码中,使得在单元测试中使用存根或模拟替换它们变得很困难。这违反了Open/Closed PrincipleDependency Inversion PrincipleSOLID principles中的两个。

你绝对正确statics are considered harmful。避免它们。

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

更新:请注意,虽然仍然认为静态有害,the capability to stub and mock static methods has been removed as of PHPUnit 4.0

答案 2 :(得分:1)

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

  • 使用依赖注入将模拟对象传递给测试中的类。
  • 模拟静态方法可以使用合适的自动加载器或操纵include_path传递给测试中的类。
  • 后期静态绑定处理在同一个类中调用静态方法的方法。
相关问题