Генераторы
Почему генераторы вынесены в отдельный метод
Аргументы 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 / false | true -> 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 вместо того, чтобы отдать свойству слишком маленькое значение.