如何在PHPUnit中管理互连测试
在一个类的测试中,如果两个方法只是同一个类的几个其他方法的包装器,您将如何减少冗余。 例如,如果我要测试一个类,该类根据其他方法验证的某些条件验证用户的帐户状态。例如,本课程:如何在PHPUnit中管理互连测试,php,testing,phpunit,Php,Testing,Phpunit,在一个类的测试中,如果两个方法只是同一个类的几个其他方法的包装器,您将如何减少冗余。 例如,如果我要测试一个类,该类根据其他方法验证的某些条件验证用户的帐户状态。例如,本课程: public function validateProfile(UserInterface $user) { // check if profile is completed } public function validatePurchasedProducts(UserInterface $user) {
public function validateProfile(UserInterface $user)
{
// check if profile is completed
}
public function validatePurchasedProducts(UserInterface $user)
{
// check if user has purchased products
}
public function validateAssociatedCard(UserInterface $user)
{
// check if user has a card associated with account
}
public function validateLoginStatus(UserInterface $user)
{
return $this->validateProfile($user)
and $this->validatePurchasedProducts($user)
and $this->validateAssociatedCard($user);
}
我可以为前3种方法编写测试,但当涉及到最后一种方法时,我必须重复我在最后3种方法中所做的完全相同的事情,并将其组合在一起。
这使得测试过于冗余:
public function testUserHasValidProfileDetails()
{
// arrange mocks // act // assert
}
public function testUserHasPurchasedProduct()
{
// arrange mocks // act // assert
}
public function testUserHasCardAssociated()
{
// arrange mocks // act // assert
}
public function testUserCanLogInToDashboard()
{
// arrange mocks // act // assert - for profile validation
// arrange mocks // act // assert - for products validation
// arrange mocks // act // assert - for card validation
}
PHPUnit中是否有允许这种行为的方式或特性?我知道我可以用@depends对测试进行注释,但这并不是问题所在。我使用@depends将项目从一个测试传递到另一个测试,这与示例中的数组非常类似。但是,在内部,我会根据您正在尝试的内容更改代码和测试。我通过让每个设置一个有效的内部值来执行验证。这允许我测试每个函数,并设置对象的内部状态,以便为将来的测试对其进行硬设置
private $ProfileValid;
private $PurchasedProducts;
private $CardAssociated;
public function validateProfile(UserInterface $user)
{
// check if profile is completed
$this->ProfileValid = true;
}
public function validatePurchasedProducts(UserInterface $user)
{
// check if user has purchased products
$this->PurchasedProducts = true;
}
public function validateAssociatedCard(UserInterface $user)
{
// check if user has a card associated with account
$this->CardAssociated = true;
}
public function validateLoginStatus(UserInterface $user)
{
if(is_null( $this->ProfileValid) )
{
$this->validateProfile($user);
}
if(is_null( $this->PurchasedProducts) )
{
$this->validatePurchasedProducts($user)
}
if(is_null( $this->CardAssociated) )
{
$this->validateAssociatedCard($user);
}
return $this->ProfileValid && $this->PurchasedProducts && $this->CardAssociated;
}
然后,我可以创建一个对象并单独运行每个测试(有或没有模拟对象),并使用反射来查看内部变量是否设置正确
然后,最终测试创建对象并再次使用反射设置值,并调用最终validateLoginStatus。由于我可以控制对象,我可以将一个或多个变量设置为null,以调用测试。同样,如果需要,可以使用模拟。此外,模拟的设置可以是测试代码中接受参数的内部函数
下面是一个类似的示例,用于从抽象类测试我自己的内部迭代器
class ABSTRACT_FOO extends FOO
{
public function CreateFoo() { }
public function CloseFoo() { }
public function AddTestElement($String)
{
$this->Data[] = $String;
}
}
class FOO_Test extends \PHPUnit_Framework_TestCase
{
protected $FOOObject;
protected function setUp()
{
$this->FOOObject = new ABSTRACT_FOO();
}
protected function tearDown()
{
}
/**
* Create the data array to have 3 items for test iteration
*/
public function testCreateData()
{
$this->FOOObject->AddTestElement('Record 1');
$this->FOOObject->AddTestElement('Record 2');
$this->FOOObject->AddTestElement('Record 3');
$ReflectionObject = new \ReflectionObject($this->FOOObject);
$PrivateConnection = $ReflectionObject->getProperty('Data');
$PrivateConnection->setAccessible(TRUE);
$DataArray = $PrivateConnection->getValue($this->FOOObject);
$this->assertEquals(3, sizeof($DataArray));
return $this->FOOObject; // Return Object for next test. Will have the 3 records
}
/**
* @covers lib\FOO::rewind
* @depends testCreateData
*/
public function testRewind($DataArray)
{
$DataArray->Next();
$this->assertGreaterThan(0, $DataArray->Key(), 'Ensure the iterator is not on the first record of the data.');
$DataArray->Rewind();
$this->assertEquals(0, $DataArray->Key());
}
/**
* @covers lib\FOO::current
* @depends testCreateData
*/
public function testCurrent($DataArray)
{
$DataArray->Rewind();
$Element = $DataArray->Current();
$this->assertInternalType('string', $Element);
$this->assertEquals('Record 1', $Element);
}
/**
* @covers lib\FOO::key
* @depends testCreateData
*/
public function testKey($DataArray)
{
$DataArray->Rewind();
$this->assertEquals(0, $DataArray->Key());
}
/**
* @covers lib\FOO::next
* @depends testCreateData
*/
public function testNext($DataArray)
{
$DataArray->Rewind();
$this->assertEquals(0, $DataArray->Key(), 'Ensure the iterator is at a known position to test Next() move on');
$DataArray->Next();
$this->assertEquals(1, $DataArray->Key());
$Element = $DataArray->Current();
$this->assertInternalType('string', $Element);
$this->assertEquals('Record 2', $Element);
}
/**
* @covers lib\FOO::valid
* @depends testCreateData
*/
public function testValid($DataArray)
{
$DataArray->Rewind();
for($i = 0; $i < 3; ++ $i) // Move through all 3 entries which are valid
{
$this->assertTrue($DataArray->Valid(), 'Testing iteration ' . $i);
$DataArray->Next();
}
$this->assertFalse($DataArray->Valid());
}
}
这使我能够检查类,而无需在加载数据时重复大量功能。首先,使用单独的测试还可以检查每个功能是否正常工作。如果您将测试结构化为对ValidateLogistatus进行测试,那么可以使用一个模拟来完成,该模拟只包含您希望设置的值,以确保所有组合都能正常工作,如果将来没有全部3个组合,您可能希望继续工作。我甚至会使用dataProvider功能测试所有3个选项。validateLoginStatus方法的目的是调用其他选项。因此,为了测试该方法,您不需要测试其他方法是否按预期工作,而是在每个方法测试中都这样做。您只需要确保调用了其他方法,可能是按照正确的顺序。为此,您可以使用类的部分模拟,并模拟所调用的方法
$object = $this->getMock(
'Class',
array(
'validateProfile',
'validatePurchasedProducts',
'validateLoginStatus'
)
);
// configure method call expectations...
$object->validateLoginStatus($user);
这样做的另一个原因是,当某些方法按预期停止工作时,只有一个测试会失败。您应该重复自己的测试。由于所有方法都是同一个类的一部分,所以您不一定知道正在调用其他方法。该类可以编写为将3个验证方法中的逻辑复制到最后一个方法中,并且测试应该通过,而不是说这是一件好事。但这可能是原始情况,重构以公开类中的部分验证不应导致任何测试失败 一般来说,如果某个东西很难测试,那么您应该重新考虑您的设计。在您的情况下,我会将部分验证分解为它们自己的类,这些类将被传入,而这些类可能会被模拟
在我看来,模拟正在测试的系统是一种糟糕的做法,因为您现在正在指定系统的实现细节。这将使以后重构类变得更加困难。甜蜜而简单!这正是我喜欢的方式。这真的不是一个好的测试实践。您正在指定被测试类的实现细节。