2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * Unit tests for the multianswer question definition class.
21 * @subpackage multianswer
22 * @copyright 2011 The Open University
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') ||
die();
30 require_once($CFG->dirroot
. '/question/engine/tests/helpers.php');
31 require_once($CFG->dirroot
. '/question/type/multianswer/questiontype.php');
32 require_once($CFG->dirroot
. '/question/type/edit_question_form.php');
33 require_once($CFG->dirroot
. '/question/type/multianswer/edit_multianswer_form.php');
37 * Unit tests for the multianswer question definition class.
39 * @copyright 2011 The Open University
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 class qtype_multianswer_test
extends advanced_testcase
{
43 /** @var qtype_multianswer instance of the question type class to test. */
46 protected function setUp() {
47 $this->qtype
= new qtype_multianswer();
50 protected function tearDown() {
54 protected function get_test_question_data() {
58 $q->name
= 'Simple multianswer';
63 'Complete this opening line of verse: "The {#1} and the {#2} went to sea".';
64 $q->questiontextformat
= FORMAT_HTML
;
65 $q->generalfeedback
= 'Generalfeedback: It\'s from "The Owl and the Pussy-cat" by Lear: ' .
66 '"The owl and the pussycat went to see';
67 $q->generalfeedbackformat
= FORMAT_HTML
;
69 $q->penalty
= 0.3333333;
71 $q->stamp
= make_unique_id_code();
72 $q->version
= make_unique_id_code();
74 $q->timecreated
= time();
75 $q->timemodified
= time();
76 $q->createdby
= $USER->id
;
77 $q->modifiedby
= $USER->id
;
79 $sadata = new stdClass();
81 $sadata->qtype
= 'shortanswer';
82 $sadata->defaultmark
= 1;
83 $sadata->options
->usecase
= true;
84 $sadata->options
->answers
[1] = (object) array('answer' => 'Bow-wow', 'fraction' => 0);
85 $sadata->options
->answers
[2] = (object) array('answer' => 'Wiggly worm', 'fraction' => 0);
86 $sadata->options
->answers
[3] = (object) array('answer' => 'Pussy-cat', 'fraction' => 1);
88 $mcdata = new stdClass();
90 $mcdata->qtype
= 'multichoice';
91 $mcdata->defaultmark
= 1;
92 $mcdata->options
->single
= true;
93 $mcdata->options
->answers
[1] = (object) array('answer' => 'Dog', 'fraction' => 0);
94 $mcdata->options
->answers
[2] = (object) array('answer' => 'Owl', 'fraction' => 1);
95 $mcdata->options
->answers
[3] = (object) array('answer' => '*', 'fraction' => 0);
97 $q->options
->questions
= array(
105 public function test_name() {
106 $this->assertEquals($this->qtype
->name(), 'multianswer');
109 public function test_can_analyse_responses() {
110 $this->assertFalse($this->qtype
->can_analyse_responses());
113 public function test_get_random_guess_score() {
114 $q = test_question_maker
::get_question_data('multianswer', 'twosubq');
115 $this->assertEquals(0.1666667, $this->qtype
->get_random_guess_score($q), '', 0.0000001);
118 public function test_load_question() {
119 $this->resetAfterTest();
121 $syscontext = context_system
::instance();
122 /** @var core_question_generator $generator */
123 $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
124 $category = $generator->create_question_category(['contextid' => $syscontext->id
]);
126 $fromform = test_question_maker
::get_question_form_data('multianswer');
127 $fromform->category
= $category->id
. ',' . $syscontext->id
;
129 $question = new stdClass();
130 $question->category
= $category->id
;
131 $question->qtype
= 'multianswer';
132 $question->createdby
= 0;
134 // Note, $question gets modified during save because of the way subquestions
136 $question = $this->qtype
->save_question($question, $fromform);
138 $questiondata = question_bank
::load_question_data($question->id
);
140 $this->assertEquals(['id', 'category', 'parent', 'name', 'questiontext', 'questiontextformat',
141 'generalfeedback', 'generalfeedbackformat', 'defaultmark', 'penalty', 'qtype',
142 'length', 'stamp', 'version', 'hidden', 'timecreated', 'timemodified',
143 'createdby', 'modifiedby', 'idnumber', 'contextid', 'options', 'hints', 'categoryobject'],
144 array_keys(get_object_vars($questiondata)));
145 $this->assertEquals($category->id
, $questiondata->category
);
146 $this->assertEquals(0, $questiondata->parent
);
147 $this->assertEquals($fromform->name
, $questiondata->name
);
148 $this->assertEquals($fromform->questiontext
, $questiondata->questiontext
);
149 $this->assertEquals($fromform->questiontextformat
, $questiondata->questiontextformat
);
150 $this->assertEquals($fromform->generalfeedback
['text'], $questiondata->generalfeedback
);
151 $this->assertEquals($fromform->generalfeedback
['format'], $questiondata->generalfeedbackformat
);
152 $this->assertEquals($fromform->defaultmark
, $questiondata->defaultmark
);
153 $this->assertEquals(0, $questiondata->penalty
);
154 $this->assertEquals('multianswer', $questiondata->qtype
);
155 $this->assertEquals(1, $questiondata->length
);
156 $this->assertEquals(0, $questiondata->hidden
);
157 $this->assertEquals($question->createdby
, $questiondata->createdby
);
158 $this->assertEquals($question->createdby
, $questiondata->modifiedby
);
159 $this->assertEquals('', $questiondata->idnumber
);
160 $this->assertEquals($syscontext->id
, $questiondata->contextid
);
162 // Build the expected hint base.
164 'questionid' => $questiondata->id
,
165 'shownumcorrect' => 0,
169 foreach ($fromform->hint
as $key => $value) {
170 $hint = $hintbase +
[
171 'hint' => $value['text'],
172 'hintformat' => $value['format'],
174 $expectedhints[] = (object)$hint;
176 // Need to get rid of ids.
177 $gothints = array_map(function($hint) {
180 }, $questiondata->hints
);
182 $this->assertEquals($expectedhints, array_values($gothints));
185 $this->assertEquals(['answers', 'questions'], array_keys(get_object_vars($questiondata->options
)));
186 $this->assertEquals(count($fromform->options
->questions
), count($questiondata->options
->questions
));
189 $this->assertEquals([], $questiondata->options
->answers
);
191 // Build the expected questions. We aren't going deeper to subquestion answers, options... that's another qtype job.
192 $expectedquestions = [];
193 foreach ($fromform->options
->questions
as $key => $value) {
196 'category' => $category->id
,
197 'parent' => $questiondata->id
,
198 'name' => $value->name
,
199 'questiontext' => $value->questiontext
,
200 'questiontextformat' => $value->questiontextformat
,
201 'generalfeedback' => $value->generalfeedback
,
202 'generalfeedbackformat' => $value->generalfeedbackformat
,
203 'defaultmark' => (float) $value->defaultmark
,
204 'penalty' => (float)$value->penalty
,
205 'qtype' => $value->qtype
,
206 'length' => $value->length
,
207 'stamp' => $value->stamp
,
209 'timecreated' => $value->timecreated
,
210 'timemodified' => $value->timemodified
,
211 'createdby' => $value->createdby
,
212 'modifiedby' => $value->modifiedby
,
214 $expectedquestions[] = (object)$question;
216 // Need to get rid of (version, idnumber, options, hints, maxmark). They are missing @ fromform.
217 $gotquestions = array_map(function($question) {
218 unset($question->version
);
219 unset($question->idnumber
);
220 unset($question->options
);
221 unset($question->hints
);
222 unset($question->maxmark
);
224 }, $questiondata->options
->questions
);
225 // Compare questions.
226 $this->assertEquals($expectedquestions, array_values($gotquestions));
229 public function test_question_saving_twosubq() {
230 $this->resetAfterTest(true);
231 $this->setAdminUser();
233 $questiondata = test_question_maker
::get_question_data('multianswer');
234 $formdata = test_question_maker
::get_question_form_data('multianswer');
236 $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
237 $cat = $generator->create_question_category(array());
239 $formdata->category
= "{$cat->id},{$cat->contextid}";
240 qtype_multianswer_edit_form
::mock_submit((array)$formdata);
242 $form = qtype_multianswer_test_helper
::get_question_editing_form($cat, $questiondata);
244 $this->assertTrue($form->is_validated());
246 $fromform = $form->get_data();
248 $returnedfromsave = $this->qtype
->save_question($questiondata, $fromform);
249 $actualquestionsdata = question_load_questions(array($returnedfromsave->id
));
250 $actualquestiondata = end($actualquestionsdata);
252 foreach ($questiondata as $property => $value) {
253 if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated', 'options', 'hints', 'stamp'))) {
254 $this->assertAttributeEquals($value, $property, $actualquestiondata);
258 foreach ($questiondata->options
as $optionname => $value) {
259 if ($optionname != 'questions') {
260 $this->assertAttributeEquals($value, $optionname, $actualquestiondata->options
);
264 foreach ($questiondata->hints
as $hint) {
265 $actualhint = array_shift($actualquestiondata->hints
);
266 foreach ($hint as $property => $value) {
267 if (!in_array($property, array('id', 'questionid', 'options'))) {
268 $this->assertAttributeEquals($value, $property, $actualhint);
273 $this->assertObjectHasAttribute('questions', $actualquestiondata->options
);
276 array('id', 'category', 'parent', 'contextid', 'question', 'options', 'stamp', 'version', 'timemodified',
278 foreach ($questiondata->options
->questions
as $subqno => $subq) {
279 $actualsubq = $actualquestiondata->options
->questions
[$subqno];
280 foreach ($subq as $subqproperty => $subqvalue) {
281 if (!in_array($subqproperty, $subqpropstoignore)) {
282 $this->assertAttributeEquals($subqvalue, $subqproperty, $actualsubq);
285 foreach ($subq->options
as $optionname => $value) {
286 if (!in_array($optionname, array('answers'))) {
287 $this->assertAttributeEquals($value, $optionname, $actualsubq->options
);
290 foreach ($subq->options
->answers
as $answer) {
291 $actualanswer = array_shift($actualsubq->options
->answers
);
292 foreach ($answer as $ansproperty => $ansvalue) {
293 // These questions do not use 'answerformat', will ignore it.
294 if (!in_array($ansproperty, array('id', 'question', 'answerformat'))) {
295 $this->assertAttributeEquals($ansvalue, $ansproperty, $actualanswer);
302 * Verify that the multiplechoice variants parameters are correctly interpreted from
307 public function test_questiontext_extraction_of_multiplechoice_subquestions_variants() {
308 $questiontext = array();
309 $questiontext['format'] = FORMAT_HTML
;
310 $questiontext['itemid'] = '';
311 $questiontext['text'] = '<p>Match the following cities with the correct state:</p>
313 <li>1 San Francisco:{1:MULTICHOICE:=California#OK~Arizona#Wrong}</li>
314 <li>2 Tucson:{1:MC:%0%California#Wrong~=Arizona#OK}</li>
315 <li>3 Los Angeles:{1:MULTICHOICE_S:=California#OK~Arizona#Wrong}</li>
316 <li>4 Phoenix:{1:MCS:%0%California#Wrong~=Arizona#OK}</li>
317 <li>5 San Francisco:{1:MULTICHOICE_H:=California#OK~Arizona#Wrong}</li>
318 <li>6 Tucson:{1:MCH:%0%California#Wrong~=Arizona#OK}</li>
319 <li>7 Los Angeles:{1:MULTICHOICE_HS:=California#OK~Arizona#Wrong}</li>
320 <li>8 Phoenix:{1:MCHS:%0%California#Wrong~=Arizona#OK}</li>
321 <li>9 San Francisco:{1:MULTICHOICE_V:=California#OK~Arizona#Wrong}</li>
322 <li>10 Tucson:{1:MCV:%0%California#Wrong~=Arizona#OK}</li>
323 <li>11 Los Angeles:{1:MULTICHOICE_VS:=California#OK~Arizona#Wrong}</li>
324 <li>12 Phoenix:{1:MCVS:%0%California#Wrong~=Arizona#OK}</li>
327 $q = qtype_multianswer_extract_question($questiontext);
328 foreach ($q->options
->questions
as $key => $sub) {
329 $this->assertSame($sub->qtype
, 'multichoice');
330 if ($key == 1 ||
$key == 2 ||
$key == 5 ||
$key == 6 ||
$key == 9 ||
$key == 10) {
331 $this->assertSame($sub->shuffleanswers
, 0);
333 $this->assertSame($sub->shuffleanswers
, 1);
335 if ($key == 1 ||
$key == 2 ||
$key == 3 ||
$key == 4) {
336 $this->assertSame($sub->layout
, qtype_multichoice_base
::LAYOUT_DROPDOWN
);
337 } else if ($key == 5 ||
$key == 6 ||
$key == 7 ||
$key == 8) {
338 $this->assertSame($sub->layout
, qtype_multichoice_base
::LAYOUT_HORIZONTAL
);
339 } else if ($key == 9 ||
$key == 10 ||
$key == 11 ||
$key == 12) {
340 $this->assertSame($sub->layout
, qtype_multichoice_base
::LAYOUT_VERTICAL
);