单元测试和静态方法

2022-08-30 10:53:59

阅读并掌握单元测试,试图理解下面的帖子,该帖子解释了静态函数调用的困难。

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

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

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

该帖子的作者声明如下:

静态方法的基本问题是它们是过程代码。我不知道如何对过程代码进行单元测试。单元测试假定我可以单独实例化应用程序的一部分。在实例化期间,我将依赖关系与模拟/友谊赛连接起来,以取代真正的依赖关系。使用过程编程,没有什么可以“连接”的,因为没有对象,代码和数据是分开的。

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

我将避免使用静态方法,但我希望了解静态方法何时有用,如果有的话。从这篇文章来看,静态方法似乎和全局变量一样邪恶,应该尽可能避免。

有关该主题的任何其他信息或链接将不胜感激。


答案 1

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

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

这些要求很简单,但您还必须设置日志记录、连接到数据库、加载数据等。该类应全权负责测试它是否可以连接和查询。该类应对日志记录执行相同的操作。 不应该处理任何这些,但它必须,因为它取决于他们。DatabaseLogfindUser()

相反,如果上述方法调用了 on 和 instances 上的实例方法,则测试可以使用特定于手头测试的脚本返回值传入模拟对象。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()$id5nullfindUser()


答案 2

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

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

静态方法的主要问题是它们引入了耦合,通常是通过将依赖项硬编码到使用代码中,使得很难在单元测试中用存根或模拟替换它们。这违反了开放/封闭原则依赖反转原则,这是 SOLID 原则中的两个。

你是绝对正确的,静态被认为是有害的。避免它们。

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

更新:请注意,虽然静态仍然被认为是有害的,但从PHPUnit 4.0开始,存根和模拟静态方法的功能已被删除。


推荐