Delphi: Тест для Большого Дяди на все 100! Или пишем свою прогу для тестирования...

Delphi

Лозовский Александр


(klouniz@mail.ru)

Наглядное пособие по подниманию денег с чужих знаний

Сегодня я рылся в недрах своего жесткого друга и наткнулся на интересную прогу под названием hackstest. Если помнишь, это такой старенький консольный тест на тему "хакер ты или нет". Он позволял оценивать грамотность населения по шкале от computer illiterate (полный чайник) до Wizard (волшебник). Но поскольку я слегка подзабыл технику пробивки перфокарт и никогда не бросал компьютер с высоты более двух этажей, я не смог достичь уровня Гуру :). Это мне очень не понравилось, и решение написать собственный хактест пришло как-то само собой.

Поэтому сегодня ты легко сможешь узнать некоторые особенности современных тестов и, как следствие, никогда не останешься без денег - это очень популярные проги и заказы на них есть всегда. Главное - уметь искать и предлагать ;).

Особенности национального кодинга

Не надо думать, что тест - это 3 варианта ответа, где один из них правильный. На самом деле, такими не пользуются уже очень давно, поскольку доказано, что если посадить за него законченного тупицу, он способен сдать предмет на трояк, даже не зная при этом, о чем идет речь. Поэтому мы с тобой должны сделать так, чтобы прога удовлетворяла самым распространенным требованиям к коммерческому тесту. Почему к коммерческому? Да потому что для себя их пишут редко, а вот для большого дяди - это да :). Вузы ведь всегда хотят тестировать студентов, ординаторов и прочих магистров, фирмы - сотрудников, а уж всякие там повышения квалификации и нормативы... Это вообще клад для кодера - как ни крути, а чтобы написать тест, много ума и времени не надо. А это уже клад для штатного кодера. Если бы ты знал, сколько времени уходит у программеров одного медицинского факультета на создание такого теста, точнее, вдалбливание новых вопросов в старую оболочку, ты бы прослезился :). Так вот, по научному эти требования называются: "Психолого-педагогические особенности тестовой формы контроля и методы составления тестовых заданий; их практическое применение при...". Хорошо звучит, а? Тебе еще повезло, что ты не читал этого монументального труда. Ладно, вот что выделит твое творение из толпы конкурентов:

1. Удобство ввода новых вопросов и редактирования старых. То есть, отдельная оболочка под это дело. Этим обычно занимаются разного рода секретарши, поэтому делай тщательную проверку от дурака на каждом этапе.

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

3. Несколько ответов. От одного до всех правильных. Такой подход сильно увеличивает объективность, что не может не радовать. Ну, экзаменатора, конечно :).

4. Защита. Не все хотят честно тестироваться, поэтому будь готов ко всему - от воровства до совращения секретарши. Итого - придется шифровать базу. Или хотя бы переименовывать.

5. Таймер. Тест - чаще всего задача на время. Скажем, одна минута на вопрос и никаких гвоздей. Коллективный разум вряд ли нужен твоему заказчику, а когда у людей появляется лишнее время, они им начинают пользоваться.

6. Оценка. Самая модная оценка такая: за 1 вопрос (сколько бы там ни было вариантов) - максимум 1 балл. За каждый правильный ответ, соответственно, 1/количество вариантов ответов. Теперь представь такую ситуацию: в твоем тесте 38 вопросов - по 1 баллу за вопрос. А теперь ответь мне, разве красиво будет выглядеть фраза: "Вы набрали 14 баллов из 38 возможных"? Конечно нет, человеческий мозг лучше воспринимает круглые цифры с большим количеством нулей :). Поэтому приводи максимальное число баллов к 100 с помощью пропорции. Согласись, 50 из 100 - это круто :).

Реквизит

На этот раз нам с тобой ничего не придется качать, потому что все вопросы и ответы будут храниться в XML таблице, а для ее использования нужен только файл midas.dll, который находится в системной директории - c:\windows\system для Win 9x или \system32 для NT-XP. Достаточно лишь зарегистрировать его с помощью regsvr32.exe и можно работать. Регистрируй ручками: Пуск -> Выполнить -> с:\windows\system\regsvr32.exe с:\windows\system\midas.dll -> Ok. Или программно, тогда тебе в этом помогут процедуры WinExec или CreateProcess. Хотя это, конечно, изврат.

Часть 0 - Ваяем таблицу

Для начала неплохо бы написать оболочку для добавления вопросов в базу - девяностые годы прошли, и мы обязаны позаботиться об удобстве пользователя. Надеюсь, ты уже запустил Delphi и зарегистрировал midas.dll? Тогда поехали.

Клади на форму компоненты DataSource и ClientDataSet1 (из вкладки Data Access палитры компонент). Свяжи эти два компонента, для этого свойство DataSet у компонента DataSource должно быть ClientDataSet1. Теперь выдели компонент ClientDataSet1 и дважды щелкни на его свойство FieldDefs - появится окно редактора полей. Выдели его и нажми клавишу Insert (на клавиатуре, где-то чуть правее Enter :)) или кнопочку Add в самом редакторе. Появится новое поле, у которого есть 3 занимательных свойства:

DataType - тип (численные, строки, memo и т.п.)

Name - имя поля

Size - размер

Создай 4 новых поля и расставляй им следующий свойства:

Поле 0:

Name - Key1, DataType - ftAutoInc. Это будет ключевое поле.

Поле 1:

Name - Quest, DataType - ftString, Size - 50. Оно, как нетрудно догадаться, будет содержать вопрос теста, а максимальный размер его будет 50 символов. Этого должно хватить, хотя на всякий пожарный можно сделать и больше.

Поле 2:

Name - Answers, DataType - ftMemo, Size - ставь 500. Чем больше, тем лучше, потому что оно будет содержать варианты ответов, а как я уже говорил, их может быть произвольное количество.

Поле 3:

Name - CorrectAnswer, DataType - ftString, Size-10. Тут будут номера правильных ответов. "Так что ж ты поставил тип String, раз это номера?" - спросит меня знакомый с информатикой читатель. А потому, батенька, отвечу я ему, что так проще и удобнее, поскольку юзер вводит номера в поле Edit в виде String, да мне и не нужно работать с ними, как с числами. В общем, пока поверь мне на слово, а потом поймешь, насколько это весело :).

Больше никакие поля мы добавлять не будем, поэтому щелкни правой кнопкой на ClientDataSet1 и выбери Create DataSet. Сделал? Молодец, теперь еще раз то же самое, только теперь менюшка будет ощутимо больше, и в ней появится пункт "Save To MyBase XML Table". Щелкни на него. В появившемся диалоговом окне введи имя файла, например, "xaktest", и жми "сохранить". Все, теперь именно он и будет содержать нашу табличку. Она практически готова, осталось только дать русские заголовки для полей (если, конечно, тебе не нравятся английские) и сбацать соответствующий user interface для добавления вопросов.

Дважды кликни на ClientDataSet1, выдели появившееся окошко и нажми Ctrl-F. Таким образом ты добавляешь все поля в редактор (эта комбинация - эквивалент пункта popup-menu под названием "add all fields"). Теперь можешь выделить любое поле и устремить свой пристальный взор на object inspector. Там есть очень много интересных свойств. Например, свойство visible для ключевого поля можно установить в false, а DisplayLabel как раз и определяет заголовок поля, который будет показан юзеру (имена, записанные в FieldDefs, нисколько при этом не страдают). Я дал такие: "Вопросы", "Ответы", "Правильные ответы". А что, незатейливо, зато понятно. Ключевому полю я вообще ничего не дал, да ему это и не надо.

Часть 1 - Редактор вопросов

Скажу тебе по секрету, у меня всегда получаются простенькие интерфейсы. Поэтому юзеры меня недолюбливают, я злюсь и делаю проги с ключа {$APPTYPE CONSOLE} :). Ах, я же обещал консольную прогу... ладно, все еще впереди, а пока открой закладку Data Controls, достань оттуда компонент DBGrid и кинь его на форму. Ставь ему свойство DataSource в DataSource1, чтобы связать его (или ее?) с нашей таблицей, а align в alTop, пусть побудет сверху. Теперь клади 2 DBEdit и 1 DBMemo из той же закладки. Все они должны быть связаны с таблицей свойством DataSource и с отдельными ее полями с помощью DataField. Логично предположить, что у первого DBEdit’а оно будет Quest, у второго - CorrectAnswer, а у DBMemo - Answers.

Внесем ясность, а именно - 3 label, которые будут показывать, что и куда ВВОДИТЬ. Располагай их над соответствующими полями и давай caption’ы:

label1 - "Вводи вопрос теста:"

label2 - "Вводи список ответов:"

label3 - "Вводи правильные ответы (без пробела)"

Ну и под занавес - 2 кнопочки с caption’ами: "Создать новую запись" и "Зафиксировать". В OnClick первой пиши:

ClientDataSet1.Insert;

А для второй он будет побольше:

ClientDataSet1.Post;

ClientDataSet1.SaveToFile('xaktest.xml');

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

Да, и еще одна маленькая деталь-строка:

ClientDataSet1.LoadFromFile('xaktest.xml');

Она записана в обработчик OnCreate для формы и позволяет данным загружаться автоматически при запуске проги.

Если ты умеешь работать с TTable или TADOTable, то легко сможешь превратить этот пример в образец античного искусства :).

Обязательно возьми на диске или скачай исходник. Там я еще добавил кнопку "пронумеровать" - она автоматически добавит порядковые номера к вариантам ответов. Тогда тебе не придется делать это самому.

ХХХ ЗАГОЛОВОК ХХХ

Часть 2 - Тест

Я думаю, не стоит лишний раз напрягаться и делать его с нуля, потому что намного проще слегка модернизировать уже сделанное. Для этого надо заменить DBEdit2 обычным Edit1 - в него наш любимый юзер будет набивать ответы. Кнопка на форме будет только одна (но, как водится, БОЛЬШАЯ) - она будет иметь caption "Следующий вопрос >". Интерфейс должен выглядеть примерно так:

 

Еще должно присутствовать 3 Label, объясняющие юзеру что к чему. Они не должны вызвать у тебя никаких затруднений, поэтому я перейду к собственно кодингу - заметь, за все это время мы почти ничего и не написали. Так что объявляй глобальные переменные:

balls, totalballs, totvarans, lim, totcorrcount: integer;

Здесь balls - количество баллов, которые юзер получить за ОДИН ВОПРОС. Напомню, что правильных ответов может быть несколько, и каждый из них оценивается отдельно.

totalballs - количество баллов за весь тест. То есть, сумма всех balls.

totvarans - количество вариантов ответов для данного вопроса.

totcorrcount - количество правильных ответов к данному вопросу.

И одну константу: CONST limit=2;. Она будет определять количество вопросов, которое необходимо задать юзеру. У меня в базе на данный момент только 2 вопроса, поэтому она мне не очень нужна, но порядок есть порядок.

С переменными покончено - самое время глянуть на OnClick нашей единственной и неповторимой кнопки:

Вот такой простой, но малопонятный с виду код :). На самом деле здесь я посимвольно сравниваю юзерский вариант ответа с правильным, присваивая за каждый верный ответ по 100/totcorrcount баллов. То есть, всего за один вопрос можно получить 100 баллов. А если учесть, что за неправильные ответы просто не начисляются баллы (а надо бы обнулять balls), то меня можно назвать очень щедрым экзаменатором.

Переменная lim фигурирует как счетчик отвеченных вопросов, она сравнивается с константой limit, и если они равны, значит, юзер ответил на положенное ему количество вопросов и может получить заключение патологоанатома. Выдается оно в виде "n баллов из 100", как я и говорил вначале.

Кстати, n - порядковый номер символа из строки правильных ответов, i – то же самое, но для ответов по версии пользователя. Теперь добавь в OnCreate формы инициализацию переменных:

totalballs:=0;

lim:=0;

На этом наш тест можно считать завершенным. Конечно, условно, поскольку ты наверняка захочешь добавить к нему timer, сделать красивый интерфейс... а потом записаться на прием к Петру Ивановичу и сказать: "Вы знаете, по-моему, система текущей проверки знаний сотрудников с помощью подбрасывания монетки является несколько устаревшей, у меня есть план ее реорганизации".

Post Mortem

Напоследок я хочу рассказать тебе один интересный случай из практики. Несколько месяцев назад мне пришлось сдавать тест. Сделан он был в далеком 92-ом в каком-то питерском институте и имел прекрасный борландовский интерфейс. Так вот, вместо заявленных 20 я ответил на 40 вопросов и на несколько ситуационных задач, получив в итоге 0.4 балла из 10 возможных. Я знал лучше, что и подтвердил стоявший за моей спиной препод. Так как же удалось профессиональным кодерам написать такую глючную прогу? Проблема, видимо, в ее простоте :), поэтому автор слегка расслабился, что едва не обернулось для меня весьма печально. Мораль: читай свой исходник. Как бы банально это ни звучало.

Листинг procedure TForm1.BitBtn1Click

procedure TForm1.BitBtn1Click(Sender: TObject);

var n, i: integer;

userans, corranswer: string;

begin

IF Edit1.Text<> '' then //Если хоть что-то написано, то

begin

n:=1; i:=1; //Инициализируем переменные

totvarans:= DBMemo1.Lines.Count; //Сколько вариантов

totcorrcount:= length(ClientDataSet1CorrectAnswer.Value);//Сколько из них правильных

userans:= Edit1.text;//Возьмем вариант юзера

corranswer:= ClientDataSet1CorrectAnswer.Value;//И возьмем ПРАВИЛЬНЫЙ вариант :)

balls:=0; //Пока 0 баллов

REPEAT //Начнем последовательно сравнивать - каждый вариант юзера со всеми правильными

IF userans[i]=corranswer[n] then //Если правильно, то

begin

Inc(i);//Проверим следующий пользовательский

n:=1; //Начиная с первого символа правильного ответа

balls:= balls+(100 div totcorrcount); //Вычислим, скольких баллов достоин

end else //А если неправильно

begin

IF n<>length(corranswer) then INC(n) else //если правильные кончились, значит юзер

//не прав и этот ответ не засчитывается. Если не кончились, то сравним со следующим

begin

INC (i); //Перейдем к следующему варианту

n:=1; //Сравнивать будем с первым правильным

end;

end;

UNTIL i>length(userans); //Завяжем, если кончатся все варианты

TotalBalls:= totalballs + balls; //Подсчитаем итоговое число баллов

Inc(lim); //Еще один вопрос отвечен...

IF lim=limit then //Если лимит достигнут, то

ShowMessage ('Вы набрали '+ inttostr((totalballs*100) div (limit*100))+' из 100 возможных!')

//Пересчитаем общее число баллов на 100 и выдадим заключение

else Clientdataset1.Next; //Иначе перейдем к следующему вопросу

end; //Здесь ты можешь поставить else на случай пустого Edit’а

end;



Опубликовал admin
19 Ноя, Среда 2003г.



Программирование для чайников.