Skip to content

Генераторы

Почему генераторы вынесены в отдельный метод

Аргументы PHP-атрибутов должны быть константными выражениями, поэтому #[Given('x', Gen::int())] записать нельзя. Вместо этого назовите метод, возвращающий array<string, ArbitraryInterface> с ключами по именам параметров. Если аргумент generators опущен, раннер ищет метод с именем <testMethod>Generators.

Методы генераторов (и примеров) объявляйте public static — либо public, если телу нужен $this. Их единственный вызов — рефлексия этого пакета, поэтому статический анализатор видит их как неиспользуемые: Rector из набора dead-code удаляет private-варианты (RemoveUnusedPrivateMethodRector). Public-методы безопасны, а Testo никогда не считает метод с не-void-возвратом тестом.

Генераторы

ФабрикаПроизводитShrink
Gen::int()IntArbitrary, PHP_INT_MIN..PHP_INT_MAXк 0
Gen::intBetween($min, $max)IntArbitrary, [$min, $max]к 0, ограничен диапазоном
Gen::intPositive()IntArbitrary, 1..PHP_INT_MAXк 1
Gen::float()FloatArbitrary, [0.0, 1.0)к 0.0
Gen::floatBetween($min, $max)FloatArbitrary, [$min, $max]к 0.0, ограничен диапазоном
Gen::bool()BoolArbitrary, true / falsetrue -> false
Gen::string()StringArbitrary, Unicode, длина 0..100к '', затем по длине, затем каждый символ к a
Gen::stringAscii()StringArbitrary, печатный ASCII, длина 0..100к '', затем по длине, затем каждый символ к a
Gen::stringOf($min, $max)StringArbitrary, Unicode, ограниченная длинак '', затем по длине, затем каждый символ к a
Gen::stringFrom($alphabet, $min, $max)CharsetStringArbitrary, символы из фиксированного алфавита (многобайтовые допустимы)к '', затем по длине, затем каждый символ к первому символу алфавита
Gen::bytes($min, $max)BytesArbitrary, сырые байтовые строки (байты 0..255)к '', затем по длине, затем каждый байт к "\x00"
Gen::arrayOf($element, $min, $max)ArrayArbitrary, списки из $element, размер по умолчанию 0..100к [], затем по длине, затем каждый элемент
Gen::nonEmptyArrayOf($element, $max)ArrayArbitrary, непустые спискипо длине (никогда ниже 1), затем каждый элемент
Gen::uniqueArrayOf($element, $min, $max)UniqueArrayArbitrary, списки попарно различных элементовкак arrayOf, но конфликтующие с другими элементы пропускаются
Gen::dictOf($key, $value, $min, $max)DictionaryArbitrary, map'ы с различными ключами из $key (int/string) и значениями из $value, размер по умолчанию 0..100к [], затем по размеру, затем каждое значение (ключи фиксированы)
Gen::record($shape)RecordArbitrary, map фиксированной формы ['field' => $arb, ...]каждое поле через свой arbitrary, набор ключей фиксирован
Gen::elements($array)OneOfArbitrary, одно значение из массива (массивная форма oneOf)к более ранним различным значениям
Gen::enum(SomeEnum::class)OneOfArbitrary по case'ам enum'ак более ранним case'ам (объявляйте простые case'ы первыми)
Gen::constant($value)ConstantArbitrary, всегда $valueне shrink'ается
Gen::char()StringArbitrary, один печатный ASCII-символк a
Gen::uuid()UuidArbitrary, строки UUID RFC 4122 v4не shrink'ается
Gen::datetime($min, $max)DateTimeArbitrary, UTC DateTimeImmutable, timestamp в [$min, $max]к Unix-эпохе, ограничен
Gen::floatSpecial()OneOfArbitrary по NAN, ±INF, -0.0 и краям представления floatк более ранним specials
Gen::intRange($min, $max)FlatMappedArbitrary, упорядоченные пары [lo, hi] с lo <= hiобе границы shrink'аются, порядок всегда соблюдается
Gen::recursive($leaf, $wrap, $maxDepth)ограниченные рекурсивные структуры: $wrap поднимает arbitrary предыдущего уровнявнутри ветви, сгенерировавшей значение
Gen::oneOf(...$values)OneOfArbitrary, одно из переданных значенийк более ранним различным значениям (кладите простые значения первыми)
Gen::nullable($inner)NullableArbitrary, null или значение $innerпредпочитает null, затем внутреннее дерево
Gen::map($inner, $fn)MappedArbitrary, $inner, трансформированный $fnчерез внутреннее дерево, с повторным применением $fn
Gen::flatMap($inner, $fn)FlatMappedArbitrary, зависимый генератор, возвращаемый $fn($innerValue)сначала исходное значение (зависимое регенерируется), затем зависимое дерево
Gen::filter($inner, $predicate)FilteredArbitrary, значения $inner, удовлетворяющие $predicate (бросает GenerationExhausted после 100 отклонений — никогда не отдаёт значение вне домена)внутреннее дерево, обрезает кандидатов, не прошедших предикат
Gen::tuple(...$elements)TupleArbitrary, кортеж фиксированной арности, по значению на элементкаждая позиция через свой элемент, арность фиксирована
Gen::frequency($pairs)FrequencyArbitrary, взвешенный выбор по парам [weight, arbitrary]внутри ветви, сгенерировавшей значение
Gen::ipv4()IPv4 dotted-quad строкикаждый октет к 0
Gen::email()адреса вида local@label.tldк кратчайшим local/label и первому TLD
Gen::url()URL'ы http(s)://host.tld[/path]к http://a.com
Gen::json($maxDepth)JSON-кодируемое значение (null/bool/int/float/string/list/object)внутри сгенерированной структуры
Gen::jsonString($maxDepth)текст json_encode от Gen::json()через дерево значения
Gen::regex($pattern) / Gen::stringMatching($pattern)строки, матчащие подмножество regex (компилируется в комбинаторы)более короткие/простые матчи (через скомпилированные деревья)

Числовые генераторы (int*, float*) смещены в сторону границ: примерно каждый пятый draw возвращает in-range edge-значение (0, ±1, min, max для int; 0.0 или min для float), где и концентрируются баги, вместо равномерного распределения. На shrinking это не влияет.

Размерные генераторы гарантируют свой минимум: uniqueArrayOf/dictOf (различные элементы/ключи) и commands (применимые шаги) могут не дотянуть до запрошенного размера, когда пространство значений исчерпано, но никогда не опускаются ниже $min — недостижимый минимум бросает GenerationExhausted вместо того, чтобы отдать свойству слишком маленькое значение.