PHP unit testing advanced (9) - core technology - stub (stub) - factory class injection stub

PHP unit testing advanced (9) - core technology - stub - factory class injection stub

The main code and text of this series of articles come from "The Art of Unit Testing", the original author: Roy Osherove. Translator: Jin Ying.

This series of articles has been adapted according to the syntax and usage habits of php. All codes are tested locally. If reproduced, please indicate the source.
The scenario discussed in this section is that you don't get an instance of an object until you operate on it, not through constructors or property injection. In other words, the stub (through the factory class) is more closely related to the method that calls the stub.

The difference in this case is that the object that initiates the stub request is the code under test. In the previous sections, those pseudo-objects were set by code outside the code under test before the test started.

Factory pattern is a design pattern that allows another class to be responsible for creating objects.
To enhance control and facilitate testing, the factory in this example has a set method to inject stubs.
The test code implemented using this technique is readable, with clear boundaries between different classes, each of which is responsible for different behaviors.
Above, there is a listing of all the code. None of the above, for the convenience of viewing, this article gives all the code.
Note that there is one more factory class. In the source code, there are now 4 files in the source code and 2 files in the test (so much work is just to test the code that calls the file manager, but it is worth it, there will be a special unit test later mock class library to write stubs).
Note that the interface, and the two implementation codes of the interface are completely unchanged.
Source code (1) The interface IExtensionManager.php (unchanged)

extracted from t2\application\index\controller according to the test needs (actually decoupling, making the program clearer )

<?php
namespace app\index\controller;

/**
 * Whether the file name is valid interface
 * The file manager class in the source code will be implemented, and a stub will also be implemented
 * The existence of the interface makes the meaning of all codes clearer and more stable.
 */
interface  IExtensionManager
{
    /**
     * Determine if the file name is valid
     * @param string $filename
     * @return boolean
     */
    public function isValid($filename);
}

(2) The file manager class under t2\application\index\controller implements the above interface, but is actually excluded from the unit test, so it is not tested. Integration tests should be used to test this class.
FileExtensionManager.php (unchanged)
<?php
namespace app\index\controller;

/**
 * File manager class
 *
 */
class FileExtensionManager implements IExtensionManager
{
    /**
     * Determine whether the file name is valid based on the content of a configuration file
     * @param string $filename
     */
    public function isValid($filename)
    {
        // will use the file_get_contents function to read the contents of a file
        // This is not written here for brevity, because it is not the point.
        return true;
    }
}

(3) Under t2\application\index\controller, the factory class returns an object that implements the above interface, which can be injected for the convenience of testing.
ExtensionManagerFactory.php
<?php
namespace app\index\controller;

/**
 * A static factory class that returns an object that implements the IExtensionManager interface,
 * Function to separate code.
 */
class ExtensionManagerFactory
{
    /**
     * @var IExtensionManager
     */
    private static $manager = null;

    /**
     * Through this method, you can inject stubs or normal objects
     * @param unknown $mgr
     */
    public static function setManager($mgr)
    {
        self::$manager = $mgr;
    }

    /**
     * Factory method, you can see that there is a default implementation
     * @return \app\index\controller\IExtensionManager
     */
    public static function create()
    {
        if (self::$manager) {
            return self::$manager;
        }
        return new FileExtensionManager();
    }
}

(4) Class under test under t2\application\index\controller, log analyzer. Constructor injection is used to write code, which is convenient for testing
LogAnalyzer.php
<?php
namespace app\index\controller;

/**
 * The log analyzer class is also the class under test
 *
 * Note that this is an example of injecting stubs with a static factory.
 */
class LogAnalyzer
{
    /**
     * @var IExtensionManager
     */
    private $manager;

    public function __construct()
    {
        // use factory class in source code
        $this->manager = ExtensionManagerFactory::create();
    }

    /**
     * To determine whether the file name is valid, call another class to implement
     * @param string $filename
     */
    public function isValidLogFileName($filename)
    {
        return $this->manager->isValid($filename);
    }
}

Test code
(5) Under t2\tests\index\controller\, the pile class is used to replace the file manager, which is convenient for testing
FakeExtensionManager.php (unchanged)
<?php
namespace tests\index\controller;
/**
 * A stub class for testing log analyzers, since log analyzers read files and hinder unit testing.
 */
class FakeExtensionManager implements \app\index\controller\IExtensionManager
{
    public $willBeValid = false;

    /**
     * Determine whether the file name is valid based on the content of a configuration file
     * @param string $filename
     */
    public function isValid($filename)
    {
        return $this->willBeValid;
    }
}


(6) Under t2\tests\index\controller\, the last is the test class, inject the pile piece LogAnalyzerTest.php with the construction method
<?php
namespace tests\index\controller;

/**
 * class for testing
 */
class LogAnalyzerTest extends \think\testing\TestCase
{

    /**
     * @test
     * Tested using static factory injection of stubs
     * Note that it is very important to make the test method names as meaningful as possible to facilitate the maintenance of the test code. regular
     */
    public function isValidFileName_NameSupportedExtension_ReturnTrue()
    {
        //Prepare a stub that returns true and inject it into the static factory.
        $myFakeManager = new FakeExtensionManager();
        $myFakeManager->willBeValid = true;
        \app\index\controller\ExtensionManagerFactory::setManager($myFakeManager);

        //Start creating the object of the class under test, ready to test
        $analyzer = new \app\index\controller\LogAnalyzer();
        $result = $analyzer->isValidLogFileName("short.ext");
        $this->assertTrue($result);
    }
}

Pass the test under cmd

Previous : Advanced PHP Unit Testing (8) - Core Technology - Stub - Attribute Injection Stub
Next: Advanced PHP Unit Testing (10) - Core Technology - Stub ) - call method injection stub

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327074685&siteId=291194637