Генератор тестів - це програмне забезпечення для конкретного треку, яке автоматично генерує тести для практичної вправи. Він робить це, перетворюючи JSON-тестові випадки вправи на тести мовою треку.
Деякі переваги наявності Генератора тестів:
Загалом Генератор тестів запускають, щоб:
Додавання Генератора тестів для нової вправи дає змогу згенерувати її файли тестів. За умови, що сам Генератор тестів уже реалізовано, генерація тестів для нової вправи вимагатиме (значно) менше роботи, ніж написання їх з нуля.
Коли у вправи вже є Генератор тестів, його можна запустити знову, щоб оновити чи синхронізувати вправу з її найновішими канонічними даними. Радимо робити це періодично, щоб перевіряти, чи немає проблемних тестових випадків, які треба оновити, і нових тестів, які варто додати.
Є дві можливі відправні точки, коли ми реалізуємо Генератор тестів для вправи:
Якщо тести вже існують, реалізуйте Генератор тестів так, щоб згенеровані ним тести не ламали наявні рішення.
Загалом файли тестів генерують одним із двох способів:
Ми помітили, що підхід на основі коду призводить до доволі складного коду Генератора тестів, тоді як підхід на основі шаблонів простіший.
Радимо такий порядок дій:
include = false у tests.toml вправи
Ключова перевага такого підходу в тому, що кожна вправа має власний шаблон, який:
Проєктуючи генератор тестів, намагайтеся:
Генератор тестів зазвичай (переважно) написано мовою треку.
Хоча можна використовувати й інші мови, кожна додаткова мова ускладнить супровід треку та долучення до нього. Тому радимо, де це можливо, використовувати мову треку, бо це полегшує і супровід, і долучення.
Якщо в треку є інструменти для форматування коду, варто запускати їх як крок постобробки після рендерингу шаблону.
Основні дані, з якими працює Генератор тестів, - це файл canonical-data.json вправи.
Цей файл визначено в репозиторії exercism/problem-specifications, який визначає спільні метадані для багатьох вправ Exercism.
Не всі вправи мають файл canonical-data.json!
Якщо його немає, тести доведеться створювати вручну, бо Генератору тестів не буде з чим працювати.
Канонічні дані визначено в JSON-обʼєкті.
Цей обʼєкт містить поле "cases", у якому зберігаються тестові випадки.
Ці тестові випадки (зазвичай) відповідають тестам у треку один до одного.
Кожен тестовий випадок має кілька властивостей, найважливіші з яких - опис, властивість, вхідні значення та очікуване значення.
Ось (частковий) приклад файлу canonical-data.json вправи leap:
{
"exercise": "leap",
"cases": [
{
"uuid": "6466b30d-519c-438e-935d-388224ab5223",
"description": "year not divisible by 4 in common year",
"property": "leapYear",
"input": {
"year": 2015
},
"expected": false
},
{
"uuid": "4fe9b84c-8e65-489e-970b-856d60b8b78e",
"description": "year divisible by 4, not divisible by 100 in leap year",
"property": "leapYear",
"input": {
"year": 1996
},
"expected": true
}
]
}
Головне завдання Генератора тестів - перетворювати ці JSON-дані на тести, специфічні для треку. Ось як наведений вище JSON міг би перетворитися на тестовий код мовою Nim:
import unittest
import leap
suite "Leap":
test "year not divisible by 4 in common year":
check isLeapYear(2015) == false
test "year divisible by 4, not divisible by 100 in leap year":
check isLeapYear(1996) == true
Структуру файлу canonical-data.json добре задокументовано, і для неї також є визначення JSON-схеми.
Деякі вправи використовують вкладеність у своїх канонічних даних.
Це означає, що кожен елемент масиву cases може бути або:
Тип елемента можна визначити, перевіривши наявність полів, притаманних лише одному типу елементів.
Напевно, найкращий спосіб зробити це - скористатися ключем "cases", який присутній лише в групах тестових випадків.
Ось приклад вкладених тестових випадків:
{
"cases": [
{
"uuid": "e9c93a78-c536-4750-a336-94583d23fafa",
"description": "data is retained",
"property": "data",
"input": {
"treeData": ["4"]
},
"expected": {
"data": "4",
"left": null,
"right": null
}
},
{
"description": "insert data at proper node",
"cases": [
{
"uuid": "7a95c9e8-69f6-476a-b0c4-4170cb3f7c91",
"description": "smaller number at left node",
"property": "data",
"input": {
"treeData": ["4", "2"]
},
"expected": {
"data": "4",
"left": {
"data": "2",
"left": null,
"right": null
},
"right": null
}
}
]
}
]
}
Якщо трек не підтримує групування тестів, потрібно:
cases, щоб залишилися лише найглибші (листові) тестові випадкиВміст ключів input та expected тестових випадків дуже різниться.
Здебільшого це скалярні значення, як-от числа, булеві значення (англ. Boolean) або рядки тексту (англ. string), чи прості обʼєкти.
Однак іноді трапляються й складніші значення, які, ймовірно, вимагатимуть певної попередньої обробки, як-от лямбда-вирази в псевдокоді, масиви операцій, які треба виконати над кодом учня, тощо.
Тестові випадки мають необовʼязкове поле scenarios.
Генератор тестів може використовувати це поле, щоб особливим чином обробити певні тестові випадки.
Найпоширеніший випадок - ігнорувати певні типи тестів, наприклад тести зі сценарієм "unicode", бо мова треку може не підтримувати Unicode.
Повний перелік сценаріїв можна знайти тут.
Є кілька способів прочитати файли canonical-data.json:
problem-specifications (наприклад, https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json).problem-specifications як Git-підмодуль до репозиторію треку.configlet.
Розташування залежить від системи користувача, але можна скористатися configlet info -o -v d | head -1 | cut -d " " -f 5, щоб програмно отримати це розташування.Якщо трек хоче додати додаткові тестові випадки, специфічні для нього (яких немає в канонічних даних), один із варіантів - створити файл additional-test-cases.json, який Генератор тестів потім може обʼєднати з файлом canonical-data.json, перш ніж передати його шаблону для рендерингу.
Рушій шаблонів, який використовуватиметься, імовірно, буде специфічним для треку. В ідеалі шаблони мають бути якомога простішими, тож не варто перейматися дублюванням коду тощо.
Самі шаблони отримують свої дані від Генератора тестів, які вони перебирають, щоб відрендерити себе.
Щоб шаблони залишалися простими, може бути корисно трохи попередньо обробити дані на боці Генератора тестів або визначити якісь «фільтри» чи інший механізм розширення, який підтримують шаблони.
configlet - це основний інструмент для супроводу треку, і його можна використовувати, щоб:
bin/configlet create --practice-exercise <slug>
tests.toml наявної вправи: запустіть bin/configlet sync --tests --update --exercise <slug>
Завдяки цьому configlet - чудовий інструмент, який можна поєднувати з Генератором тестів для справді потужних робочих процесів.
Варто зробити використання Генератора тестів і простим, і потужним. Для цього радимо створити один або кілька файлів скриптів.
Можна вибрати будь-який формат файлу скриптів, який найкраще пасує треку. Скрипти оболонки та скрипти PowerShell - поширені варіанти, і обидва можуть добре працювати.
Ось приклад скрипта оболонки, який поєднує configlet і Генератор тестів, щоб швидко створити каркас нової вправи:
bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>
Перш ніж починати створювати Генератор тестів, радимо подивитися на кілька наявних Генераторів тестів, щоб відчути, як інші треки їх реалізували:
Якщо виникнуть запитання, форум - найкраще місце, щоб їх поставити. Обговорення на форумі про Генератор тестів для Rust і для JavaScript теж можуть бути корисними.
Радимо будувати Генератор тестів поступово, починаючи з мінімально життєздатного продукту.
Найпростіша версія читала б canonical-data.json вправи й просто передавала б ці дані шаблону.
Почніть з однієї вправи, бажано простої, як-от leap.
І лише коли це працюватиме, поступово додавайте більше вправ.
І намагайтеся тримати Генератор тестів якомога простішим.
В ідеалі контрибʼютор міг би просто вставити чи змінити наявний шаблон, навіть не розуміючи, як Генератор тестів працює всередині.
Те, як використовувати Генератор тестів чи долучатися до нього, залежить від треку.
Шукайте вказівки в README.md, CONTRIBUTING.md треку або в каталозі з кодом Генератора тестів.