单元测试和静态方法

ste*_*lin 43 php phpunit unit-testing

阅读并接受单元测试,试图理解下面的帖子,解释了静态函数调用的艰辛.

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

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

// start loading app related objects ..
$app = new App();
Run Code Online (Sandbox Code Playgroud)

//阅读帖子后,我现在的目标是......

$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
 // etc ...
Run Code Online (Sandbox Code Playgroud)

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

该职位的作者陈述如下:

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

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

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

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

Dav*_*ess 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
}
Run Code Online (Sandbox Code Playgroud)

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

  • 传递除正整数以外的任何内容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));
}
Run Code Online (Sandbox Code Playgroud)

如果findUser()忽略调用connect(),传递错误的值$id(5上面),或者返回除了之外的任何内容,则上述测试将失败null.美妙之处在于不涉及数据库,使测试快速而稳健,这意味着它不会因与网络故障或不良样本数据等测试无关的原因而失败.它允许您专注于真正重要的事项:其中包含的功能findUser().

  • @stefgosselin - 不是很讽刺,你不觉得吗?:) PHPUnit的断言是在`PHPUnit_Framework_Assert`中定义的静态方法,`TestCase`继承.虽然PHP允许你使用` - >`来调用静态方法,但是`self ::`更短*且*更正确. (2认同)

Gor*_*don 21

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

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

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

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

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

更新:请注意,虽然静态仍被视为有害,但自PHPUnit 4.0起,已删除存根和模拟静态方法的功能