Молодогвардейцев 454015 Россия, Челябинская область, город Челябинск 89085842764
MindHalls logo

Реализация теста на знание языка на PHP

Доброго времени суток всем. Это моя первая запись про веб-программирование, в ней я расскажу о том, как реализовывал тестирование на знание английского языка для сайта языкового клуба в Челябинске. Писал на небезызвестном языке программирования, а именно PHP. Его я начал изучать незадолго до того, как создал этот самый блог, который часто использую в качестве «песочницы» для тестирования. Но ведь кроме песочницы, мой блог это еще и копилка знаний, поэтому сразу к делу!

Задача

Дать возможность посетителям сайта оценить свой уровень английского языка в режиме онлайн. Необходимо, чтобы ответ был получен сразу же, не привлекая к проверке живого человека. Признаюсь, первое и самое забавное решение, которое пришло мне в голову это отправлять заполненный тест преподавателю по почте(слава богу не голубиной), но это, конечно же, шутка. И самое главное желание, уже мое личное — не использовать плагины, написать все самому. Они создают лишнюю нагрузку, а мы хотим, чтобы даже на IE6 можно было пройти наш тест. А еще в них может быть вшит какой угодно вредоносный код и вообще я же программист, верно?

В тесте 40 вопросов, которые разбиты на 4 логические секции. К сожалению, они разной длины, это чуть усложнило исходный код. Каждая секция оценивается отдельно, в итоге пользователю сообщается о его уровне в каждой секции. У вопроса 4 варианта ответа, один верный.

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

Реализация

Приступим к реализации. Писать я твердо решил на PHP, не привлекая JavaScript и прочие радости. Все вопросы было решено поместить в форму и прилепить к ним кнопку «отправить результаты». Варианты ответа в виде радиокнопок, чтобы хитрецы не выбирали все подряд. Списки вопросов, вариантов ответа и правильные ответы были мной заботливо переписаны в массивы. Из файла читать я не захотел, хотя это было бы разумнее и красивее. Все мое внимание было сосредоточено на функции проверки результатов. Вот, как выглядит вывод вопросов на страницу. Определение массивов я стер, оставил только объявление.

Исходный код на php

$questions = array(
    /* Один вопрос - один элемент массива */
    );

$answers = array(
        /* Один вариант ответа - один элемент массива */
        );

//Value радиокнопок с правильными ответами
$keys = array("q0b", "q1b", "q2c", "q3d", "q4d", "q5c", "q6c", "q7a", "q8b", "q9d", "q10b", "q11b", "q12a", "q13a", "q14c", "q15b", "q16a", "q17d", "q18b", "q19a", "q20c", "q21c", "q22a", "q23a", "q24c", "q25b", "q26b", "q27c", "q28a", "q29b", "q30a", "q31c", "q32d", "q33a", "q34a", "q35c", "q36b", "q37c", "q38a", "q39b");

//Приветствие, либо результаты
if($showResults == False) {
    echo "<p>Здравствуйте, ниже вам будет предложено ответить на 40 вопросов с вариантом выбора ответа. В каждом вопросе только один правильный вариант ответа, время на прохождение теста не ограничено. По результатам вам будет присвоен предварительный уровень подготовки по английскому языку. Удачи!</p>
        <h2>Choose the best word or phrase (a, b, c or d) to fill each blank.<br></h2>";
}
else {
    echo "<p>Ваши результаты:</p>";
}

//Отображение теста
if($showResults == False) {
    echo "<form method='post'>";
    for($i = 0; $i < count($questions); $i++) {
        if($i == 0) {
            echo "<h2>Section 1</h2>";
        }
        else if($i == 12) {
            echo "<h2>Section 2</h2>";
        }
        else if($i == 20) {
            echo "<h2>Section 3</h2>";
        }
        else if ($i == 32) {
            echo "<h2>Section 4</h2>";
        }

        echo "<p>", $questions[$i], "</p>";

        for($j = $i * 4; $j < $i * 4 + 4; $j++) {
            echo '<input type="radio" name="q'.$i.'" value="q'.$i.$answers[$j][0].'" />'.$answers[$j];
        }
    }

    echo '<br>';
    echo "<input type='submit' value='Посмотреть результат'>";
    echo "</form>";
}

Тест заполняется, пользователь нажимает на кнопку и в бой вступает алгоритм подсчета правильных ответов. Сравниваем ответ, полученный от формы с правильным и инкрементируем нужный счетчик. Вот сам алгоритм:

//Считаем правильные ответы
$numberOfRightAns = array(0, 0, 0, 0); //Количество правильных ответов в каждой из 4 секций
for($num = 0; $num < count($questions); $num++) {
    if(isset($_POST['q'.$num])) {
        if($_POST['q'.$num] == $keys[$num]) {
                if($num < 12) { /* Section 1 */ 
                $numberOfRightAns[0]++;
            }
            else if($num < 20) { /* Section 2 */ 
                $numberOfRightAns[1]++;
            }
            else if($num < 32) { /* Section 3 */ 
                $numberOfRightAns[2]++;
            }
            else { /* Section 4 */ 
                $numberOfRightAns[3]++;
            }
            
        }
    }
}

//Делаем вывод о результатах теста
$langLevel = array('', '', '', '');
for($i = 0; $i < count($numberOfRightAns); $i++) {
    switch ($i) {
        case 0:
            if($numberOfRightAns[$i] < 6) {
                $langLevel[$i] = 'Starter';
            }
            else if($numberOfRightAns[$i] < 9) {
                $langLevel[$i] = 'Elementary';
            }
            else if($numberOfRightAns[$i] < 10) {
                $langLevel[$i] = 'Pre-intermediate';
            }
            else {
                $langLevel[$i] = 'Intermediate';
            }

            break;
        
        case 1:
            if($numberOfRightAns[$i] < 3) {
                $langLevel[$i] = 'Starter';
            }
            else if($numberOfRightAns[$i] < 5) {
                $langLevel[$i] = 'Elementary';
            }
            else if($numberOfRightAns[$i] < 6) {
                $langLevel[$i] = 'Pre-intermediate';
            }
            else {
                $langLevel[$i] = 'Intermediate';
            }

            break;

        case 2:
            if($numberOfRightAns[$i] < 3) {
                $langLevel[$i] = 'Starter';
            }
            else if($numberOfRightAns[$i] < 5) {
                $langLevel[$i] = 'Elementary';
            }
            else if($numberOfRightAns[$i] < 8) {
                $langLevel[$i] = 'Pre-intermediate';
            }
            else {
                $langLevel[$i] = 'Intermediate';
            }

            break;

        case 3:
            if($numberOfRightAns[$i] < 3) {
                $langLevel[$i] = 'Starter';
            }
            else if($numberOfRightAns[$i] < 4) {
                $langLevel[$i] = 'Elementary';
            }
            else if($numberOfRightAns[$i] < 7) {
                $langLevel[$i] = 'Pre-intermediate';
            }
            else {
                $langLevel[$i] = 'Intermediate';
            }
            
            break;
    }
}

//Сообщаем результаты и предлагаем пройти тест еще раз
if($showResults == True) {
    for($i = 0; $i < count($langLevel); $i++) {
        echo "<p>In section ", $i + 1, ": ", $langLevel[$i], "</p>";
    } 

    echo "<form method='post'>";
    echo "<input type='submit' value='Пройти тест еще раз'>";
    echo "</form>";
}

Как вы могли заметить, самая интересная особенно, а именно сокрытие теста после отправки результатов, была реализована самым обыкновенным флагом $showResults. Люблю использовать флаги, ничего не могу с собой поделать. Если при отправке в форме был хоть один ответ, мы считаем и показываем результаты.

//Флаг, что показывать в данный момент, тест или результаты
$showResults;
//Пробежать по всем радиокнопкам и посмотреть есть ли хоть один ответ, если есть, то показать результаты теста
for($i = 0; $i < count($keys); $i++) {    
    if(isset($_POST['q'.$i])) { 
        $showResults = True;
        break;
    }
    else {
        $showResults = False;
    }
}

Заключение

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

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

На этом все, спасибо за внимание!

  • Хорошая работа, Кирилл. Дерзну предложить решение, более лояльное к структуре. Массив исходных данных записать так:
    $db = array(
    0 => array( # section 0
    0 => array( # item 0 in section 0
    ‘q’ => ‘question 0’,
    ‘a’ => array( ‘answer 0’, ‘answer 1’, … ),
    ‘r’ => 1 # number of right answer
    )
    1 => …
    ),
    1 => array( # section 1
    0 => …
    ),

    );
    Перебирать в цикле $i секции, в цикле $j вопросы в секции, в цикле $a варианты ответов, выводя вопросы примерно таким образом:
    $db[$i][$j][‘q’]
    $db[$i][$j][‘a’][$a]
    Чтобы после колдовства с синтаксисом php на выходе было так:
    section 1 que 2
    section 1 que 2 ans 3

    Тогда через $_POST[‘a’] будет передан двумерный массив, который будем сравнивать в двойном цикле с правильными ответами: $db[$i][$j][‘r’]

    Теперь скрипт не зависит от количества вопросов в секции и даже от количества вариантов ответов (даже если в одном вопросе их 2, а в другом 5).

    Сам массив $db можно записать в файл:
    file_put_contents( ‘db’, serialize( $db ) )
    Если возникнет желание написать админку для редактирования вопросов, ответов

    • Кузьминых Кирилл

      Спасибо за решение, правда душа моя не очень любит вложенные массивы, но да, иногда это гораздо структурированнее

  • $db[$i][$j][‘q’]
    <input type=»radio» name=»a[$i][$j]» value=»$a»&qt; $db[$i][$j][‘a’][$a]
    Чтобы после колдовства с синтаксисом php на выходе было так:
    section 1 que 2
    <input type=»radio» name=»a[1][2]» value=»3″&qt; section 1 que 2 ans 3

  • Vito

    Благодарю за статью!
    Отличный у вас сайт.