Інтерфейс засобу запуску тестів


Тест-раннер має єдину відповідальність: узяти рішення, запустити всі тести й повернути уніфікований вивід. Усі взаємодії із сайтом Exercism відбуваються автоматично й не є частиною цієї специфікації.

Виконання

  • Тест-раннер має надавати виконуваний скрипт. Більше інформації можна знайти у файлі docker.md.
  • Скрипт отримуватиме три параметри:
    • Слаг вправи (наприклад, two-fer).
    • Шлях до каталогу вхідних даних (із завершальною рискою), який містить надіслані файли рішення та будь-які інші файли вправи. Цей каталог слід вважати доступним лише для читання. Технічно записувати в нього можна, але краще використовувати /tmp для тимчасових файлів (наприклад, для компіляції джерел).
    • Шлях до каталогу вихідних даних (із завершальною рискою). У цей каталог можна записувати.
  • Скрипт має записати файл results.json у каталог вихідних даних.
  • Раннер має завершитися з кодом виходу 0, якщо він відпрацював успішно, незалежно від статусу тестів.

Дозволений час виконання

Тест-раннер отримує 100% CPU і 3 ГБ памʼяті на 20-секундне вікно для кожного рішення. Після 20 секунд процес зупиняється й повідомляє про перевищення часу.

Note

Ми наполегливо радимо дотримуватися нашого документа з найкращими практиками продуктивності, щоб зменшити ймовірність перевищення часу.

Формат вихідних даних

У файлах results.json підтримуються такі поля:

Верхній рівень

Версія

ключ: version, тип: number, наявність: обовʼязкова

версія: 1, 2, 3

Версія специфікації, якої дотримується цей файл:

  • 1: для треків, чий тест-раннер не може надати інформацію про окремі тести.
  • 2: для треків, чий тест-раннер може виводити інформацію про окремі тести. Мінімальна обовʼязкова версія для треків із концептуальними вправами.
  • 3: для треків, чий тест-раннер може привʼязати окремі тести до завдання.

Статус

ключ: status, тип: string, наявність: обовʼязкова

версія: 1, 2, 3

Дійсними є такі загальні статуси:

  • pass: усі тести пройдено
  • fail: принаймні один тест має статус fail або error
  • error: жоден тест не виконано (зазвичай це означає помилку компіляції або синтаксичну помилку)

Статус error слід використовувати лише тоді, коли всі тести завершилися з помилкою. Для компільованих мов це зазвичай наслідок того, що код не компілюється. Для інтерпретованих мов це помилка часу виконання, наприклад синтаксична помилка, через яку файл не розбирається.

Повідомлення

ключ: message, тип: string, наявність: обовʼязкова, якщо status = error, або коли status = fail і version = 1

версія: 1, 2, 3

Коли статус error (жоден тест не виконано правильно), слід надати ключ message верхнього рівня. Він має показати користувачеві помилку, що сталася. Оскільки це єдина інформація, яку користувач отримає про те, як налагодити свою проблему, вона має бути якомога зрозумілішою:

  • Спростіть шляхи до чогось на кшталт <solution-dir>/relative/path замість /full/path/to, бо там будуть некорисні дані, специфічні для ECR
  • Коли можливо або доречно, згортайте стеки, що не належать до коду користувача
  • Ніколи не показуйте стеки викликів без контексту (тобто без повідомлення про помилку)
  • Не змінюйте повідомлення про помилку (якщо це можливо), бо так його буде легше знайти в пошуку

У Ruby в разі синтаксичної помилки ми надаємо помилку часу виконання та стек викликів. Для компільованих мов слід надавати помилку компіляції.

Значення message верхнього рівня обмежене 65535 символами. Якщо значення містить багатобайтові символи, фактична максимальна довжина менша.

Коли статус не error, або встановіть значення null, або взагалі пропустіть ключ.

Тести

ключ: tests, тип: array, наявність: обовʼязкова, якщо status = fail або status = pass

версія: 2, 3

Це масив результатів тестів, описаних у розділі «Для кожного тесту» нижче.

Тести ПОВИННІ повертатися в тому порядку, у якому їх задано у файлі тестів. Для мов, які виконують тести у випадковому порядку, це може означати перевпорядкування результатів відповідно до порядку, заданого у файлі тестів.

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

Для кожного тесту

Назва

ключ: name, тип: string, наявність: обовʼязкова

версія: 2, 3

Це назва тесту в зручному для читання форматі.

Код тесту

ключ: test_code, тип: string, наявність: обовʼязкова, якщо вправа є концептуальною вправою

версія: 2, 3

Це поле ПОВИННЕ бути присутнім для концептуальних вправ і МАЄ бути присутнім для практичних вправ. Ця різниця у вимогах випливає з того, що в концептуальних вправах учням не показують тести, тож розвʼязати вправу може бути неможливо без показаного test_code, тоді як для практичних вправ тести показано.

Це тіло команди, яку тестують. Наприклад, такий тест на Ruby:

def test_duplicate_items_uniqs_list
  cart = ShoppingCart.new
  cart.add(:STARIC)
  cart.add(:MEDNEW)
  cart.add(:MEDNEW)
  assert_equal 'Newspaper, Rice', cart.items_list
end

має повернути таке значення test_code:

"cart = ShoppingCart.new
cart.add(:STARIC)
cart.add(:MEDNEW)
cart.add(:MEDNEW)
assert_equal 'Newspaper, Rice', cart.items_list"

(з переносами рядків, заміненими на \n, щоб JSON був валідним).

Статус

ключ: status, тип: string, наявність: обовʼязкова

версія: 2, 3

Дійсними є такі статуси для кожного тесту:

  • pass: тест пройдено
  • fail: тест не пройдено
  • error: тест завершився з помилкою, тобто не повернув значення

Повідомлення

ключ: message, тип: string, наявність: обовʼязкова, якщо status має значення fail або error

версія: 2, 3

Ключ message для кожного тесту використовують, щоб повернути результати тесту зі status fail або error. Він має бути якомога зручнішим для читання. Усе, що тут написано, буде показано учневі, коли його тест не проходить. Якщо немає повідомлення про невдачу тесту чи повідомлення про помилку, або встановіть значення null, або взагалі пропустіть ключ. Тут також дозволено виводити вивід набору тестів. Значення message не обмежене за довжиною.

Вихідні дані

ключ: output, тип: string, наявність: необовʼязкова

версія: 2, 3

Ключ output для кожного тесту слід використовувати, щоб зберігати й виводити все, що користувач навмисно виводить для тесту.

  • Його слід додавати до всіх результатів тестів, які містять вивід користувача.
  • Показувати слід лише вміст, який користувач вивів вручну, а не автоматичний вивід тест-раннера.
  • Можна або перехоплювати вміст, який виводять звичайними засобами (наприклад, puts у Ruby, print у Python або Debug.WriteLine у C#), або надати метод, яким користувач може скористатися (наприклад, тест-раннер Ruby дає користувачеві глобально доступний метод debug, який той може використати й який має ті самі характеристики, що й стандартний метод puts).
  • Вивід має бути обмежений 500 символами. Прийнятними є або обрізання з повідомленням «Вивід обрізано. Будь ласка, обмежте його до 500 символів», або повернення помилки в цій ситуації.

Ідентифікатор завдання

ключ: task_id, тип: number, наявність: необовʼязкова

версія: 3

Привʼяжіть тест до конкретного завдання через ідентифікатор завдання, тобто число на початку заголовка завдання. Привʼязуйте тест до завдання лише тоді, коли його можна привʼязати рівно до одного завдання.

Наразі лише концептуальні вправи мають чітко визначені завдання, до яких можна привʼязати тести, але в майбутньому це може змінитися.

Наприклад, розгляньмо такий файл instructions.md:

# Instructions

You're going to write some code to help Lucian cook an exquisite lasagna from his favorite cook book.

## 1. Define the expected oven time in minutes

...

## 2. Calculate the remaining oven time in minutes

...

Ці інструкції визначають два завдання:

  1. Визначити очікуваний час у печі в хвилинах
  2. Обчислити решту часу в печі в хвилинах

Тоді файл results.json міг би містити такий запис:

{
  "name": "Expected oven time in minutes",
  "status": "pass",
  "task_id": 1,
  "test_code": "Assert.Equal(40, Lasagna.ExpectedMinutesInOven());"
}

Тепер цей тест привʼязано до першого завдання: «Визначити очікуваний час у печі в хвилинах». Зауважте, що назва не мусить збігатися з описом завдання.

Треки можуть реалізувати це різними способами:

  • Додати метадані до тестів у файлі тестів (наприклад, через атрибути, анотації чи коментарі) і змусити тест-раннер читати ці метадані під час запуску тестів.
  • Зберігати відповідність назв тестів та ідентифікаторів завдань в окремому файлі (як-от файл .meta/config.json вправи) і додавати цю інформацію до згенерованого файлу results.json.

Приклади

Ось приклади того, як може мати вигляд валідний файл results.json для різних версій:

Приклад v1

{
  "version": 1,
  "status": "fail",
  "message": "Failed: test_answer\nExpected: 42, actual: 3"
}

Приклад v2

{
  "version": 2,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()"
    }
  ]
}

Приклад v3

{
  "version": 3,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()",
      "task_id": 1
    }
  ]
}

Питання UI/UX

Коли тест не проходить

Коли рішення учня не проходить тест, має показуватися щось таке:

Test Code:
  <test_code>

Test Result:
  <message>

Коли тест проходить

Коли рішення проходить тест, має показуватися щось таке:

Test Code:
  <test_code>

Як додати метадані для набору тестів своєї мови

Усі дороги ведуть до Риму, і жодного припису, як саме цього досягти, немає. Досі застосовували кілька підходів:

  • Допоміжні JSON-файли, скомпільовані вручну й обʼєднані з результатами тестів під час виконання тестів.
  • Автоматичний статичний аналіз набору тестів, обʼєднаний з результатами тестів під час виконання тестів.
    • Це можна зробити за допомогою аналізу AST або текстового розбору