Вернуться к списку задач

UnitTests

Таксономии
Instruction Following
Testing
Long Context Comprehension
Synthesis
Метрика
CodeBLEU
Языки
Python
Go
Java
C#
JavaScript

Описание задачи

Оценка генерации юнит-тестов для функций и методов на пяти языках программирования (Java, Python, Go, JavaScript и C#). Датасет содержит 2500 задач.

Тестируемые навыки моделей: Instruction Following, Long Context Comprehension, Synthesis, Testing

Авторы: Алена Пестова, Валентин Малых

Мотивация

Юнит-тестирование - это важная практика разработки программного обеспечения, при которой отдельные компоненты программной системы оцениваются изолированно.

Данный бенчмарк направлен на оценку способностей моделей генерировать юнит-тесты для методов и функций на пяти языках программирования - Java, Python, Go, JavaScript и C#.

Задача генерации юнит-теста формулируется следующим образом: дана функция или метод (далее фокальная/тестируемая функция/метод), и необходимо сгенерировать юнит-тест для нее (далее - тестовая функция/метод/тест).

Датасет ориентирован на инструктивные кодовые и мультимодальные модели.

Результаты оценки могут быть полезны:

  • исследователям в области автоматической генерации кода в целом и юнит-тестов в частности
  • разработчикам инструментов для автоматической генерации кода

На основании результатов можно будет сделать вывод о том, насколько сгенерированные моделью тесты схожи с тестами, написанными людьми.

Задача оценивает способность модели генерировать юнит-тесты для данной функции/метода, учитывая дополнительный контекст, собранный из проекта. Таким образом, также проверяется способность генерации кода на уровне проекта, что важно при использовании моделей для генерации юнит-тестов для реальных проектов.

Модели предоставляется инструкция, которая содержит

  • задачу (сгенерировать юнит-тест)
  • используемый язык программирования
  • текст метода/функции для тестирования,
  • путь к файлу метода/функции для тестирования, а также остальной код из этого файла
  • путь к файлу, где будет лежать сгенерированная тестовая функция
  • (опционально) тестовый фреймворк, который необходимо использовать
  • тип юнит-теста для генерации - метод или функция
  • дополнительный контекст из как бы существующего будущего тестового файла

Про дополнительный контекст из тестового файла:

Так как датасет собирался из файлов реальных репозиториев с гитхаба, в тестовых файлах проектов содержится не одна тестовая функция, а также могут быть необходимые импорты, переменные, вспомогательные функции и методы, а также сами тестовые функции на разные юниты. Понятно, что в базовом сценарии мы можем просто дать модели юнит (и даже какой-то дополнительный контекст для этого юнита) и попросить сгенерировать для него тест. Однако в таком случае появляется проблема, что модель не видела контекста из тестового файла. Если такие данные используются для обучения, то мы намеренно учим модель галлюцинировать, а именно испоьзовать в тестовой функции какие-то библиотеки, другие функции и классы, которые нигде не были описаны ранее. Если мы используем такие данные для тестирования, то получается, что мы предоставили модели очень ограниченный контекст и сравнение с человеческими тестами не самое честное.

Можно было бы просто давать модели весь остальной текст из тестового файла и просить сгенерировать таргетную тестовую функцию, но тогда появляется большая вероятность утечки данных - где-то в файле могла использоваться данная тестовая функция, к тому же другие тестовые функции тоже могли попасть в такой контекст. Поэтому нами было принято решение собирать из тестовых файлов некоторый урезанный контекст для каждой тестовой функции и передавать его модели. 

Метрикой оценки качества является CodeBLEU, оценивающая схожесть сгенерированного теста и теста, написанного человеком.

Описание датасета

Поля датасета

  • instruction [str] — Промпт-инструкция для модели, содержащая шаблон для вставки элементов вопроса.
  • inputs — Вводные данные, формирующие задание для модели. Могут включать одну или несколько модальностей - видео, аудио, изображение, текст.
    • focal_func [str] — тестируемая функция/метод для тестирования;
    • test_func_type [str] — тип теста (function/method);
    • test_func_context [str] — контекст тестовой функции, собранный из тестового файла - импорты, глобальные переменные, краткое описание других функций/классов;
    • language [str] — язык программирования (python, java, csharp, js, go);
    • focal_file_path [str] — путь к файлу тестируемой функции/метода в исходном репозитории;
    • test_file_path [str] — путь к файлу тестовой функции/метода в исходном репозитории;
    • focal_func_context [str] — контекст фокальной функции: текст фокального файла, откуда вырезан сам тестируемый метод и заменен на текст `#focal function/method here`;
    • test_framework [str] — тестовый фреймворк, который необходимо использовать (только для JS);
  • outputs [str] — Правильный ответ на вопрос.
  • meta — Метаданные, относящиеся к тестовому примеру, но не используемые в вопросе (скрытые от тестируемой модели).
    • id [int] — Номер-идентификатор вопроса в датасете.
    • repo_id [str] — Идентификатор репозитория;
    • focal_func_type [str] — тип тестируемоего объекта(function/method)

Промпты

Для задачи были подготовлены 20 промптов, которые были равномерно распределены по вопросам по принципу "один вопрос – один промпт". Шаблоны в фигурных скобках в промпте заполняются из полей внутри поля inputs в каждом вопросе.

Пример промпта:

"Сгенерируйте функцию на языке {language}.

Напиши тест для этого кода на языке {language} из файла '{focal_file_path}'.

Вот код, который надо протестировать:

{focal_func}

Тебе необходимо написать {test_func_type} на языке {language}. Тест будет помещен в файл '{test_file_path}'.

Обязательно учитывай код, собранный из будущего тестового файла:

{test_func_context}

Для тебя собран код из репозитория, который может помочь тебе в написании теста:

{focal_func_context}

Напиши только {test_func_type} без пояснений и комментариев. Не забывай соблюдать синтаксис языка {language}.

Оформи свой ответ с соблюдением markdown разметки для кода:```{language}

<your code>```

Создание датасета

Процесс сбора датасета состоял из следующих этапов:

1. Парсинг списка репозиториев, фильтрация списка и скачивание репозиториев.

2. Парсинг репозиториев, функций, методов и тестов

3. Сопоставление методов/функций и соответвующих им тестов.

Далее будут подробнее описаны эти этапы.

Список репозиториев для каждого языка был получен с помощью GitHub API. Мы выбрали репозитории только с открытыми лицензиями и те, у которых больше 10 звезд. Мы также отфильтровали fork-репозитории. Список используемых лицензий: MIT License, Apache License 2.0, The Unlicense, Mozilla Public License 2.0, BSD 2-Clause "Simplified" License, BSD 3-Clause "New" or "Revised" License, EPL 1.0 license, EPL 2.0 license, MPL 2.0 License, Unlicense License, 0BSD license.

При построении набора данных использовались одинаковые правила фильтрации для всех языков:

+ Пустые тесты удалены.

+ Из одного репозитория было собрано не более 200 пар метод-тест. Если пар было больше, они отбирались случайным образом.

+ Тестовый пример должен содержать не более 5000 символов. Это ограничение установлено для удаления из данных слишком длинных тестов.

+ Максимальная длина входных данных (тестируемая функция с контекстом) должна составлять не более 70000 символов.

+ Максимальное количество проверок (слово "assert" в тестовом примере) равно 20.

+ Для Python и Java была реализована дополнительная фильтрация тестов с синтаксическими ошибками (с использованием библиотек ast и javalang соответственно).

+ Обучающие данные были отфильтрованы на наличие дубликатов.

Для всех языков (кроме Python) для разбора кода использовался tree-sitter, в частности, для поиска и разбора функций/методов и классов, идентификации вызовов и т.д. Для Python мы использовали встроенную библиотеку ast.

После того, как мы спарсили все классы, методы и функции в репозитории, необходимо как-то понять, какой именно метод/функцию тестируют тестовые функции. То есть необходимо их сопоставить и сформировать список метод-тест. Методы/функции и юнит-тесты для них были сопоставлены с использованием метода, адаптированного из статьи (в работе сопоставляли только методы и тесты на Java). Далее будет кратко описано, как мы адаптировали этот метод для каждого языка.

+ В Java тестовые классы сопоставляются с фокальными классами по их путям в репозитории и именам. Затем методы фокального и тестового классов сопоставляются с помощью двух эвристик - имен и уникальности вызова метода.

+ Для Python все проанализированные функции и методы были сопоставлены с тестами в соответствии с правилами именования тестов в pytest. Исходя из логики, согласно которой одна функция может быть протестирована несколькими тестами, но один тест предназначен только для одной функции, в набор данных добавляются только тесты, соответствующие одной функции/методу.

+ Для Go идентификация тестовых функций и сопоставление их с фокальными функциями были выполнены в соответствии с практиками именования тестовых файлов и функций в библиотеке testing. Процедура сопоставления тестов была выполнена таким же образом, как и для Python.

+ Для C# фокальные и тестовые методы объединяются в пару, если название тестового метода включает в себя название фокального метода из репозитория и вызывает этот метод. В набор данных добавляются только тесты, сматченные с одним фокальным методом.

+ Для JavaScript также был проанализирован тестовый фреймворк, используемый в репозитории, путем поиска одной из следующих библиотек в зависимостях в файле "package.json": "mocha", "jest", "jasmine", "qunit", "nightwatch". Впоследствии название фреймворка было добавлено во инпут модели как одна из частей контекста тестового файла. В отличие от других языков, это необходимо, поскольку импорт часто не содержит информации о тестовом фреймворке. Если тестовый фреймворк из списка не был найден в зависимостях репозитория, тестовая функция все равно добавлялась в набор данных, но тестовый фреймворк определялся как "Unknown". Что касается сопоставления метода и теста, то это единственный язык, где оно было основано только на последнем вызове локального метода/функции, поскольку тестовые функции не имеют идентификаторов при объявлении в it() и test().

Метрики

Для агрегированной оценки ответов моделей используются следующие метрики:

CodeBLEU: Code BLEU учитывает соответствие двух частей кода, аналогичное исходному BLEU, но может также учитывать грамматическую и логическую корректность, используя абстрактное синтаксическое дерево и структуру потока данных.

Языки
Python
Go
Java
C#
JavaScript