我有一个简单的PHP类,它封装了对数据库的访问,以检索用户并希望对其进行单元测试
要测试的类:
class UserTable {
protected $tableGateway;
public function __construct('Zend'Db'TableGateway'TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
public function getUserWithId($id) {
return $this->tableGateway->select(['id' => $id])->current();
}
}
单元测试:
class UserTableTest extends 'PHPUnit_Framework_TestCase {
public function testGetUserWithIdReturnsCorrectUser() {
$user = new User();
$resultSet = new ResultSet();
$resultSet->initialize([$user]);
$mockTableGateway = $this->getMock(''Zend'Db'TableGateway'TableGateway', ['select'], [], '', false);
$mockTableGateway->expects($this->once())->method('select')->with(['id' => 1])->willReturn($resultSet);
$userTable = new UserTable($mockTableGateway);
$this->assertEquals($user, $userTable->getUserWithId(1));
}
}
然而,现在如果我后来决定改变使用表网关的方式(例如使用select(['id = ?' => $id]
),单元测试将失败。这将单元测试绑定到应该避免的getUserWithId($id)
的实现细节。
防止单元测试依赖于实现细节的最佳实践是什么?建立一个单元测试可以运行的实际测试数据库(这也会大大降低测试的执行速度)值得吗?或者有更好的方法来模拟表网关吗?
不要模拟您不拥有的代码!*对于使用数据库的类,必须编写集成测试。好的方面是,这将迫使您将DB访问与其他逻辑分离。
*这是"成长中的面向对象软件,以测试为指导"一书中的实际建议,由我自己为使用Doctrine的实体管理器的代码编写测试的经验支持