Как создать собственную платформу для тестирования кодирования с помощью Node.js
В наши дни все больше и больше компаний используют тесты кодирования при приеме на работу разработчиков программного обеспечения. Такие сайты, как HackerRank, Codility и т. Д., Помогают компаниям проводить эти тесты и оценивать кандидатов на основе производительности их кода при выполнении некоторых тестовых примеров.
В этой статье я опишу, как создать простое веб-приложение для администрирования теста кодирования. Веб-приложение представит пользователям проблему, позволит пользователю отправить свое решение, запустить решение в некоторых предопределенных тестовых случаях и отобразить результаты пользователю.
Мы будем работать с Express.js для веб-сервера, а Python будет языковыми решениями, на которых будут представлены.
Подготовка сцены
Мы начнем с создания пользовательского интерфейса, который будет состоять из области вопросов и области кодирования.
Для этого сначала создадим по два элемента div для каждой области. Затем мы их стилизуем:
<div class="question"> <h2><strong>QUESTION</strong></h2> <span id="questionText"></span> </div> <div class="code-area"> <textarea rows="20" autofocus>def solution(arr):</textarea> </div>
Затем мы стилизуем два div:
.question, .code-area { padding: 25px; display: table-cell; width: 50%; } .question { text-align: left; position: absolute; top: 0vh; } .code-area { border-left: 2px solid navy; }
В приведенном выше CSS мы создаем две ячейки таблицы div (чтобы они могли располагаться рядом). Нам нужна таблица, в которой они будут содержаться. Для этого мы создадим содержащий div:
<div class="container"> <div class="question"> ... </div> <div class="code-area"> ... </div> </div>
А затем стилизуем его:
.container { display: table; height: 95vh; width: 95vw; }
Мы также добавим немного стиля к элементу body
, чтобы изменить цвет фона и шрифт:
body { background-color: aliceblue; text-align: center; font-size: 16pt; font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; }
Ранее мы создали элемент textarea
, текст которого по умолчанию был def solution(arr):
. Дадим этому textarea
класс и идентификатор:
<textarea rows="20" class="input" id="code" autofocus>def solution(arr):</textarea>
Давайте также создадим элемент button
для выполнения тестов и элемент span
для результатов:
... <textarea rows="20" class="input" id="code" autofocus>def solution(arr):</textarea> <br/><br/> <button>RUN TESTS</button> <br/><br/> <span id="results"></span> ...
А теперь давайте добавим стили к элементам:
.input, #results { height: 60%; } .input { border: 1px solid gray; width: 90%; font-family: monospace; font-size: 12pt; padding: 10px; } button { width: 150px; height: 50px; cursor: pointer; background-color: lightgreen; color: white; font-weight: bolder; }
На этом наша работа над дизайном пользовательского интерфейса завершена. Веб-страница теперь выглядит так:
Подготовка бэкенда
На другом конце веб-приложения мы начнем с импорта необходимых библиотек:
const bodyParser = require('body-parser'); const cors = require('cors'); const execSync = require('child_process').execSync; const express = require('express'); const fs = require('fs'); const path = require('path');
Нам нужно:
body-parser
, чтобы помочь нам с нашими запросами POSTcors
для запросов из разных источниковchild_process
,fs
иpath
для выполнения решения кода пользователя.express
, очевидно.
Затем мы создадим приложение Express и настроим его на использование необходимых нам зависимостей:
const app = express(); app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true }));
И, наконец, мы настроим конечные точки и заставим приложение прослушивать порт 5000:
function testCode(req, res) { return res.send("Success"); } app.get('/', (req, res) => { res.send("Hello world"); }); app.post('/test/', testCode); app.listen(5000, () => console.log(`Listening on port 5000.`), );
У нас есть две конечные точки выше:
/
: для обслуживания интерфейсной страницы и/test/
: для тестирования пользовательского кода.
Конечная точка /test/
при посещении вызывает функцию testCode
. Эта функция запишет код пользователя в файл Python, а затем запустит другой скрипт Python для проверки кода:
... const CODE_FOLDER = "code"; function testCode(req, res) { let code = req.body["code"]; try { fs.writeFileSync(path.join(__dirname, CODE_FOLDER, "input_code.py"), code); const proc = execSync("python3 " + path.join(CODE_FOLDER, "tests.py")); const results = proc.toString(); return res.send(results); } catch (error) { console.log("An error occurred"); console.log(error); return res.send("An error occurred."); } }
В приведенном выше коде код, который вводит пользователь, который будет отправлен в качестве параметра запроса с именем code
из внешнего интерфейса, будет сохранен в файле с именем input_code.py
, затем скрипт Python tests.py
будет выполнен с использованием функции execSync
узла (которая выполняет консольная команда, ожидает ее вывода и возвращает его). Затем вывод скрипта будет отправлен во внешний интерфейс.
Вот что содержит tests.py
скрипт:
from input_code import solution def get_test_cases(): pass def get_expected_outputs(): pass def test_code(): pass if __name__ == '__main__': test_code()
Он содержит три функции:
get_test_cases()
: чтобы тестовые примеры запускали код пользователяget_expected_outputs()
: чтобы получить ожидаемый результат для каждого тестового примераtest_code()
: для проверки решения пользователя на тестовых примерах.
Давайте заполним эти функции:
def get_test_cases(): return { "SMALL_INPUT": [1, 2, 3], "LARGE_INPUT": [1, 2, 3] * 1000 + [4], } def get_expected_outputs(): return { "SMALL_INPUT": 3, "LARGE_INPUT": 4, }
Эти первые две функции предоставляют нам ввод и вывод некоторых примеров тестов. Они также помечают тестовые примеры “SMALL_INPUT”
и “LARGE_INPUT”
для идентификации.
Теперь давайте используем их в третьей функции для проверки пользовательского кода:
def test_code(): test_cases = get_test_cases() expected = get_expected_outputs() test_cases_count = len(test_cases) passed_test_cases = 0 failed_test_cases = [] for label in test_cases.keys(): code_result = solution(test_cases[label]) if code_result == expected[label]: passed_test_cases += 1 else: failed_test_cases.append(label) print("Passed", passed_test_cases, "out of", test_cases_count, "test cases.") if len(failed_test_cases) > 0: print("Test cases not passed:", ", ".join(failed_test_cases))
С помощью приведенного выше кода мы перебираем каждый тестовый пример, вызываем решение пользователя на входе тестового примера и сравниваем результат пользователя с ожидаемым результатом.
Если результаты совпадают, мы добавляем единицу к количеству пройденных тестов; в противном случае мы добавляем метку неудачного тестового примера в массив.
Наконец, мы печатаем количество пройденных тестовых случаев, а если есть какие-то неудачные, печатаем их метки. Обратите внимание, что код пользователя должен содержать функцию с именем solution()
, которая будет передаваться через массив для каждого тестового примера и должна возвращать число.
На этом задняя часть завершена.
Соединение двух концов
Наша следующая задача - подключить переднюю часть к задней.
Для этого мы создадим функцию JavaScript, которая будет вызываться всякий раз, когда пользователь нажимает кнопку RUN TESTS
. Эта функция отправит код пользователя в серверную часть и отобразит результаты пользователя.
<script> function runTests() { document.getElementById("results").innerHTML = "Running..."; const code = document.getElementById("code").value; let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState == 4 && xhr.status == 200) { document.getElementById("results").innerHTML = xhr.responseText; } } xhr.open("POST", "http://localhost:5000/test/"); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.send("code=" + code); } </script>
Функция сначала получает код пользователя из textarea
с идентификатором code
, а затем использует AJAX для отправки запроса в конечную точку /test/
серверной части. Затем, когда он получает ответ, он устанавливает результаты в span
с идентификатором results
.
Давайте добавим этот обработчик событий к кнопке RUN TESTS
:
<button onclick="runTests()">RUN TESTS</button>
Тестирование приложения
Чтобы протестировать веб-приложение, давайте добавим простой вопрос в область вопросов:
<div class="question"> <h2><strong>QUESTION</strong></h2> Find the maximum number in an array of integers. <br/> For example, in the array [1, -3, 5], the maximum number is 5. </div>
Вопрос просто просит пользователя найти наибольшее число в массиве. Вот решение проблемы:
def solution(arr): maximum = arr[0] return maximum
Решение возвращает первый элемент в массиве (что явно неверно). Нажатие кнопки RUN TESTS
дает нам следующее:
Результаты теста говорят нам, что решение неверное, поскольку оно не проходит ни один тестовый пример. В нем также указано, что имена тестовых примеров не помогли пользователю правильно настроить свой код.
Теперь давайте попробуем со следующим кодом:
def solution(arr): maximum = 0 for elem in arr: if elem > maximum: maximum = elem return maximum
Это решение просматривает массив и проверяет, какой элемент является наибольшим, сравнивая каждый элемент с ранее известным наибольшим элементом. Скорее всего, это правильное решение, но давайте проверим его, чтобы подтвердить:
Код прошел оба тестовых случая! Наше веб-приложение для тестирования кодирования работает должным образом.
Спасибо, что дошли до этого места. Вы можете найти весь код веб-приложения в моем репозитории GitHub.