Common skills of unit test (PHPUnit + Laravel)

1. Data provider

To provide parameters and results, use the @ dataProvider annotation to specify which data provider method to use. For example, to check whether the app upgrade data meets the expectation, addProviderAppUpdateData() provides the parameters and results of the test. testAppUpdateData() detects whether the result returned by appUpdateData() is equal to the given expected result, that is, if $appid = 'apply_.3.2_', $result = ['status' = > 0, 'isios' = > false], then if there is ['status' = > 0,' isios' = > false] in $data, the assertion succeeds. It is recommended to name the data providers one by one with string key names, so that when the assertion fails, the name of the failure will be output, making it easier to locate the problem.

  • Example code:
<?php
    namespace Tests\Unit;

    use App\Services\ClientService;
    use Tests\TestCase;

    class ClientServiceTest extends TestCase
    {
        /**
         * @dataProvider addProviderAppUpdateData
         *
         * @param $appId
         * @param $result
         */
        public function testAppUpdateData($appId, $result)
        {
            $data = (new ClientService($appId))->appUpdateData();

            $this->assertTrue(count(array_intersect_assoc($data, $result)) == count($result));
        }

        public function addProviderAppUpdateData()
        {
            return [
                'null'                 => [null, ['status' => 0, 'isIOS' => false, 'latest_version' => 'V']],
                'error app id'         => ['sdas123123', ['status' => 0, 'isIOS' => false, 'latest_version' => 'V']],
                'android force update' => ['bx7_3.3.5_120', ['status' => 0, 'isIOS' => false]],
                'ios force update'     => ['apple_3.3.2_117', ['status' => 1, 'isIOS' => true]],
                'android soft update'  => ['sanxing_3.3.2_117', ['status' => 2, 'isIOS' => false]],
                'ios soft update'      => ['apple_3.3.3_118', ['status' => 2, 'isIOS' => true]],
                'android normal'       => ['fhqd_3.3.6_121', ['status' => 1, 'isIOS' => false]],
                'ios normal'           => ['apple_3.3.5_120', ['status' => 1, 'isIOS' => true]],
                'h5'                   => ['h5_3.3.3', ['status' => 1, 'isIOS' => false]]
            ];
        }
    }
  • Assertion success result:

2. Assertion method

Commonly used are assertTrue(), assertFalse(), assertNull(), assertEquals(), assertThat().

assertThat() custom assertion. Common constraints include isNull(), isTrue(), isFalse(), isInstanceOf(); common combined constraints logicalOr(), logicalAnd(). For example, check whether the returned result is null or ApiApp class.

  • Example code:
<?php
    namespace Tests\Unit;

    use App\Models\ApiApp;
    use App\Services\SystemConfigService;
    use Tests\TestCase;

    class SystemConfigServiceTest extends TestCase
    {
        /**
         * @dataProvider additionProviderGetLatestUpdateAppApi
         *
         * @param $appType
         */
        public function testGetLatestUpdateAppApi($appType)
        {
            $result = SystemConfigService::getLatestUpdateAppApi($appType);
            $this->assertThat($result, $this->logicalOr($this->isNull(), $this->isInstanceOf(ApiApp::class)));
        }

        public function additionProviderGetLatestUpdateAppApi()
        {
            return [
                'apple'   => [1],
                'android' => [2],
                'null'    => [9999]
            ];
        }
    }
  • Assertion success result:

3. Test for exceptions

Use expectExceptionCode() to detect the error code. It is not recommended to detect the error information copy. For example, check whether 3026 error code is thrown after the device is locked.

  • Example code:
<?php
    namespace Tests\Unit;

    use App\Services\UserSecurityService;
    use Illuminate\Support\Facades\Cache;
    use Tests\TestCase;

    class UserSecurityServiceTest extends TestCase
    {
        public static $userId = 4;

        /**
         * Equipment lock detection
         * @throws \App\Exceptions\UserException
         */
        public function testDeviceCheckLock()
        {
            $this->expectExceptionCode(3026);
            Cache::put('device-login-error-account-', '1,2,3,4,5', 300);
            UserSecurityService::$request = null;
            UserSecurityService::$udid    = null;
            UserSecurityService::deviceCheck(self::$userId);
        }
    }
  • Assertion success result:

4. Test private properties and private methods using reflection mechanism

  • If only private methods can be tested, the ReflectionMethod() reflection method can be used, setAccessible(true) setting method can be used, and invokeArgs() or invoke() can be used to call methods (invokeArgs passes parameters as arrays). For example, check whether IP is in the white list.
  • Example code:

Detected code:

namespace App\Facades\Services;

    /**
     * Class WebDefender
     */
    class WebDefenderService extends BaseService
    {
          //ip white list
        private $ipWhiteList = [
            '10.*',  
            '172.18.*',  
            '127.0.0.1' 
        ];

        /**
         * ip Is it in the white list
         *
         * @param string $ip
         *
         * @return bool
         */
        private function checkIPWhiteList($ip)
        {
            if (!$this->ipWhiteList || !is_array($this->ipWhiteList)) {
                return false;
            }
            foreach ($this->ipWhiteList as $item) {
                if (preg_match("/{$item}/", $ip)) {
                    return true;
                }
            }

            return false;
        }
     }

Test method:

<?php

    namespace Tests\Unit;

    use App\Facades\Services\WebDefenderService;
    use Tests\TestCase;

    class WebDefenderTest extends TestCase
    {
        /**
         * Test IP white list
         * @dataProvider additionProviderIp
         *
         * @param $ip
         * @param $result
         *
         * @throws \ReflectionException
         */
        public function testIPWhite($ip, $result)
        {
            $checkIPWhiteList = new \ReflectionMethod(WebDefenderService::class, 'checkIPWhiteList');
            $checkIPWhiteList->setAccessible(true);
            $this->assertEquals($result, $checkIPWhiteList->invokeArgs(new WebDefenderService(), [$ip]));
        }

        public function additionProviderIp()
        {
            return [
                '10 ip'  => ['10.1.1.7', true],
                '172 ip' => ['172.18.2.5', true],
                '127 ip' => ['127.0.0.1', true],
                '192 ip' => ['192.168.0.1', false]
            ];
        }
     }
  • To test the private property, use ReflectionClass(), getProperty(), setValue(), getMethod(), and setAccessible(true) to set the property and method. For example, detect white list path.
  • Example code:

Detected code:

<?php
    namespace App\Facades\Services;

    use App\Exceptions\ExceptionCode;
    use App\Exceptions\UserException;
    use Illuminate\Support\Facades\Cache;

    /**
     * CC Attack defender
     * Class WebDefender
     */
    class WebDefenderService extends BaseService
    {
        //Path white list (regular)
        private $pathWhiteList = [
            //'^auth\/(.*)',
        ];

        private static $request = null;

         /**
         * Whether the request path is in the white list
         *
         * @return bool
         */
        private function checkPathWhiteList()
        {
            $path = ltrim(self::$request->getPathInfo(), '/');
            if (!$path || !$this->pathWhiteList || !is_array($this->pathWhiteList)) {
                return false;
            }
            foreach ($this->pathWhiteList as $item) {
                if (preg_match("/$item/", $path)) {
                    return true;
                }
            }

            return false;
        }
    }

Test method:

<?php
    namespace Tests\Unit;

    use App\Facades\Services\WebDefenderService;
    use Illuminate\Http\Request;
    use Tests\TestCase;

    class WebDefenderTest extends TestCase
    {
         /**
         * Detect whitelist path
         * @dataProvider additionProviderPathWhiteList
         *
         * @param $pathProperty
         * @param $request
         * @param $result
         *
         * @throws \ReflectionException
         */
        public function testCheckPathWhiteList($pathProperty, $request, $result)
        {
            $reflectedClass = new \ReflectionClass('App\Facades\Services\WebDefenderService');

            $webDefenderService     = new WebDefenderService();
            $reflectedPathWhiteList = $reflectedClass->getProperty('pathWhiteList');
            $reflectedPathWhiteList->setAccessible(true);
            $reflectedPathWhiteList->setValue($webDefenderService, $pathProperty);

            $reflectedRequest = $reflectedClass->getProperty('request');
            $reflectedRequest->setAccessible(true);
            $reflectedRequest->setValue($request);

            $reflectedMethod = $reflectedClass->getMethod('checkPathWhiteList');
            $reflectedMethod->setAccessible(true);
            $this->assertEquals($result, $reflectedMethod->invoke($webDefenderService));
        }

        public function additionProviderPathWhiteList()
        {
            $allPath            = ['.*'];
            $checkPath          = ['^auth\/(.*)'];
            $authSendSmsRequest = new Request([], [], [], [], [], ['HTTP_HOST' => 'api.dev.com', 'REQUEST_URI' => '/auth/sendSms']);
            $indexRequest       = new Request([], [], [], [], [], ['HTTP_HOST' => 'api.dev.com', 'REQUEST_URI' => '/']);
            $noMatchRequest     = new Request([], [], [], [], [], ['HTTP_HOST' => 'api.dev.com', 'REQUEST_URI' => '/product/sendSms']);

            return [
                'index'               => [[], $authSendSmsRequest, false],
                'no request'          => [$allPath, $indexRequest, false],
                'all request'         => [$allPath, $authSendSmsRequest, true],
                'check auth sms'      => [$checkPath, $authSendSmsRequest, true],
                'check path no match' => [$checkPath, $noMatchRequest, false]
            ];
        }
    }

5. Code coverage

Reports exported using -- coverage HTML include class and attribute coverage, row coverage, function and method coverage. You can view the coverage of the current unit test. For example, output the code coverage of webdefender test to the desktop (phpunit tests / unit / webdefender test -- coverage HTML ~ / desktop / test)

6. Specify which files to include in the code coverage report

In the configuration file (phpunit.xml), set processuncoveredfiles from whitelist = true, set the directory with < Directory > label, and set the file with < File > label. For example, specify all files in the app/Services directory and app / facades / services / webdefender service.php in the report.

  • Example code:
 <?xml version="1.0" encoding="UTF-8"?>
    <phpunit backupGlobals="false"
             backupStaticAttributes="false"
             bootstrap="tests/bootstrap.php"
             colors="true"
             convertErrorsToExceptions="true"
             convertNoticesToExceptions="true"
             convertWarningsToExceptions="true"
             processIsolation="false"
             stopOnFailure="false">
        <testsuites>
            <testsuite name="Unit">
                <directory suffix="Test.php">./tests/Unit</directory>
            </testsuite>

            <testsuite name="Feature">
                <directory suffix="Test.php">./tests/Feature</directory>
            </testsuite>
        </testsuites>
        <filter>
            <whitelist processUncoveredFilesFromWhitelist="true">
                <directory suffix=".php">./app/Services</directory>
                <file>./app/Facades/Services/WebDefenderService.php</file>
            </whitelist>
        </filter>
        <php>
            <server name="APP_ENV" value="local"/>
            <server name="BCRYPT_ROUNDS" value="4"/>
            <server name="CACHE_DRIVER" value="credis"/>
            <server name="MAIL_DRIVER" value="array"/>
            <server name="QUEUE_CONNECTION" value="sync"/>
            <server name="SESSION_DRIVER" value="array"/>
            <server name="APP_CONFIG_CACHE" value="bootstrap/cache/config.phpunit.php"/>
            <server name="APP_SERVICES_CACHE" value="bootstrap/cache/services.phpunit.php"/>
            <server name="APP_PACKAGES_CACHE" value="bootstrap/cache/packages.phpunit.php"/>
            <server name="APP_ROUTES_CACHE" value="bootstrap/cache/routes.phpunit.php"/>
            <server name="APP_EVENTS_CACHE" value="bootstrap/cache/events.phpunit.php"/>
        </php>
    </phpunit>

7. Reference documents

[PHPUnit Official documents https://phpunit.readthedocs.io/zh_CN/latest/index.html](https://phpunit.readthedocs.io/zh_CN/latest/index.html)
[Reflection class https://www.php.net/manual/en/class.reflectionclass.php](https://www.php.net/manual/en/class.reflectionclass.php)
[Reflection method https://www.php.net/manual/en/class.reflectionmethod.php](https://www.php.net/manual/en/class.reflectionmethod.php)

Tags: PHP Android iOS xml

Posted on Wed, 06 Nov 2019 04:19:38 -0500 by mrsocks