{"id":20645043,"url":"https://github.com/romanow/merge-github-autograder","last_synced_at":"2026-06-04T17:31:17.414Z","repository":{"id":146005659,"uuid":"489107840","full_name":"Romanow/merge-github-autograder","owner":"Romanow","description":"Report for Merge Conf","archived":false,"fork":false,"pushed_at":"2022-09-09T09:45:36.000Z","size":27514,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-17T09:31:26.717Z","etag":null,"topics":["autograding","education","github-actions"],"latest_commit_sha":null,"homepage":"https://mergeconf.ru/educationandcareer/education/romanov","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Romanow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-05T19:51:58.000Z","updated_at":"2022-05-17T15:47:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"b52217cd-72af-4de4-b755-0e19e94d935d","html_url":"https://github.com/Romanow/merge-github-autograder","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fmerge-github-autograder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fmerge-github-autograder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fmerge-github-autograder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fmerge-github-autograder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Romanow","download_url":"https://codeload.github.com/Romanow/merge-github-autograder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242714053,"owners_count":20173581,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autograding","education","github-actions"],"created_at":"2024-11-16T16:18:31.474Z","updated_at":"2026-06-04T17:31:17.381Z","avatar_url":"https://github.com/Romanow.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Автоматизация обучения с использованием GitHub\n\n[![License: CC BY-NC-ND 4.0](https://img.shields.io/badge/License-CC%20BY--NC--ND%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-nd/4.0/)\n\n## Зачем нужна автоматизация приема заданий?\n\nКогда идет речь про обучение разработчиков – самое ценное, это feedback, который преподаватель дает студентам по итогам\nвыполненного задания. Но когда в вашей группе 50+ человек и 5+ лабораторных работ, работать с каждым даже по 20 минут (а\nименно столько обычно занимает прием одной лабораторной) становится слишком затратно.\n\nНо делать это безусловно нужно.\n\nПрием лабораторной обычно делится на две части:\n\n* проверка корректности выполнения задания;\n* обсуждение как она была реализована и собственно сам feedback по этому решению.\n\nИ вот первый пункт как раз хотелось бы автоматизировать, потому что на него тратится минимум 50% времени приема. Плюс,\nиногда у меня была ситуация, когда я чувствовал, что студент сделал лабораторную неправильно, но не мог понять где у\nнего ошибка в реализации.\n\nОбычно такая ситуация происходила из-за того, что студент:\n\n* недопонял задание и сделал что-то другое;\n* неправильно понял реализацию.\n\nКак бы ни было хорошо описано задание, все равно найдется кто-то, кто сделает не то что нужно. Тут, несомненно,\nответственность преподавателя, но если задание уровня `Hello, World` описать во всех подробностях на 5 страниц, читать\nего никто не будет. Соответственно задание нужно формулировать кратно и четко описывать требования.\n\nЯ в своих лабораторных придерживаюсь такого шаблона:\n\n```markdown\n# Название\n\n## Формулировка\n\nКраткое описание проблематики и что нужно сделать.\n\n### Требования\n\nПодробно по пунктам, что нужно сделать, какие технологии использовать и какие есть _ограничения_.\n\n### Пояснения\n\nВ этом пункте стараемся подробно описать нюансы реализации, как и для чего использовать конкретные технологии (если\nприменимо).\n\nЭто самый важный пункт, т.к. в нем мы стараемся предусмотреть _все_ возникающие в процессе реализации у студента вопросы\nи дать на них ответы или ссылки на материалы.\n\n### Прием задания\n\nОписание шагов, как запустить и проверить корректность выполнения задания. Например, если у нас Java приложение,\nто `./gradlew clean test`.\n\n## Литература\n\nСсылки на статьи, которые помогут выполнить лабораторную работу.\n```\n\nВот примеры описания [homework1](https://github.com/Romanow-Education/homework1-template),\n[homework2](https://github.com/Romanow-Education/homework2-template).\n\n## Как будем делать автоматизацию?\n\nПомимо описания требований, еще очень полезно описывать критерии приема, понятные всем – **тесты**. Если тесты прошли –\nваша реализация рабочая, если нет – вот исходники, разбирайтесь. Этот подход очень близкий к реальной работе, т.к. если\nвы что-то исправили и тесты сломались – ответственность за их исправлении лежит на вас.\n\nМы не будем разбирать критерии полноты тестов для оценки правильности выполнения лабораторной: это очень большая и\nсложная тема, требующая отдельного разговора.\n\nПоговорим про то, как нам с помощью тестов проверить автоматизировать проверку правильности выполнения лабораторных.\n\n### Автоматизация через GitHub Actions\n\nДля работы со студентами я использую GitHub как публично доступный инструмент + репозитории на GitHub – это портфолио\nдля разработчиков.\n\nВ [2019](https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/)\nгоду [Github Actions](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions) стали\nпублично доступными и _бесплатными_ для публичных репозиториев.\n\nСоответственно, мы создаем репозиторий и в нем описываем шаги CI/CD. Если сборка проходит успешно – это _необходимое_\nусловие приема лабораторной.\n\n[classroom.yml](https://github.com/Romanow-Education/homework1-template/blob/master/.github/workflows/classroom.yml)\n\n```yaml\nname: Build project\non:\n  push:\n    branches: [ master ]\njobs:\n  build:\n    name: Autograding\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-java@v1\n        with:\n          java-version: 11\n      - uses: eskatos/gradle-command-action@v1\n        with:\n          arguments: clean build\n      - name: Test Report\n        uses: mikepenz/action-junit-report@v2\n        if: always()\n        with:\n          report_paths: '**/build/test-results/test/TEST-*.xml'\n```\n\nУспешная сборка – лишь _необходимое_ условие приема лабораторных, а _достаточным_ условием все равно остается очное\nобщение со студентом и обсуждение его реализации.\n\n### Прием через Fork + Pull Request\n\nИдем дальше: мы создали репозиторий, описание задание, теперь нужно это выдать студентам и потом собрать результат.\n\nСамый простой способ работы – это `Fork` репозитория и `Pull Request` по окончанию выполнения. Вариант простой и\nудобный, более того, вы можете в `Pull Request` писать feedback.\n\nНо здесь есть две проблемы:\n\n* `Pull Request` виден всем студентам, а значит сложно сдержаться от _просмотра чужих решений_;\n* в `Pull Request` не доступны ваши `Secrets`, а значит все токены и т.п. нужно прописывать в явном виде.\n\n### GitHub Classroom\n\nGitHub активно поддерживает обучение молодых разработчиков: у них образовательные курсы по Git и GitHub, они\nпредоставляют [Student Developers Pack](https://education.github.com/pack): набор программ и решений, бесплатных на\nвремя обучения. А самое главное, у них есть своя платформа для автоматизации приема\nлабораторных: [GitHub Classroom](https://classroom.github.com/).\n\nИз больших плюсов этого решения: здесь есть приватные репозитории и dashboard с агрегированной информацией по студентам.\n\nПосмотрим как пользоваться этим решением. (в примерах используем\nорганизацию [https://github.com/Romanow-Education/](https://github.com/Romanow-Education/))\n\n1. Создаем организацию: `New Organization` -\u003e `Romanow-Education`, `My Personal Account` -\u003e `Next` -\u003e `Complete Setup`.\n   ![Organization](images/organization.png)\n2. Создаем Classroom: `https://classroom.github.com/` -\u003e `New Classroom` -\u003e `demo-classroom` -\u003e `Create Classroom`.\n   ![Classroom](images/classroom-empty.png)\n3. Создаем Template Repository с шаблоном проекта и заданием для нашей\n   лабораторной: [homework1-template](https://github.com/Romanow-Education/homework1-template/). Важно в настройках\n   репозитория указать что это template: `Settings` -\u003e `General` -\u003e `Template Repository`.\n   ![homework1-template](images/homework1-template.png)\n4. В Classroom создаем задание (assignment): `New Assignment` -\u003e title: `Homework1`,\n   `Grant students admin access to their repository` -\u003e\n   `Add a template repository to give students starter code`: `homework1-template` -\u003e `Continue` -\u003e `Create Assignment`.\n   ![Assigment First Step](images/assingment-first-step.png)\n   ![Assigment Second Step](images/assignment-second-step.png)\n5. Для того чтобы сопоставить студентов с их репозиториями, нужно перед выдачей заданий в `Classroom` -\u003e `Students`\n   добавить список студентов.\n6. В результате будет создана ссылка вида `https://classroom.github.com/a/test-test`, которую нужно выдать студентам.\n   После того как студент перешел по ссылке, ему будет предложено выбрать свою фамилию имя из списка, а после на базе\n   template создается репозиторий в организации и студент появляются в dashboard.\n   ![Dashboard](images/dashboard.png)\n\n### Настройка Auto Grading\n\nИтак, мы создали Organization и Classroom, описали шаги в GitHub Actions, как теперь сделать, чтобы при успешной сборке\nв dashboard было отмечено успешное выполнение лабораторной?\n\nДля этого в папке `./github/classroom/` создается\nфайл [autograding.json](https://github.com/Romanow-Education/homework1-template/blob/master/.github/classroom/)\nследующего содержания:\n\n```json\n{\n  \"tests\": [\n    {\n      \"name\": \"Run tests\",\n      \"setup\": \"\",\n      \"run\": \"./gradlew clean test\",\n      \"input\": \"\",\n      \"output\": \"\",\n      \"comparison\": \"included\",\n      \"timeout\": 10,\n      \"points\": 10\n    }\n  ]\n}\n```\n\nА в папке `./github/workflows` манифест сборки нужно переименовать\nв [classroom.yml](https://github.com/Romanow-Education/homework1-template/blob/master/.github/workflows/classroom.yml) и\nдобавить там шаг:\n\n```yaml\n- uses: education/autograding@v1\n```\n\nЭтот шаг будет запускать проверки, описанные в `autograding.json` и в случае успешного завершения, отмечать в dashboard.\n\n![Successfully Run](images/actions.png)\n\nЕсли при прогоне тестов в шаге `education/autograding@v1` появляется ошибка:\n\n```\nAutograding failure: HttpError: Resource not accessible by integration\n```\n\nТо в настройках Organization открыть `Settings` -\u003e `Workflow permissions` -\u003e установить `Read and write permissions`\n(Workflows have read and write permissions in the repository for all scopes).\n\n### Использование тестов для автоматизированной проверки\n\nЕсли у нас Java приложение, то в `autograding.json` можно описать стандартную команду `./gradlew clean test`, а в самом\nприложении описать unit и интеграционные тесты с\nиспользованием [TestContainers](https://www.testcontainers.org/test_framework_integration/junit_5/).\n\nНапример, как в [homework1](https://github.com/Romanow-Education/homework1-template).\n\n![Auto Grading](images/autograding.png)\n\nНо что делать, если язык реализации не задан или для проверки функциональности unit тестов недостаточно?\n\nДля этого можно использовать [newman](https://www.npmjs.com/package/newman) – cli клиент для Postman, а тесты описывать\nкак end-to-end сценарии. Тут есть два нюанса:\n\n* newman (postman) – это http клиент, соответственно, его можно использовать только для проверки web сервера;\n* т.к. у нас web сервер, для проверки нужно его куда-то задеплоить (например\n  на [Heroku](https://devcenter.heroku.com/articles/how-heroku-works)), а в newman указывать внешний url.\n\n### Проблемы при использовании GitHub Classroom\n\nДля _приватных_ репозиториев в организации время использования GitHub Actions не бесконечное, в бесплатном пакете\nдоступно 2000 минут в месяц, которых при активном использовании группой в 50 человек хватает на неделю.\n\n![Billing](images/billing.png)\n\nДальше есть два варианта решения:\n\n* [покупка платного аккаунта](https://github.com/pricing): 3000 минут за 4$/user в месяц или 50000 минут за 21$/user в\n  месяц.\n* [поминутная оплата](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates):\n  0.008$/min на Linux runner;\n* использование\n  [self-hosted runner](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners):\n  можно просто развернуть свой runner на виртуальной машине в вашей компании, главное чтобы она была доступна из\n  интернета.\n\nПоследний вариант самый удобный, более того, если вы знаете, что будете использовать его для конкретного языка и\nтехнологий, то все необходимые зависимости можно сразу установить на виртуальную машину, тем самым пропуская шаг\nустановки и настройки зависимостей в actions.\n\nЕсли же требуется получить обобщенный runner под разные языки и технологии, то можно с помощью packer\nсобрать [образ](https://github.com/actions/virtual-environments/tree/main/images/linux), который GitHub использует для\nсвоих runner.\n\nНа каждый запуск action GitHub пересоздает runner, тем самым получая чистое окружение. Если использовать постоянный\nself-hosted runner, то он со временем забьется мусором и его чистить или пересоздавать.\n\n## Как вести учет лабораторных?\n\nПри работе с курсом студентов использование Classroom Dashboard не очень удобно, потому что там нет разбиения на группы\nи нет сводной таблицы по всем лабораторным. Для ведения сводной таблицы я\nиспользую [Google Sheet](https://docs.google.com/spreadsheets/d/1XbYbAtGO6dm4BQNDsFWmAQhdhdFOcUQakxpEJTaQI4Y/edit?usp=sharing)\n\n![Google Sheet](images/google-sheet.png)\n\nПереносить руками из Classroom Dashboard в Google Sheet очень неудобно, поэтому я написал\nсвой [github-auto-grader-mark action](https://github.com/marketplace/actions/github-auto-grader-mark), который по имени\nпользователя, запустившего сборку, ищет нужную строчу в Google Sheet и ставит пометку об успешном выполнении.\n\nЭтот action идет после запуска `education/autograding@v1`:\n\n```yaml\n- name: Github auto grader mark\n  uses: Romanow/google-sheet-autograder-marker@v1.0\n  with:\n    google_token: ${{secrets.GOOGLE_API_KEY}}\n    sheet_id: \"1XbYbAtGO6dm4BQNDsFWmAQhdhdFOcUQakxpEJTaQI4Y\"\n    homework_number: 2\n    mark: \"'+\"\n```\n\n![Google Sheet update](images/google-sheet-update.png)\n\nВзаимодействие выполняется Google Sheet API с использованием Service Account, токен для которого прописан в secret для\nOrganization.\n\n### Почему не используем Google Classroom\n\nЛогичным решением было бы использовать [Google Classroom](https://edu.google.com/workspace-for-education/classroom/):\nбесплатный и удобный LMS, тем более что в GitHub Classroom есть возможность интеграции для получения списка групп.\n\nНо в этой интеграции нет возможности связать успешное прохождение тестов в GitHub и прием задания в Google Classroom.\n\nСам процесс приема задания в Google Classroom выглядит следующим образом:\n\n* _студент_ прикрепляет материалы и нажимает `Отметить как выполненное`;\n* _преподаватель_ просматривает материалы и может вернуть работу.\n\nКак и в предыдущем решении, можно сделать эту интеграцию через Google Classroom API, но сдача работы должна выполняться\n_от имени студента_, а значит требуется для каждого студента заводить `Access Token`, что крайне усложняет\nавтоматизацию.\n\n## Какие еще есть проблемы?\n\nПоследний вопрос, который я бы хотел обсудить в рамках нашего доклада, это как определить, что студент списал работу?\n\nПервый способ, который приходит в голову: сравнение файлов. Но если брать конкретный язык и фреймворк, и формализованное\nзадание, то процент схожести работ будет 95%, потому что в рамках языка и фреймворка обычно есть только один нормальный\nвариант решения.\n\nПоэтому для задания, где задан язык и фреймворк, выявить что студент списал работу можно только при личном общении.\n\nВ своей практике я чаще всего определял что работа списана, если:\n\n* во время обсуждения студент терялся в навигации по коду;\n* не мог объяснить что делает конкретный метод;\n* я замечал какие-то странные и бросающиеся в глаза конструкции, которые я уже видел раньше, например:\n  ```kotlin\n  @RestController\n  @RequestMapping(\"/api/v1/eval\")\n  class CalculationController(private val calculationService: CalculationService) {\n      @PostMapping(consumes = [MediaType.TEXT_PLAIN_VALUE], produces = [MediaType.TEXT_PLAIN_VALUE])\n      fun eval(@RequestBody expression: String): String {\n          println(\"Vychislyaem vyrazhenie: $expression\") // \u003c-- например, такое\n          return calculationService.eval(expression).toString()\n      }\n  }\n  ```\n\nЕсли рассматривать проблему в общем без привязки к конкретному языку и в условиях более свободного задания на\nреализацию, то тут стоит выделить 3 основных шага:\n\n1. для каждого языка и фреймворка описать типовые файлы конфигурации, которые стоит исключить из рассмотрения, т.к. они\n   будут у всех одинаковые;\n2. в коде выделяются методы, и выполняется сравнение реализаций _конкретных методов_ нечетким сравнением, т.е. без учета\n   названия переменных и пробельных символов, а лишь учитывая схожесть синтаксических конструкций;\n3. задание высокой планки суммарной схожести кода под 85-90%, потому что схожесть реализации 2-3 методов не означает что\n   студенты списали друг у друга.\n\n## Замечания к докладу\n\n1. Преподавание программирование – это обучение навыкам.\n2. Переход к слайду с формулировкой задания: мы описываем шаблон, чтобы студенты все поняли.\n3. Рассказ, что такое тесты: черный ящик с данными на вход и ожиданием на выход.\n4. Почему GitHub: открытый, сразу у студентов есть портфолио, GitHub Student Pack.\n5. В конце презентации вставить ссылку на homework1, homework2.\n6. Подводка к слайду \"Проблемы при использовании GitHub Actions\": GitHub удобный и гибкий, но при активном использовании\n   может возникнуть проблема, что не хватает бесплатных минут.\n7. Автоматизировали приемку, теперь разберемся с тем, как собирать эти данные. Т.к. у нас несколько лабораторных, нам\n   нужно видеть агрегированную таблицу aka журнал.\n8. Все студенты сдали работу, у всех все отлично – такой ситуации не бывает, все равно кто-то списывает. Наша задача,\n   сделать эту задачу максимально сложной.\n\n## Литература\n\n1. [Linux Virtual Environments packer](https://github.com/actions/virtual-environments/tree/main/images/linux)\n\n## Контакты\n\nРоманов Алексей (mail: romanowalex@mail.ru, tg: @romanowalex)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromanow%2Fmerge-github-autograder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromanow%2Fmerge-github-autograder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromanow%2Fmerge-github-autograder/lists"}