我正在重构一个类,同时为它编写单元测试。有一种情况是,我的一个方法完全调用了另一个对象的方法,该方法被注入到我正在测试的这个类中。
所以我必须模拟我注入到类中的对象。
现在,问题是,为这种特殊的方法编写单元测试值得吗?编写单元测试似乎很奇怪,它所做的一切都是调用其他对象的方法,而该对象本身必须被嘲笑,那么我为什么要测试这个方法呢?
测试的目的难道不是检查一个方法的功能是否如预期那样工作吗?如果是,那么当我嘲笑它所拥有的一切,而这个特定的方法已经没有什么可测试的了,那么我为什么要测试呢?
我真的很困惑!
我一直坚持的方法是这样的(用于自定义会话处理):
public function write($sessionId, $sessionData)
{
$sth = $this->databaseConnection->prepare("INSERT INTO `{$this->DBTableName}` (`session_id`,`session_name`,`session_data`) VALUES(:session_id, :session_name, :session_data) ON DUPLICATE KEY UPDATE `session_data`=:session_data;");
$sth->bindValue(':session_id', $sessionId);
$sth->bindValue(':session_name', $this->sessionName);
$sth->bindValue(':session_data', $sessionData);
return $sth->execute();
}
这也是这段代码的链接:http://pastebin.com/1FBeU6mb
顺便说一句,我刚开始为我的课程编写测试,我是测试领域的初学者,缺乏经验。
提前谢谢。
我对php不是很熟悉,但看起来你只是在构建和执行一个数据库查询,对吧?
除非稍后在这个方法和数据库之间还有其他我看不到的层,否则这里真的没有什么值得嘲笑的地方。所以你是对的,在这里嘲笑并不能给你带来很多价值。从某种意义上说,测试这种方法的价值是有限的,因为你实际上只是在测试数据库层,通常我们可以假设它已经是正确和稳定的,因此不需要测试。
一般来说,mocking的价值在于,它允许您通过假设其他方法正在做什么来简化测试,并且允许您不必间接测试这些其他方法。
在选择测试write方法时,您所测试的是您有正确的步骤来返回正确的结果。就是这样。我不熟悉php mocking框架,但我知道对于其他语言的框架,你可以设置所谓的期望值,它可以让你指定某个方法将在具有特定参数的某个对象上调用。您甚至可以经常指定执行它们的顺序。这里的要点是,您正在测试对象发送的传出消息,而不是这些消息的返回值。
这取决于您来决定这与该测试所需的维护是否有价值。
您正在测试是否将正确的参数传递给准备好的语句。此外,您还应该测试write
方法是否返回准备好的统计结果。
如果不对此进行测试,应用程序可能会以多种方式中断。
- 重命名或删除方法参数(
$sessionId
、$sessionData
) - 重命名您的财产
$this->sessionName
- 正在删除其中一个
bindValue
调用 - 绑定别名命名错误。它们应该与您的查询相匹配
- 返回除
execute()
的结果之外的其他内容
等等。等
所以,是的,这是一个很好的实践来测试这一点。
假设您的示例描述了一个SessionHandler
类,它看起来类似于:
class SessionHandler
{
public function __construct($sessionTableName, $sessionName, 'PDO $databaseConnection)
{
$this->DBTableName = $sessionTableName;
$this->sessionName = $sessionName;
$this->databaseConnection = $databaseConnection;
}
// among others, your method write($sessionId, $sessionData) follows
}
这可以涵盖方法write()
:
public function testWriteInsertsOrUpdatesSessionData()
{
/**
* initialize a few explaining variables which we can refer to
* later when arranging test doubles and eventually act
*/
$sessionTableName = 'sessions';
$sessionName = 'foobarbaz';
$sessionId = 'foo';
$sessionData = serialize([
'bar' => 'baz',
]);
$executed = true;
/**
* create a test double for the statement that we expect to be returned
* from `PDO::prepare()`
*/
$statement = $this->createMock('PDOStatement::class);
/**
* set up expectations towards which methods should be invoked
* on the statement, specifying their order
*/
$statement
->expects($this->at(0))
->method('bindValue')
->with(
$this->identicalTo(':session_id'),
$this->identicalTo(sessionId)
);
$statement
->expects($this->at(1))
->method('bindValue')
->with(
$this->identicalTo(':session_name'),
$this->identicalTo($sessionName)
);
$statement
->expects($this->at(2))
->method('bindValue')
->with(
$this->identicalTo(':session_data'),
$this->identicalTo(sessionData)
);
$statement
->expects($this->at(3))
->method('execute')
->willReturn($executed);
/**
* create a test double for the database connection we inject
* into SessionHandler during construction
*/
$databaseConnection = $this->createMock('PDO::class);
$databaseConnection
->expects($this->once())
->method('prepare')
->with($this->identicalTo(sprintf(
'INSERT INTO `%s` (`session_id`,`session_name`,`session_data`) VALUES(:session_id, :session_name, :session_data) ON DUPLICATE KEY UPDATE `session_data`=:session_data;',
$sessionTableName
)))
->willReturn($statement);
$sessionHandler = new SessionHandler(
$sessionTableName,
$sessionName,
$databaseConnection
);
$result = $sessionHandler->write(
$sessionId,
$sessionData
);
$this->assertSame($executed, $result);
}
参考:
- https://phpunit.de/manual/current/en/test-doubles.html