Pull to refresh

Тестирование приложений методом Fuzzing

Reading time 4 min
Views 14K
Все, кто разрабатывает на php, да и вообще на любом другом языке программирования с нечеткой типизацией, сталкивались с подобной проблемой:
PHP Notice:  Undefined index: test in /var/www/Testing.php on line 171
PHP Notice:  Undefined index: test2 in /var/www/Testing.php on line 171

Или даже такой:
PHP Fatal error:  Cannot use object of type stdClass as array in /var/www/Testing.php on line 171


В ходе разработки одного крупного проекта пришлось заняться тестированием написанных методов на правильно подданные параметры.
В моем случае я тестировал класс и контракт для него, но идеология тестирования одна и та же.
Допустим, есть функция которая привыкла принимать входной параметр массив, обрабатывать некоторые элементы.
Например:
PHP Warning:  mysql_affected_rows() expects parameter 1 to be resource, string given in /var/www/Testing.php on line 177

Я написал простенький класс, который помогает в тестировании функций, он генерирует рандомные данные, начиная от int закачивая null-ом.
Вот его код:
class tester
{
    public static $useRandom = true;
    public static $randMin = 1;
    public static $randMax = 50;
    public static $stringDefaultPreffix = 'string_';
    public static $mode = 'real';

    const T_INTEGER = 0;
    const T_STRING = 1;
    const T_ARRAY = 2;
    const T_OBJECT = 3;
    const T_BOOLEAN = 4;
    const T_FUNCTION = 5;
    const T_NULL = 6;
    /**
     * Настройка "рандомайзера"
     * чтобы не переписывать код по несколько раз для разных тестов
     */
     public static $allowedTytes = array(self::T_INTEGER,self::T_STRING);

    public static function getRand()
    {
        $dice = array_rand(self::$allowedTytes, 1);
        /*
         * если честно то какой то топорный механизм декларации методов =(
         * мне не нравится
         */

        switch ($dice)
        {
            /**
             * пусть будет int
             */
            case self::T_INTEGER :
                return self::getInt();
                break;
            /**
             * а это строка
             */
            case self::T_STRING:
                return self::getString();
                break;
            /**
             * массив
             */
            case self::T_ARRAY:

                return self::getArray();
                break;

            /**
             * ну ладно еще и объект
             */
            case self::T_OBJECT:
                return self::getObject();
                break;

            /**
             * Еще и були!
             */
            case self::T_BOOLEAN:
                return self::getBoolean();
                break;

            /**
             * функция, чем черт не шутит?
             */
            case self::T_FUNCTION:
                return self::getFunction();
                break;
            /**
             * null?
             */
            case self::T_NULL:
                return self::getNull();
                break;

            default:
                return true;
                break;

        }
    }

    /**
     * получение null
     * @static
     * @return null
     */
    public static function getNull()
    {
        return null;
    }

    /**
     * получение массива
     * @static
     * @return array
     */
    public static function getArray()
    {
        $array = array();
        $arrayLength = mt_rand(self::$randMin, self::$randMax);
        while (count($array) < $arrayLength)
        {
            /**
             * self::getRand(); почему то ошибка сегментирования =(
             * а как хотелось бы чтоб все влезало в память=)
             */
            $array[] = mt_rand(self::$randMin, self::$randMax);
        }


        return $array;
    }

    /**
     * получение объекта
     * @static
     * @return stdClass
     */
    public static function getObject()
    {
        return new stdClass();
    }

    /**
     * получение булевого значения
     * @static
     * @param bool $default
     * @return bool
     */
    public static function getBoolean($default = true)
    {
        if (self::$useRandom) {
            return (mt_rand(0, 1) ? true : false);

        } else
        {
            return $default;
        }

    }

    /**
     * получение целого числа
     * @static
     * @param int $default
     * @return int
     */
    public static function getInt($default = 42)
    {
        if (self::$useRandom) {
            return mt_rand(self::$randMin, self::$randMax);

        } else
        {
            return $default;
        }
    }

    /**
     * генерация рандомной строки
     * @static
     * @param string $default
     * @return string
     */
    public static function getString($default = 'string_1234567')
    {
        if (self::$useRandom) {
            return uniqid(self::$stringDefaultPreffix);

        } else
        {
            return $default;
        }

    }

    /**
     * создаем функцию
     * @static
     * @return string
     */
    public static function getFunction()
    {
        /**
         * честно, копипаст с
         * http://php.net/manual/en/function.create-function.php
         */
        return create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
    }


    /**
     * наш еррор хендлер, можно отнаследоваться и поставить свой
     * @param $errno
     * @param $errstr
     * @param $errfile
     * @param $errline
     * @return bool
     */
    public function errorHandler($errno, $errstr, $errfile, $errline)
    {
        echo '-----ERROR----', PHP_EOL;
        echo  $errstr, PHP_EOL;
        echo  'Line:', $errline, ' in file: ', $errfile, PHP_EOL;
        echo '-------------', PHP_EOL;
        return true;
    }


    /**
     * активация встроенное ерорхендлера
     * @static
     * @return void
     */
    public static function setErrorHandler()
    {
        set_error_handler(array(new self(), "errorHandler"));
    }

    /**
     * тестирование класса
     * @static
     * @param string $className
     * @param null $method
     * @return void
     */
    public static function test($className = 'stdClass', $method = null)
    {

        if (class_exists($className, true)) {
            $obj = new ReflectionClass($className);
            /**
             * берем список всех публичных методов которые будем вызывать
             */
            $methods = $obj->getMethods(ReflectionMethod::IS_PUBLIC);
            foreach ($methods as $method)
            {
                $argsCount = $method->getNumberOfParameters();

                $args = array();
                for ($i = 0; $i < $argsCount; $i++)
                {
                    $args[] = self::getRand();
                }
                if ($method->isStatic()) {
                    $result = call_user_func(array($className, $method->getName()), $args);
                }
                else {
                    $obj = new $className();
                    $result = call_user_func(array($obj, $method->getName()), $args);
                }


            }

        }
    }

}



Он очень простенький, но зато сохраняет очень много времени.

Пример использования:
class badWritenClass
{
    public function testFunc1($param)
    {

        echo count($param),PHP_EOL;
    }

    public function testFunc2($param)
    {
        $calcVar = $param['test'] / $param['test2'] * 50;
        echo $calcVar ,PHP_EOL;
    }

    public static function goodWritenFunc($param)
    {
        if (is_array($param) && isset($param['name'])) {
            echo 'Hello ', $param['name'],PHP_EOL;
        } else
        {
            echo 'Hello Guest!',PHP_EOL;
        }
    }

}

function testFunc1($param)
{

   echo count($param) ,PHP_EOL;
}

function testFunc2($param)
{
   $calcVar = $param['test'] + $param['test2'] * 50;
   echo $calcVar ,PHP_EOL;
}

/**
 * установка хендлера встроенного в класс
 */
tester::setErrorHandler();
tester::$allowedTytes = array(tester::T_INTEGER, tester::T_STRING, tester::T_ARRAY, tester::T_OBJECT, tester::T_BOOLEAN, tester::T_FUNCTION, tester::T_NULL);

/**
 * тестим какие функции
 */
testFunc1(tester::getRand());

testFunc2(tester::getRand());



/**
 * тестим целый класс
 */
tester::test('badWritenClass');


Резюмируя, могу сказать, что метод тестирования который я «навелосипедил» называется Fuzzing.
Пара ссылок по теме:
www.vr-online.ru/?q=content/fuzzing-tehnologija-ohoty-za-bagami-752

wiki.xakep.ru/Default.aspx?Page=fuzzing&AspxAutoDetectCookieSupport=1

ps Мой первый топик на хабре, немного сумбурный и наверное много ошибок, прошу сильно не пинать, все замечания учту и сделаю его лучше.
upd добавил ссылку на vr-online.ru
upd2 немного переписал класс, учитывая замечания из коментариев
Tags:
Hubs:
+21
Comments 23
Comments Comments 23

Articles