Лучшие практики phpunit в сложном сценарии - PullRequest
0 голосов
/ 10 июля 2020

Я пытаюсь протестировать свою php cms, и у меня много вопросов о том, почему и как:

  • официальная политика НЕ для тестирования защищенных / частных методов

но тогда речь идет о функциональном / поведенческом тестировании, а не о модульном тестировании

  • разработка phpunit прекратила поддержку тестирования базы данных все время назад; sth они назвали это dbunit, если я правильно помню

, в результате нам действительно нужно создавать сложные тестовые классы для управления базой данных, или потому что эти классы уже существуют в такой обширной работе, как моя, мы должны здесь и там существовали методы из других классов, которые облегчают управление базой данных, поэтому тестируемая система фактически помогает тестирующему ПО в тесном взаимодействии друг с другом

  • Говорят, что тестирование на самом деле вызывает ваш код еще до того, как написать его с помощью вашей потрясающей командной строки и шаг за шагом перейти к своей цели

, но если ваше тестирование код нацелен на более функциональный подход, я могу назвать свой в процессе разработки любым другим способом, даже из командной строки, и отладить его, отправив вывод в php журнал с error_log.

Сказав, что я избавляю себя от необходимости писать 100 строк кода + более 100 строк тестовых файлов, изменение 100 строк кода + изменение более 100 строк теста файлы и так далее ....

Если мы добавим выше все аргументы о том, что не нужно тестировать защищенные / закрытые члены, мы закончим простым вопросом: зачем эта возня с модульным тестированием частных / защищенных методов что может иметь длинную цепочку внутренних вызовов, когда есть гораздо более простые методы тестирования, пока мы кодируем?

Все приведенное выше введение кажется мне необходимым, чтобы читатель понял, о чем я спрашиваю:

Учитывая следующий конкретный c метод, можете ли вы предложить лучший подход (модульного?) Тестирования?

1 Тестируемый метод

Файл /home/test/server/auth/Auth.php

namespace g3\auth;

use g3\Singleton;
use g3\TokenFactory;
use g3\Registry;
use g3\Header;
use g3\Session;
use g3\Lang;
use g3\Utils;
use g3\mail\MailClient;
use Firebase\JWT;

class Auth {
   /** @var AuthToken $token The AuthToken of this class */
   protected $token;
   /** @var PDO $dbh The PDO handler to the database */
   protected $dbh;
...............
...............
   /**
    * Called by user/admin during the registration.
    * 
    * We use argument '$params' to handle the additional fields that we added at 
    * table 'profiles' (beyond, 'id', 'emai', 'password', 'isactive' and 'dt'). 
    * An example for '$params': array('first_name' => "John", 'second_name' => "Doe").
    * The available fields:
    * - 'title', 
    * - 'fullname',
    * - 'first_name',
    * - 'second_name',
    * - 'username',
    * - 'address1',
    * - 'address2',
    * - 'zip1',
    * - 'zip2',
    * - 'tel',
    * - 'mobile',
    * - 'fax',
    * - 'city',
    * - 'state',
    * - 'country',
    * - 'birth',
    * - 'language',
    * - 'currency',
    * - 'img',
    * - 'capacity_kb'.
    * 
    * Two modes of call:
    * -by user: addUser($email, $password, $params, $name),
    * -by admin: addUser($email, $password, $params, $name, true).
    * 
    * In admin mode no request is added or email is sent.
    * 
    * If useradd succeeds but profile fails to be inserted in table 'profiles' 
    * then return array contains key 'message' although key 'error' remains 
    * 'false' (registration proceeds).
    * 
    * @param string $email
    * @param string $password
    * @param string[] $params Data for table 'profiles'
    * @param boolean $admin True to bypass activation process
    * 
    * @return A hash array with keys 'error', 'message' and if successful, 'uid'
    */
   protected function addUser($email, $password, $params = array(), $name = '', $admin = false) {
      $return['error'] = true;
      $query = $this->dbh->prepare("INSERT INTO {$this->token->get('table_users')} (isactive) VALUES (0)");
      if (!$query->execute()) {
         $return['message'] = $this->getLang()["system_error"] . " #03";
         return $return;
      }
      $uid = $this->dbh->lastInsertId();
      $email = \htmlentities(\strtolower($email));
      if (((int)$this->token->get('suppress_activation') != 1) && !$admin) {
         $addRequest = $this->addRequest($uid, $email, "activation", $name);
         if ($addRequest['error'] == 1) {
            $query = $this->dbh->prepare("DELETE FROM {$this->token->get('table_users')} WHERE id = ?");
            $query->execute(array($uid));
            $return['message'] = $addRequest['message'];
            return $return;
         }
         $isactive = 0;
      } else {
         $isactive = 1;
      }
      $password = $this->getHash($password);
      $query = $this->dbh->prepare("UPDATE {$this->token->get('table_users')} SET email = ?, password = ?, isactive = ? WHERE id = ?");
      if (!$query->execute(array($email, $password, $isactive, $uid))) {
         $query = $this->dbh->prepare("DELETE FROM {$this->token->get('table_users')} WHERE id = ?");
         $query->execute(array($uid));
         $return['message'] = $this->getLang()["system_error"] . " #04";
         return $return;
      }
      if (!\is_array($params))
         $params = array();
      if(($r = $this->addProfile($uid, $params)) !== true)
         $return['message'] = $r['message'];
      $return['error'] = false;
      $return['uid'] = $uid;
      return $return;
   }
.............
.............
}

База данных - это SQLite.

Очевидно, что вызывается множество других методов class Auth:

Auth::addUser
  | | | |
  v | | |
Auth::getLang
    | | |
    v | |
Auth::addRequest
      | |
      v |
Auth::getHash
        |
        v
Auth::addProfile

Также используется ряд других классов :

class Auth
  | | |
  v | |
Auth::token is a AuthToken: contains parameters supporting the 
authentication system like table names, database path etc.
    | |
    v |
class Lang: contains parameters that lead to language specific 
messages
      |
      v
Auth::dbh is a database connection returned by DbHandler::getHandler
class DbHandler: manages the database

Вы можете пропустить следующие части и прочитать «5. Напишите несколько тестов "

2 Установка Composer

  • просто скачайте локальную composer.phar

  • сборку a composer.json: меня не волнует автозагрузчик composer, так как у меня есть мой, и я загружаю его туда из своей подпапки /home/test/server/g3

  • Я не используйте vendor, но только для разработки пакетов, все производственное программное обеспечение, включая мою g3 framework, развернуто в подпапке /home/test/server в отдельных подпапках с настраиваемым автозагрузчиком

/ home / test / composer. json

{
   "autoload": {
      "psr-4": {
         "": "server"
      }
    },
   "require-dev": {
         "mockery/mockery": "=1.3.1",
         "phpunit/phpunit": "=6.5.14",
         "phpunit/php-invoker": "=1.1.4",
         "gealex/doublit": "=2.1.5",
         "phpspec/prophecy": "=1.10.3"
    }
}

/ home / test / vendor / autoloader. php

<?php

// my autoloader
require_once("/home/test/server/g3/ClassLoader.php");

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit1e0ee11cc0a18a5af0d236383dd8a888::getLoader();

3 Настройте phpunit

  • просто загрузите локальный /home/test/server/dev/phpunit-6.phar

  • настройте /home/test/server/dev/phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="/home/test/vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
         verbose="true">

    <testsuites>
        <testsuite name="Root">
            <directory suffix="Test.php">../../tests/server/root</directory>
        </testsuite>

        <testsuite name="Core">
            <directory suffix="Test.php">../../tests/server/core</directory>
        </testsuite>

        <testsuite name="Auth">
            <directory suffix="Test.php">../../tests/server/auth</directory>
        </testsuite>

        <testsuite name="mail">
            <directory suffix="Test.php">../../tests/server/mail</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">server</directory>
        </whitelist>
    </filter>

    <php>
        <var name="skip_mail_integration_tests" value="1"/>
        <var name="skip_auth_integration_tests" value="1"/>
        <var name="domain" value="4f4d4ebc35f1.eu.ngrok.io"/>
        <var name="mailTo" value="john.doe@gmail.com"/>
    </php>
</phpunit>

4 Расширьте PHPUnit \ Framework \ TestCase

Сначала давайте расширим PHPUnit\Framework\TestCase с помощью /home/test/tests/server/CustomTestCase.php, что

  • поддерживает Prophecy и Mockery

  • содержит служебные методы callMethod, getProperty и setProperty который может получить доступ к закрытым / защищенным членам

  • инициализирует \g3\Registry реальными \g3\Token значениями, поступающими из json файлов

<?php
declare(strict_types=1);

use Mockery\Adapter\Phpunit\MockeryTestCase;
use PHPUnit\Framework\TestCase;

/**
 * runTestsInSeparateProcesses
 */
class CustomTestCase extends MockeryTestCase {
   protected $prophet;
   
   // returns the call to private/protected methods
   public static function callMethod($obj, $name, array $args) {
      $class = new \ReflectionClass($obj);
      $method = $class->getMethod($name);
      $method->setAccessible(true);
      return $method->invokeArgs($obj, $args);
   }
   // returns private/protected properties
   public static function getProperty($obj, $name){
      $r = new ReflectionObject($obj);
      $p = $r->getProperty($name);
      $p->setAccessible(true);
      return $p->getValue($obj);
      // alternative syntax with closures
      //$cls = \Closure::bind(function() use ($name){return $this->{$name};}, $obj, \get_class($obj));
      //return $cls();
   }
   // sets a value to a private/protected property
   public static function setProperty($object, $property, $value){
       $ref = new \ReflectionClass($object);
       $ref_prop = $ref->getProperty($property);
       $ref_prop->setAccessible(true);
       $ref_prop->setValue($object, $value);
   }
   
   public static function setUpBeforeClass(){
      Mockery::globalHelpers();
   }
   public static function tearDownAfterClass(){
   }
   
   protected function setup() {
      $this->prophet = new \Prophecy\Prophet;
   }
   protected function tearDown() {
      $this->prophet->checkPredictions();
      Mockery::close();
   }
}

5 Напишите несколько тестов

Файл /home/test/tests/server/g3/auth/AuthTest.php

<?php
declare(strict_types=1);

require_once(__DIR__ . '/../CustomTestCase.php');

/**
 * runTestsInSeparateProcesses
 */
class AuthTest extends CustomTestCase {
   protected $tokens;
   
   public static function setUpBeforeClass(){
      parent::setUpBeforeClass();
      // tokens initialize the g3 framework and these contain full data 
      // from json files; access them like: 
      // \g3\Registry::getInstance()->getToken('g3\auth\AuthToken') etc.
      static::callMethod(\g3\Dispatcher::getInstance(), 'tokenInit', array());
   }
   public static function tearDownAfterClass(){
      parent::tearDownAfterClass();
      \g3\Registry::getInstance()->destroy();
   }
   
   /**
    * Creates mocked constructor argument objects before each test.
    * These tokens do not contain data read from json files; dummy tokens.
    * Accessible through $this->tokens.
    */
   protected function setup() {
      parent::setup();
      
      $resolverToken = $this->getMockBuilder('\g3\ResolverToken')
         ->disableOriginalConstructor()
         ->getMock();
      
      $requestToken = $this->getMockBuilder('\g3\RequestToken')
         ->disableOriginalConstructor()
         ->getMock();
      
      $authToken = $this->getMockBuilder('\g3\auth\AuthToken')
         ->disableOriginalConstructor()
         ->getMock();
      
      $accountToken = $this->getMockBuilder('\g3\AccountToken')
         ->disableOriginalConstructor()
         ->getMock();
      
      $dbh = $this->getMockBuilder('\g3\DbHandler')
         ->disableOriginalConstructor()
         ->getMock();
      
      $lang = $this->getMockBuilder('\g3\Lang')
         ->disableOriginalConstructor()
         ->getMock();
      
      $session = $this->getMockBuilder('\g3\Session')
         ->disableOriginalConstructor()
         ->getMock();
      
      $this->tokens = array('accountToken' => $accountToken, 'resolverToken' => $resolverToken, 'requestToken' => $requestToken, 'authToken' => $authToken, 'dbh' => $dbh, 'lang' => $lang, 'session' => $session);
   }
   
   /**
    * Deletes mocked objects stored at $this->tokens after each test.
    */
   protected function tearDown() {
      parent::tearDown();
      $this->tokens = null;
   }
   
   /**
    * Prints a message on CLI
    */
   public function out($msg){
      $msg = \str_replace(array('test_', '_', "\n"), array('', ' ', ":\n"), $msg);
      fwrite(STDOUT, $msg);
   }

   /**
    * Adds an inactive user. It uses real tokens.
    */
   public function helper_addUserInactive($arr) {
      // AuthToken is not stored in Registry & not synchronized
      \g3\TokenFactory::getInstance()->synchronize($arr['authToken'], 'r');
      $test = $this->getMockBuilder('\g3\auth\Auth')
         ->setConstructorArgs(array($arr['resolverToken'], $arr['requestToken'], $arr['authToken'], $arr['accountToken'], $arr['dbh']))
         ->setMethods(['addRequest', 'getHash'])
         ->getMock();
      $test->expects($this->exactly(1))
         ->method('addRequest')
         ->will($this->returnValue(array('error' => false)));
      $test->expects($this->exactly(1))
         ->method('getHash')
         ->will($this->returnValue('123'));
      $token = static::getProperty($test, 'token');
      $token->set('suppress_activation', 0);
      $profile = array('first_name' => 'John', 'second_name' => 'Doe', 'username' => 'John', 'address1' => 'White House, Washington D.C.', 'country' => 'USA');
      // call 'addUser'
      $r = static::callMethod($test, 'addUser', array($arr['to'], '123', $profile, 'John'));
      $users = $arr['authToken']->get('table_users');
      $profiles = $arr['authToken']->get('table_profiles');
      
      return array(
         'dbh' => $arr['dbh'], 
         'user' => array('id' => $r['uid'], 'email' => $arr['to'], 'password' => '123', 'role' => 'guest', 'isactive' => '0'), 
         'profile' => $profile, 
         'tables' => array('users' => $arr['authToken']->get('table_users'), 'profiles' => $arr['authToken']->get('table_profiles'), 'requests' => $arr['authToken']->get('table_requests'))
      );
   }

/**
    * Adds an active user. It uses real tokens.
    */
   public function helper_addUserActive($arr) {
      // AuthToken is not stored in Registry & not synchronized
      \g3\TokenFactory::getInstance()->synchronize($arr['authToken'], 'r');
      $test = $this->getMockBuilder('\g3\auth\Auth')
         ->setConstructorArgs(array($arr['resolverToken'], $arr['requestToken'], $arr['authToken'], $arr['accountToken'], $arr['dbh']))
         ->setMethods(['addRequest', 'getHash'])
         ->getMock();
      $test->expects($this->never())
         ->method('addRequest');
      $test->expects($this->exactly(1))
         ->method('getHash')
         ->will($this->returnValue('123'));
      $token = static::getProperty($test, 'token');
      $token->set('suppress_activation', 1);
      $profile = array('first_name' => 'John', 'second_name' => 'Doe', 'username' => 'John', 'address1' => 'White House, Washington D.C.', 'country' => 'USA');
      // call 'addUser'
      $r = static::callMethod($test, 'addUser', array($arr['to'], '123', $profile, 'John'));
      $users = $arr['authToken']->get('table_users');
      $profiles = $arr['authToken']->get('table_profiles');
      
      return array(
         'dbh' => $arr['dbh'], 
         'user' => array('id' => $r['uid'], 'email' => $arr['to'], 'password' => '123', 'role' => 'guest', 'isactive' => '1'), 
         'profile' => $profile, 
         'tables' => array('users' => $arr['authToken']->get('table_users'), 'profiles' => $arr['authToken']->get('table_profiles'), 'requests' => $arr['authToken']->get('table_requests'))
      );
   }
   
   /**
    * Adds an active user as admin. It uses real tokens.
    */
   public function helper_adminAddsUserActive($arr) {
      // AuthToken is not stored in Registry & not synchronized
      \g3\TokenFactory::getInstance()->synchronize($arr['authToken'], 'r');
      $test = $this->getMockBuilder('\g3\auth\Auth')
         ->setConstructorArgs(array($arr['resolverToken'], $arr['requestToken'], $arr['authToken'], $arr['accountToken'], $arr['dbh']))
         ->setMethods(['addRequest', 'getHash'])
         ->getMock();
      $test->expects($this->never())
         ->method('addRequest');
      $test->expects($this->exactly(1))
         ->method('getHash')
         ->will($this->returnValue('123'));
      $token = static::getProperty($test, 'token');
      $token->set('suppress_activation', 0);
      $profile = array('first_name' => 'John', 'second_name' => 'Doe', 'username' => 'John', 'address1' => 'White House, Washington D.C.', 'country' => 'USA');
      // call 'addUser'
      $r = static::callMethod($test, 'addUser', array($arr['to'], '123', $profile, 'John', true));
      $users = $arr['authToken']->get('table_users');
      $profiles = $arr['authToken']->get('table_profiles');
      
      return array(
         'dbh' => $arr['dbh'], 
         'user' => array('id' => $r['uid'], 'email' => $arr['to'], 'password' => '123', 'role' => 'guest', 'isactive' => '1'), 
         'profile' => $profile, 
         'tables' => array('users' => $arr['authToken']->get('table_users'), 'profiles' => $arr['authToken']->get('table_profiles'), 'requests' => $arr['authToken']->get('table_requests'))
      );
   }
   
   /**
    * Inserts a user & profile without the call to any `Auth` method.
    * It uses real tokens.
    */
   public function helper_insertUser($arr, $active = '0') {
      // AuthToken is not stored in Registry & not synchronized
      \g3\TokenFactory::getInstance()->synchronize($arr['authToken'], 'r');
      $users = $arr['authToken']->get('table_users');
      $profiles = $arr['authToken']->get('table_profiles');
      $requests = $arr['authToken']->get('table_requests');
      // table 'users'
      $time = \time();
      $query = $arr['dbh']->getHandler()->prepare("INSERT INTO {$users} (email, password, isactive, dt) VALUES (?, ?, ?, ?)");
      $query->execute(array($arr['to'], '123', $active, $time));
      $uid = $arr['dbh']->getHandler()->lastInsertId();
      return array(
         'dbh' => $arr['dbh'], 
         'user' => array('id' => $uid, 'email' => $arr['to'], 'password' => '123', 'role' => 'guest', 'isactive' => $active),  
         'tables' => array('users' => $users, 'profiles' => $profiles, 'requests' => $requests)
      );
   }

   /**
    * Deletes user from tables `users`, `profiles` and `requests`.
    * `$arr` is the return of `::test_helper_addUserInactive` or 
    * `::test_helper_addUserActive`.
    */
   public function helper_deleteAddedUser($arr) {
      $users = $arr['tables']['users'];
      $profiles = $arr['tables']['profiles'];
      $requests = $arr['tables']['requests'];
      $query = $arr['dbh']->getHandler()->prepare("DELETE FROM {$users} WHERE id = ?");
      $query->execute(array($arr['user']['id']));
      $query = $arr['dbh']->getHandler()->prepare("DELETE FROM {$profiles} WHERE uid = ?");
      $query->execute(array($arr['user']['id']));
      $query = $arr['dbh']->getHandler()->prepare("DELETE FROM {$requests} WHERE uid = ?");
      $query->execute(array($arr['user']['id']));
   }

   /**
    * Helper that returns real tokens of g3 platform.
    * @group integration_email
    * @group test
    */
   public function test_helper_construct_with_real_tokens() {
      $systemToken = \g3\Registry::getInstance()->getToken('g3\SystemToken');
      $accountToken = \g3\Registry::getInstance()->getToken('g3\AccountToken');
      $lang = new \g3\Lang(ROOT . "/server/g3/auth/language", $accountToken->get('site', 'language'));
      $exceptionToken = \g3\Registry::getInstance()->getToken('g3\ExceptionToken');
      $dbh = new \g3\DbHandler('sqlite', 'db_auth', $accountToken, $exceptionToken);
      // AuthToken is not stored in Registry & not synchronized
      $authToken = \g3\TokenFactory::getInstance()->getToken('g3\auth\AuthToken', null, array('registry' => \g3\Registry::getInstance(), 'dbh' => $dbh));
      \g3\TokenFactory::getInstance()->synchronize($authToken, 'r');
      $requestToken = \g3\Registry::getInstance()->getToken('g3\RequestToken');
      $resolverToken = \g3\Registry::getInstance()->getToken('g3\ResolverToken');
      $session = \g3\Session::getInstance();
      $to = $GLOBALS['mailTo'];
      
      $this->assertTrue(true);
      
      return array('systemToken' => $systemToken, 'accountToken' => $accountToken, 'resolverToken' => $resolverToken, 'requestToken' => $requestToken, 'authToken' => $authToken, 'exceptionToken' => $exceptionToken, 'dbh' => $dbh, 'lang' => $lang, 'session' => $session, 'to' => $to);
   }

   // !!!WAIT WHAT THE HELL!!!
   // !!!WE DIDN'T EVEN BEGIN TO TEST!!!

   /*
    * ===============
    * Auth::addUser()
    * ===============
    */
   /**
    * @depends test_helper_construct_with_real_tokens
    */
   public function test_addUser_for_misspelled_table_users($arr) {
      $this->out(__METHOD__ . "\n");
      // AuthToken is not stored in Registry & not synchronized
      \g3\TokenFactory::getInstance()->synchronize($arr['authToken'], 'r');
      $test = $this->getMockBuilder('\g3\auth\Auth')
         ->setConstructorArgs(array($arr['resolverToken'], $arr['requestToken'], $arr['authToken'], $arr['accountToken'], $arr['dbh']))
         ->setMethods(null)
         ->getMock();
      $token = static::getProperty($test, 'token');
      $token->set('table_users', 'user');
      // call 'addUser'
      $this->expectException(PDOException::class);
      $r = static::callMethod($test, 'addUser', array($arr['to'], '123', array(), 'John'));
      echo "\n";
   }
   /**
    * @depends test_helper_construct_with_real_tokens
    */
   public function test_addUser_updates_tables_users_profiles($tokens) {
      $this->out(__METHOD__ . "\n");
      $arr = $this->helper_addUserInactive($tokens);
      $users = $arr['tables']['users'];
      $profiles = $arr['tables']['profiles'];
      // query table 'users'
      $query = $arr['dbh']->getHandler()->prepare("SELECT email, role, password, isactive FROM $users WHERE id = ?");
      $query->execute(array($arr['user']['id']));
      $row = $query->fetch(\PDO::FETCH_ASSOC);
      $this->assertSame($arr['user']['email'], $row['email']);
      $this->assertSame($arr['user']['role'], $row['role']);
      $this->assertSame($arr['user']['password'], $row['password']);
      $this->assertSame($arr['user']['isactive'], $row['isactive']);
      // query table 'profiles'
      $query = $arr['dbh']->getHandler()->prepare("SELECT first_name, second_name, username, address1 FROM $profiles WHERE uid = ?");
      $query->execute(array($arr['user']['id']));
      $row = $query->fetch(\PDO::FETCH_ASSOC);
      $this->assertSame($arr['profile']['first_name'], $row['first_name']);
      $this->assertSame($arr['profile']['second_name'], $row['second_name']);
      $this->assertSame($arr['profile']['username'], $row['username']);
      $this->assertSame($arr['profile']['address1'], $row['address1']);
      // delete entries in tables
      $this->test_helper_deleteAddedUser($arr);
      echo "\n";
   }
   /**
    * @depends test_helper_construct_with_real_tokens
    */
   public function test_addUser_updates_tables_users_profiles_with_supress_activation($tokens) {
      $this->out(__METHOD__ . "\n");
      $arr = $this->helper_addUserActive($tokens);
      $users = $arr['tables']['users'];
      $profiles = $arr['tables']['profiles'];
      // query table 'users'
      $query = $arr['dbh']->getHandler()->prepare("SELECT email, role, password, isactive FROM $users WHERE id = ?");
      $query->execute(array($arr['user']['id']));
      $row = $query->fetch(\PDO::FETCH_ASSOC);
      $this->assertSame($arr['user']['email'], $row['email']);
      $this->assertSame($arr['user']['role'], $row['role']);
      $this->assertSame($arr['user']['password'], $row['password']);
      $this->assertSame($arr['user']['isactive'], $row['isactive']);
      // query table 'profiles'
      $query = $arr['dbh']->getHandler()->prepare("SELECT first_name, second_name, username, address1 FROM $profiles WHERE uid = ?");
      $query->execute(array($arr['user']['id']));
      $row = $query->fetch(\PDO::FETCH_ASSOC);
      $this->assertSame($arr['profile']['first_name'], $row['first_name']);
      $this->assertSame($arr['profile']['second_name'], $row['second_name']);
      $this->assertSame($arr['profile']['username'], $row['username']);
      $this->assertSame($arr['profile']['address1'], $row['address1']);
      // delete entries in tables
      $this->test_helper_deleteAddedUser($arr);
      echo "\n";
   }
   /**
    * @depends test_helper_construct_with_real_tokens
    */
   public function test_addUser_updates_tables_users_profiles_for_admin($tokens) {
      $this->out(__METHOD__ . "\n");
      $arr = $this->helper_adminAddsUserActive($tokens);
      $users = $arr['tables']['users'];
      $profiles = $arr['tables']['profiles'];
      // query table 'users'
      $query = $arr['dbh']->getHandler()->prepare("SELECT email, role, password, isactive FROM $users WHERE id = ?");
      $query->execute(array($arr['user']['id']));
      $row = $query->fetch(\PDO::FETCH_ASSOC);
      $this->assertSame($arr['user']['email'], $row['email']);
      $this->assertSame($arr['user']['role'], $row['role']);
      $this->assertSame($arr['user']['password'], $row['password']);
      $this->assertSame($arr['user']['isactive'], $row['isactive']);
      // query table 'profiles'
      $query = $arr['dbh']->getHandler()->prepare("SELECT first_name, second_name, username, address1 FROM $profiles WHERE uid = ?");
      $query->execute(array($arr['user']['id']));
      $row = $query->fetch(\PDO::FETCH_ASSOC);
      $this->assertSame($arr['profile']['first_name'], $row['first_name']);
      $this->assertSame($arr['profile']['second_name'], $row['second_name']);
      $this->assertSame($arr['profile']['username'], $row['username']);
      $this->assertSame($arr['profile']['address1'], $row['address1']);
      // delete entries in tables
      $this->test_helper_deleteAddedUser($arr);
      echo "\n";
   }

Я только что тестировал то, что знал с самого начала - несколько месяцев go - он работал так, как я мог отправлять запросы GET или POST и с введенным error_log У меня есть производственный безошибочный код, обходящий все теории модульного тестирования.

Также эти тесты подвержены ошибкам, если кто-то начинает возиться с методом тестирования, они начинают создавать код ошибки из тестового файла; в этом случае вам придется отлаживать тесты, которые пытаются проверить ваш код!

С моей точки зрения, я начинаю верить, что модульное тестирование - отличный способ потратить потерять ваше время. Я понимаю ваш аргумент, что это не модульное тестирование, поэтому я заменить protected на public, это что-то исправляет? Интересно, может ли кто-нибудь доказать, что я неправ? Каков тогда ваш подход?

1 Ответ

0 голосов
/ 10 июля 2020

Мой подход такой же, как и подход, который я использую при решении определенной проблемы / задачи - разбейте его на как можно меньшие части и решайте их один за другим.

Если мне нужно протестировать продвинутый класс и убедитесь, что все его методы соответствуют определенным критериям, я бы проверил каждый его метод, даже если он является частным или защищенным (с функциями закрытия). Так что в следующий раз, когда что-то выдаст ошибку / не пройдет тест, вы точно знаете, что это такое и почему не удалось.

...