簡體   English   中英

使用PHPUnit和ZF2 Factory

[英]Using PHPUnit and ZF2 Factory

我想為我的工廠實現PHPUnit測試,它調用一個服務。 這是我的工廠:

class FMaiAffaireServiceFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $dbAdapter = $serviceLocator->get('Zend\Db\Adapter\Adapter');

        $resultSetPrototype = new ResultSet();
        $tableGateway = new TableGateway(
            'f_affaire',
            $dbAdapter,
            null,
            $resultSetPrototype
        );
        $adapter = $tableGateway->getAdapter();
        $sql = new Sql($adapter);

        $maiAffaireTable = new FMaiAffaireTable(
            $tableGateway,
            $adapter,
            $sql
        );

        $typeaffaireService = $serviceLocator->get(
            'Intranet\Service\Model\PTypeaffaireService'
        );

        $etatAffaireService = $serviceLocator->get(
            'Intranet\Service\Model\PEtataffaireService'
        );

        $maiPrestationService = $serviceLocator->get(
            'Maintenance\Service\Model\PMaiPrestationService'
        );

        $maiAffaireService = new FMaiAffaireService(
            $maiAffaireTable,
            $typeaffaireService,
            $etatAffaireService,
            $maiPrestationService
        );

        return $maiAffaireService;
    }

廣告有我的測試,但它不起作用:

class FMaiAffaireServiceFactoryTest extends \PHPUnit_Framework_TestCase
{
    public function testCreateService()
    {
        $sm = new ServiceManager();
        $factory = new FMaiAffaireServiceFactory();
        $runner = $factory->createService($sm);
    }
}

編輯:我的新測試腳本:

public function testCreateService()
    {
        $this->mockDriver = $this->getMock('Zend\Db\Adapter\Driver\DriverInterface');
        $this->mockConnection = $this->getMock('Zend\Db\Adapter\Driver\ConnectionInterface');
        $this->mockDriver->expects($this->any())->method('checkEnvironment')->will($this->returnValue(true));
        $this->mockDriver->expects($this->any())->method('getConnection')->will($this->returnValue($this->mockConnection));
        $this->mockPlatform = $this->getMock('Zend\Db\Adapter\Platform\PlatformInterface');
        $this->mockStatement = $this->getMock('Zend\Db\Adapter\Driver\StatementInterface');
        $this->mockDriver->expects($this->any())->method('createStatement')->will($this->returnValue($this->mockStatement));
        $this->adapter = new Adapter($this->mockDriver, $this->mockPlatform);
        $this->sql = new Sql($this->adapter);


        $mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array(), array(), '', false);


        $smMock = $this->getMockBuilder('Zend\ServiceManager\ServiceManager')
                       ->getMock();

        $maiPrestationTable = $this->getMockBuilder('Maintenance\Model\BDD\PMaiPrestationTable')
             ->setMethods(array())
             ->setConstructorArgs(array($mockTableGateway, $this->adapter, $this->sql))
             ->getMock();

        $smMock->expects($this->any())
            ->method('get')
            ->with('Maintenance\Service\Model\PMaiPrestationService')
            ->will($this->returnValue(new PMaiPrestationService($maiPrestationTable)));

        $etatAffaireTable = $this->getMockBuilder('Intranet\Model\BDD\PEtataffaireTable')
            ->setMethods(array())
            ->setConstructorArgs(array($mockTableGateway))
            ->getMock();

        $smMock->expects($this->any())
            ->method('get')
            ->with('Intranet\Service\Model\PEtataffaireService')
            ->will($this->returnValue(new PEtataffaireService($etatAffaireTable)));

        $typeaffaireTable = $this->getMockBuilder('Intranet\Model\BDD\PTypeaffaireTable')
            ->setMethods(array())
            ->setConstructorArgs(array($mockTableGateway))
            ->getMock();

        $smMock->expects($this->any())
            ->method('get')
            ->with('Intranet\Service\Model\PTypeaffaireService')
            ->will($this->returnValue(new PTypeaffaireService($typeaffaireTable)));

        $smMock->expects($this->any())
            ->method('get')
            ->with('Zend\Db\Adapter\Adapter')
            ->will($this->returnValue($this->adapter));

        $factory = new FMaiAffaireServiceFactory();
        $runner = $factory->createService($smMock);
        // assertions here
    }

這告訴我:get無法為Zend \\ Db \\ Adapter \\ Adapter獲取或創建實例

編輯:這是服務:

public function createService(ServiceLocatorInterface $serviceLocator)
        {
            $dbAdapter = $serviceLocator->get('Zend\Db\Adapter\Adapter');

            $resultSetPrototype = new ResultSet();
            $tableGateway = new TableGateway(
                'f_affaire',
                $dbAdapter,
                null,
                $resultSetPrototype
            );
            $adapter = $tableGateway->getAdapter();
            $sql = new Sql($adapter);

            $maiAffaireTable = new FMaiAffaireTable(
                $tableGateway,
                $adapter,
                $sql
            );

            $typeaffaireService = $serviceLocator->get(
                'Intranet\Service\Model\PTypeaffaireService'
            );

            $etatAffaireService = $serviceLocator->get(
                'Intranet\Service\Model\PEtataffaireService'
            );

            $maiPrestationService = $serviceLocator->get(
                'Maintenance\Service\Model\PMaiPrestationService'
            );

            $maiAffaireService = new FMaiAffaireService(
                $maiAffaireTable,
                $typeaffaireService,
                $etatAffaireService,
                $maiPrestationService
            );

            return $maiAffaireService;
        }

我怎樣才能使它工作?

謝謝。

如果要測試工廠,則無需使用實際的服務管理器。 如果你這樣做了,你也要測試ServiceManager類,打破規則只測試一件事。

相反,您可以直接測試工廠方法並模擬服務管理器:

class FMaiAffaireServiceFactoryTest extends \PHPUnit_Framework_TestCase
{

    public function testCreateService()
    {
        /** @var ServiceManager|\PHPUnit_Framework_MockObject_MockObject $smMock */
        $smMock = $this->getMockBuilder('Zend\ServiceManager\ServiceManager')
            ->getMock();
        $smMock->expects($this->any())
            ->method('get')
            ->with('Intranet\Service\Model\PTypeaffaireService')
            ->will($this->returnValue(new PTypeaffaireService()));
        // more mocked returns here

        $factory = new FMaiAffaireServiceFactory();
        $runner = $factory->createService($smMock);
        // assertions here
    }

}

如果是服務經理,您需要自己定義退貨,而不是使用其他工廠(這也意味着要測試所有這些工廠)。

請注意,返回的對象也可能需要進行模擬。 例如您的數據庫適配器。

您可以在此處找到有關PHPUnit中模擬對象的更多信息: http//code.tutsplus.com/tutorials/all-about-mocking-with-phpunit--net-27252

編輯:以下是兩種可能的解決方案,用於模擬您的案例中的服務管理器:

首先,您需要模擬所有依賴項。 再一次, 這是一個例子! 我不知道你的其他類是什么樣的,所以你可能需要禁用構造函數,定義方法等。

/** @var Adapter|\PHPUnit_Framework_MockObject_MockObject $smMock */
$adapterMock = $this->getMockBuilder('Zend\Db\Adapter\Adapter')
    ->disableOriginalConstructor()
    ->getMock();
$typeaffaireService = $this->getMock('Intranet\Service\Model\PEtataffaireService');
$etataffaireService = $this->getMock('Intranet\Service\Model\PTypeaffaireService');
$maiPrestationService = $this->getMock('Maintenance\Service\Model\PMaiPrestationService');

第一個解決方案:通過回調,非常靈活的解決方案,不測試依賴關系。

這個模擬並不關心是否通過服務管理器等獲取實例而不注入依賴項。它只是確保服務管理器模擬能夠返回所需類的模擬。

$smReturns = array(
    'Zend\Db\Adapter\Adapter' => $adapterMock,
    'Intranet\Service\Model\PTypeaffaireService' => $etataffaireService,
    'Intranet\Service\Model\PEtataffaireService' => $typeaffaireService,
    'Maintenance\Service\Model\PMaiPrestationService' => $maiPrestationService,
);

/** @var ServiceManager|\PHPUnit_Framework_MockObject_MockObject $smMock */
$smMock = $this->getMockBuilder('Zend\ServiceManager\ServiceManager')
    ->getMock();
$smMock->expects($this->any())
    ->method('get')
    ->will($this->returnCallback(function($class) use ($smReturns) {
        if(isset($smReturns[$class])) {
            return $smReturns[$class];
        } else {
            return NULL;
        }
    }));

第二種解決方案:通過指定單方法調用。

這是嚴格的解決方案,如果未注入其中一個依賴項,或者即使在錯誤的時間請求實例,也會拋出錯誤。

/** @var ServiceManager|\PHPUnit_Framework_MockObject_MockObject $smMock */
$smMock = $this->getMockBuilder('Zend\ServiceManager\ServiceManager')
    ->getMock();
$smMock->expects($this->at(0))
    ->method('get')
    ->with('Zend\Db\Adapter\Adapter')
    ->will($this->returnValue($adapterMock));
$smMock->expects($this->at(1))
    ->method('get')
    ->with('Intranet\Service\Model\PTypeaffaireService')
    ->will($this->returnValue($typeaffaireService));
$smMock->expects($this->at(2))
    ->method('get')
    ->with('Intranet\Service\Model\PEtataffaireService')
    ->will($this->returnValue($etataffaireService));
$smMock->expects($this->at(3))
    ->method('get')
    ->with('Maintenance\Service\Model\PMaiPrestationService')
    ->will($this->returnValue($maiPrestationService));

創建一個需要四個其他依賴項對象的真實對象的工廠應該只使用四個模擬對象。

現在看看你的工廠代碼,看看第一部分和第二部分之間的區別:為FMaiAffaireService對象創建最后三個參數是好的和干凈的:從服務管理器中獲取三個對象,你就完成了。 這很容易被嘲笑,即使有點重復。

但是第一個參數顯然需要五個模擬對象,兩個真實對象,在這些對象中模擬至少三個方法(不計算在真實對象中調用的方法的數量)。 此外,最后三個參數都被實例化為具有模擬參數的真實對象。

你可以用工廠測試什么? 您可以在測試中做的唯一真正的斷言是工廠是否忠實於合同並交付某種類型的對象。 你可以在其他地方對該對象進行單元測試,因此從工廠中抓取一個充滿模擬的對象然后用它做一些真正的工作並不是很有用!

堅持最簡單的測試,使您的工廠代碼能夠通過。 沒有循環,沒有條件,因此在一次測試中獲得100%的代碼覆蓋率非常容易。

你的工廠應該是這樣的:

public function createService(ServiceLocatorInterface $serviceLocator)
{
    $maiAffaireTable = $serviceLocator->get('WHATEVER\CLASS\KEY\YOU\THINK');
    $typeaffaireService = $serviceLocator->get('Intranet\Service\Model\PTypeaffaireService');
    $etatAffaireService = $serviceLocator->get('Intranet\Service\Model\PEtataffaireService');
    $maiPrestationService = $serviceLocator->get('Maintenance\Service\Model\PMaiPrestationService');

    $maiAffaireService = new FMaiAffaireService(
        $maiAffaireTable,
        $typeaffaireService,
        $etatAffaireService,
        $maiPrestationService
    );

    return $maiAffaireService;
}

這個想法是一個具有四個對象作為依賴關系的對象是一個復雜的野獸,工廠應該盡可能地保持干​​凈和易於理解。 出於這個原因,構建maiAffaireTable對象被推送到另一個工廠,這將導致更容易測試相應工廠中的那個單一方面 - 而不是在此測試中。

你只需要五個模擬:其中四個FMaiAffaireService你的FMaiAffaireService對象的參數,第五個是服務管理器:

    $smMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class)
        ->disableOriginalConstructor()
        ->getMock();

    $FMaiAffaireTableMock = $this->getMockBuilder(FMaiAffaireTable::class)
        ->disableOriginalConstructor()
        ->getMock();


    $PTypeaffaireServiceMock = $this->getMockBuilder(PTypeaffaireService::class)
        ->disableOriginalConstructor()
        ->getMock();

    $PEtataffaireServiceMock = $this->getMockBuilder(PEtataffaireService::class)
        ->disableOriginalConstructor()
        ->getMock();

    $PMaiPrestationServiceMock = $this->getMockBuilder(PMaiPrestationService::class)
        ->disableOriginalConstructor()
        ->getMock();

請注意,我使用包含類名的字符串切換到使用包含完全限定類名的::class靜態常量,即使您使用use將類導入到命名空間中也是如此。 這可以用於PHP 5.5(並且使用它比使用字符串要好得多:自動完成IDE,重構時支持...)。

現在必要的設置:唯一具有被調用方法的模擬對象是服務管理器,它應該以任意順序發出其他模擬而不抱怨。 這就是returnValueMap()的用途:

$mockMap = [
    ['WHATEVER\CLASS\KEY\YOU\THINK', $FMaiAffaireTableMock],
    ['Intranet\Service\Model\PTypeaffaireService', $PTypeaffaireServiceMock],
    ['Intranet\Service\Model\PEtataffaireService',  $PEtataffaireServiceMock],
    ['Maintenance\Service\Model\PMaiPrestationService',  $PMaiPrestationServiceMock]
];
$smMock->expects($this->any())->method('get')->will($this->returnValueMap($mockMap));

現在進行最后的測試:

$factory = new FMaiAffaireServiceFactory();
$result = $factory->createService($smMock);
$this->assertInstanceOf(FMaiAffaireService::class, $result);

這就是它:實例化服務管理器和它應該發出的所有模擬,將它們放入一個地圖數組,並運行一次工廠方法以查看是否正在創建該對象。

如果這個簡單的測試不適用於您的代碼,那么您在代碼中做錯了。 在工廠本身旁邊執行的唯一真實代碼是創建對象的構造函數。 除了將傳遞的參數復制到內部成員之外,此構造函數不應執行任何操作。 不要訪問數據庫,文件系統,網絡,任何東西。 如果要這樣做:在實例化對象后調用方法。

請注意,我根本不關心被調用的服務管理器。 工廠方法要求我傳遞此對象,但無論是按字母順序對所有已配置對象進行十次,零次調用,還是隨機調用:這根本不重要,它是此工廠方法的實現細節。 更改調用順序不應該破壞測試。 唯一相關的是代碼工作並返回正確的對象。 必須配置服務管理器才能使代碼運行所需的工作量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM