PHP unit testing advanced (10) - core technology - stub (stub) - call method injection stub

PHP unit testing advanced (10) - core technology - stub (stub) - call method 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 steps to use this method are as follows.
In the class under test:
    add a method that returns a real instance
    . Use the method normally in your code.
In the
test code:     create a new class that inherits the class under test.     Create
    a public field of the type of the interface you want to replace.
The method of the test class that returns the real instance
    returns the public fields above
In the
test class:     Create a stub that implements the interface to be replaced
    Create a
new derived class instead of an instance of the class under test     Configure this new instance, inject Stubs

Compared with the previous article, the above introduces the factory class injection stubs, 4 files for source code and 2 files for test code.
This article introduces the calling method injection stub, which consists of 3 files of source code and 3 files of test code.
One interface and two implementations remain unchanged, the tested class changes, the test class changes, and a new test auxiliary class is added, that is, a derived class.
The main point of this method: In fact, the subclass is tested, not the original tested class.

This approach is better than the previous ones because it allows you to directly replace dependencies without going deeper (changing dependencies deep in the call stack), which is clean and fast to implement.

Disadvantage of this approach: Great for simulating input to the code under test, but inconvenient for validating calls from the code under test to dependencies.
For example, if your test code calls a web service and gets a return value, and you want to mock your own return value, this method is fine. But if you want to test that your code is calling the web service correctly, things quickly go bad. This requires a lot of hand coding, and mock frameworks or class libraries are better suited for this kind of work. In conclusion, this method is suitable for mocking the return value or the entire return interface, but not for examining the interaction between objects.
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) The class under test under t2\application\index\controller, the log analyzer. The method of calling method injection is used to write code, which is convenient for derived classes to cover, and then test
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 call method.
 */
class LogAnalyzer
{
    /**
     * To determine whether the file name is valid, call another class to implement
     * @param string $filename
     */
    public function isValidLogFileName($filename)
    {
        return $this->getManager()->isValid($filename);
    }

    protected function getManager()
    {
        return new FileExtensionManager();
    }
}

Test code
(4) 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;
    }
}

(5) Under t2\tests\index\controller\, the subclass of the tested class is used to cover the method of generating piles, which is convenient for testing. Because this subclass is dedicated to testing, it is of course placed under the test folder.
LogAnalyzerExtend.php
<?php
namespace tests\index\controller;

use app\index\controller\IExtensionManager;

/**
 * Test auxiliary class, which is a subclass of the source code tested class. It is used to override the method of the original class under test, which is convenient for testing.
 * Please pay attention to the writing method of this class. In order to make the code more general, the construction method is used to inject stubs instead of dead writing.
 * In fact, it can also be hard-coded, with less code, but this class cannot be reused.
 *
 * But you can see, what if the original code has a construction method, what to do with the conflict? Actually it doesn't matter.
 * Because we can write different test auxiliary classes to test multiple methods of a class in the source code.
 */
class LogAnalyzerExtend extends \app\index\controller\LogAnalyzer
{
    /**
     * @var \app\index\controller\IExtensionManager
     */
    private $manager;

    /**
     * Inject by constructor
     * @param \app\index\controller\IExtensionManager $mgr
     */
    public function __construct($mgr)
    {
        $this->manager = $mgr;
    }

    /**
     * Override the original method for easy testing
     */
    protected function getManager()
    {
        return $this->manager;
    }
}

(6) Under t2\tests\index\controller\, the last is the test class, but not the tested class, but the subclass of the tested class.
LogAnalyzerTest.php
<?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.
        $myFakeManager = new FakeExtensionManager();
        $myFakeManager->willBeValid = true;

        //Start creating objects of subclasses of the class under test, inject stubs, and prepare for testing
        $analyzer = new LogAnalyzerExtend($myFakeManager); //Create simultaneous injection
        $result = $analyzer->isValidLogFileName("short.ext");
        $this->assertTrue($result);
    }
}

The test under cmd passed.

Previous: PHP Unit Testing Advanced (9) - Core Technology - Stub - Factory Class Injection Stub
Next: PHP Unit Testing Advanced (11) - Core Technology - Stub - No Use stakes

Guess you like

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