这是第一次通过PHPunit创建单元测试来测试我的Web应用程序的业务模型。我很困惑嘲笑用来坚持实体的我的类。这是newUser()的功能。
class AccountBM extends AbstractBusinessModel {
public function newUser(Account $account) {
$salt = StringHelper::generateRandomString ( "10" );
$account->setUsername ( $account->getEmail () );
$invitation_code = hash ( 'md5', $account->getEmail () );
$account->setInviationCode ( $invitation_code );
$account->setPassword ( $this->encoder->encodePassword ( $account->getPassword (), $salt ) );
$account->setSalt ( $salt );
$this->persistEntity ( $account );
return $account;
}
}
然后,我尝试为这个功能testNewUser()
创建一个测试单元 public function testNewUser() {
$account = $this->newAccountEntity ();
$account_bm =$this->getMockBuilder('AccountBM')
->setMethods(array('newUser'))
->disableOriginalConstructor()
->getMock();
$account=$account_bm->newUser($account);
// compare setter value with saved entity
$this->assertEquals ( 'test@test.com', $account->getEmail () );
$this->assertEquals ( '123456789', $account->getPhone () );
$this->assertEquals ( '987654321', $account->getMobilePhone () );
$this->assertEquals ( 'Mr', $account->getTitle () );
$this->assertEquals ( 'test', $account->getFirstName () );
$this->assertEquals ( 'test', $account->getLastName () );
$this->assertEquals ( 'male', $account->getGender () );
$this->assertEquals ( '1', $account->getCompanyLogo () );
$this->assertEquals ( AccountType::IDS, $account->getUserType () );
$this->assertEquals ( RegionType::NCN, $account->getRegion () );
$this->assertEquals ( hash ( 'md5', $account->getEmail () ), $account->getInviationCode () );
$this->assertEquals ( hash ( 'sha256', $account->getSalt () . "test" ), $account->getPassword () );
}
public function newAccountEntity() {
$account = new Account ();
// set account
$account->setEmail ( "test@test.com" );
$account->setPassword ( "test" );
$account->setPhone ( "123456789" );
$account->setMobilePhone ( "987654321" );
$account->setTitle ( "Mr" );
$account->setFirstName ( "test" );
$account->setLastName ( "test" );
$account->setGender ( "male" );
$account->setCompanyLogo ( "1" );
$account->setUserType ( AccountType::IDS );
$account->setRegion ( RegionType::NCN );
return $account;
}
但它似乎不会嘲笑“newUser”这个方法,而不是我的实体。测试代码有什么问题吗?感谢。
答案 0 :(得分:1)
回答您的问题:
模拟方法不是您要测试的方法,而是您不想要测试的方法。在你的代码中,你正在创建一个你想要测试的类的模拟对象(虽然你通常模拟你不想测试的依赖类,但是完全没问题)并告诉模拟对象,newUser()
是一个方法你想嘲笑
相反,您想要测试newUser()
,并且可能想要测试persistEntity()
:
$account_bm =$this->getMockBuilder('AccountBM')
->setMethods(array('persistEntity'))
->disableOriginalConstructor()
->getMock();
$account=$account_bm->newUser($account);
您可能还想查看有关模拟对象的PHPUnits文档: https://phpunit.de/manual/current/en/test-doubles.html
其次,使用PHPUnit进行开发的一些技巧:
在你的测试中,你有很多断言。最好的情况是每个测试只有一个断言,PHPUnit实际上可以很好地帮助你,因为它能够比较几乎所有东西。因此,不是断言对象的每个值,而是可以用另一个声明一个对象,如果这些对象不相等,PHPUnit将显示差异。
另外,您甚至可能想在实体中遗漏一些您不需要的信息。您的数据库可能需要名字和姓氏,但您的方法不需要,因此您的测试也不需要。您不必编写那么多代码,而且更具可读性。
但这不是全部,断言也可以用模拟方法完成。
尝试这样的测试:
public function testNewUser() {
// this is the entity for the method
$account = new Account();
$account->setEmail ( "test@test.com" );
$account->setPassword ( "test" );
// this is how the entity should look like when it's done
$expectedAccount = new Account ();
$expectedAccount ->setEmail ( "test@test.com" );
$expectedAccount ->setInviationCode ( md5("test@test.com") );
// changes needed, how does your encoder create the password hash?
// Or even better: Also mock your encoder, you don't want to test it here
$expectedAccount ->setPassword ( "test" );
// As this is a random value, you might want to mock it as well
// mock a static call
$expectedAccount ->setSalt ( "10" );
/** @var AccountBM|\PHPUnit_Framework_MockObject_MockObject $account_bm */
$account_bm = $this->getMockBuilder('AccountBM')
->setMethods(array('persistEntity'))
->disableOriginalConstructor()
->getMockForAbstractClass();
$account_bm->expects($this->once())
->method("persistEntity")
->with($expectedAccount);
$account_bm->newUser($account);
}
您现在可以使用$expectedEntity
声明返回的实体,但我离开时向您显示,期待一个方法被称为带有特定参数将触发一个断言。
正如您将看到的,上述测试将失败并显示哪些属性是错误的。为什么会这样?
有两个ToDo
标记:您的两个值是由类外的方法生成的,应该被模拟。这些是$this->encoder->encodePassword()
和StringHelper::generateRandomString()
。
因此,为encoder
创建一个模拟对象,并模拟一个静态方法,将其放在AccountBM
中的单独方法中以包装它:
<强> AccountBm 强>
public function newUser(Account $account) {
$salt = $this->generateSalt("10");
$account->setUsername ( $account->getEmail () );
$invitation_code = hash ( 'md5', $account->getEmail () );
$account->setInviationCode ( $invitation_code );
$account->setPassword ( $this->encoder->encodePassword ( $account->getPassword (), $salt ) );
$account->setSalt ( $salt );
$this->persistEntity ( $account );
return $account;
}
public function generateSalt($string)
{
return StringHelper::generateRandomString ( $string );
}
现在和encoder
:
public function testNewUser() {
$account = new Account();
$account->setEmail ( "test@test.com" );
$account->setPassword ( "test" );
$expectedAccount = new Account ();
$expectedAccount ->setEmail ( "test@test.com" );
$expectedAccount ->setInviationCode ( md5("test@test.com") );
$expectedAccount ->setPassword ( "test" );
$expectedAccount ->setSalt ( "10" );
/** @var AccountBM|\PHPUnit_Framework_MockObject_MockObject $account_bm */
$account_bm = $this->getMockBuilder('AccountBM')
->setMethods(array('persistEntity', 'generateSalt'))
->disableOriginalConstructor()
->getMockForAbstractClass();
// mock the wrapper for the static method
$account_bm->expects($this->once())
->method("generateSalt")
->with("10")
->will($this->returnValue("10"));
// mock the encoder and set it to your object
$encoder = $this->getMockBuilder("stdClass")
->setMethods(array("encodePassword"))
->disableOriginalConstructor()
->getMock();
$encoder->expects($this->once())
->method("encodePassword")
->with("test", "10")
->will($this->returnValue(md5("test10")));
$account_bm->setEncoder($encoder);
$account_bm->expects($this->once())
->method("persistEntity")
->with($expectedAccount);
$account_bm->newUser($account);
}
但是测试仍然失败 - 我会留给你更换错误的值来检查你是否理解了代码是如何工作的; - )