See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 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. 8 // 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. 13 // 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/>. 16 17 namespace qtype_multianswer; 18 19 use question_attempt_step; 20 use question_display_options; 21 use question_state; 22 use question_testcase; 23 24 defined('MOODLE_INTERNAL') || die(); 25 26 global $CFG; 27 require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); 28 29 30 /** 31 * Unit tests for qtype_multianswer_question. 32 * 33 * @package qtype_multianswer 34 * @copyright 2011 The Open University 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 * @covers \qtype_multianswer_question 37 */ 38 class question_test extends \advanced_testcase { 39 public function test_get_expected_data() { 40 $question = \test_question_maker::make_question('multianswer'); 41 $this->assertEquals(array('sub1_answer' => PARAM_RAW_TRIMMED, 42 'sub2_answer' => PARAM_RAW), $question->get_expected_data()); 43 } 44 45 public function test_is_complete_response() { 46 $question = \test_question_maker::make_question('multianswer'); 47 48 $this->assertFalse($question->is_complete_response(array())); 49 $this->assertTrue($question->is_complete_response(array('sub1_answer' => 'Owl', 50 'sub2_answer' => '2'))); 51 $this->assertTrue($question->is_complete_response(array('sub1_answer' => '0', 52 'sub2_answer' => 0))); 53 $this->assertFalse($question->is_complete_response(array('sub1_answer' => 'Owl'))); 54 } 55 56 public function test_is_gradable_response() { 57 $question = \test_question_maker::make_question('multianswer'); 58 59 $this->assertFalse($question->is_gradable_response(array())); 60 $this->assertTrue($question->is_gradable_response(array('sub1_answer' => 'Owl', 61 'sub2_answer' => '2'))); 62 $this->assertTrue($question->is_gradable_response(array('sub1_answer' => '0', 63 'sub2_answer' => 0))); 64 $this->assertTrue($question->is_gradable_response(array('sub1_answer' => 'Owl'))); 65 } 66 67 public function test_grading() { 68 $question = \test_question_maker::make_question('multianswer'); 69 $question->start_attempt(new question_attempt_step(), 1); 70 71 $rightchoice = $question->subquestions[2]->get_correct_response(); 72 73 $this->assertEquals(array(1, question_state::$gradedright), $question->grade_response( 74 array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice)))); 75 $this->assertEquals(array(0.5, question_state::$gradedpartial), $question->grade_response( 76 array('sub1_answer' => 'Owl'))); 77 $this->assertEquals(array(0.5, question_state::$gradedpartial), $question->grade_response( 78 array('sub1_answer' => 'Goat', 'sub2_answer' => reset($rightchoice)))); 79 $this->assertEquals(array(0, question_state::$gradedwrong), $question->grade_response( 80 array('sub1_answer' => 'Dog'))); 81 } 82 83 public function test_get_correct_response() { 84 $question = \test_question_maker::make_question('multianswer'); 85 $question->start_attempt(new question_attempt_step(), 1); 86 87 $rightchoice = $question->subquestions[2]->get_correct_response(); 88 89 $this->assertEquals(array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice)), 90 $question->get_correct_response()); 91 } 92 93 public function test_get_question_summary() { 94 $question = \test_question_maker::make_question('multianswer'); 95 96 // Bit of a hack to make testing easier. 97 $question->subquestions[2]->shuffleanswers = false; 98 99 $question->start_attempt(new question_attempt_step(), 1); 100 101 $qsummary = $question->get_question_summary(); 102 $this->assertEquals('Complete this opening line of verse: "The _____ and the ' . 103 '{Bow-wow; Wiggly worm; Pussy-cat} went to sea".', $qsummary); 104 } 105 106 public function test_summarise_response() { 107 $question = \test_question_maker::make_question('multianswer'); 108 $question->start_attempt(new question_attempt_step(), 1); 109 110 $rightchoice = $question->subquestions[2]->get_correct_response(); 111 112 $this->assertEquals(get_string('subqresponse', 'qtype_multianswer', 113 array('i' => 1, 'response' => 'Owl')) . '; ' . 114 get_string('subqresponse', 'qtype_multianswer', 115 array('i' => 2, 'response' => 'Pussy-cat')), $question->summarise_response( 116 array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice)))); 117 } 118 119 public function test_get_num_parts_right() { 120 $question = \test_question_maker::make_question('multianswer'); 121 $question->start_attempt(new question_attempt_step(), 1); 122 123 $rightchoice = $question->subquestions[2]->get_correct_response(); 124 $right = reset($rightchoice); 125 126 $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right); 127 list($numpartsright, $numparts) = $question->get_num_parts_right($response); 128 $this->assertEquals(1, $numpartsright); 129 $this->assertEquals(2, $numparts); 130 $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right); 131 list($numpartsright, $numparts) = $question->get_num_parts_right($response); 132 $this->assertEquals(2, $numpartsright); 133 $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3); 134 list($numpartsright, $numparts) = $question->get_num_parts_right($response); 135 $this->assertEquals(0, $numpartsright); 136 $response = array('sub1_answer' => 'Owl'); 137 list($numpartsright, $numparts) = $question->get_num_parts_right($response); 138 $this->assertEquals(1, $numpartsright); 139 $response = array('sub1_answer' => 'Dog'); 140 list($numpartsright, $numparts) = $question->get_num_parts_right($response); 141 $this->assertEquals(0, $numpartsright); 142 $response = array('sub2_answer' => $right); 143 list($numpartsright, $numparts) = $question->get_num_parts_right($response); 144 $this->assertEquals(1, $numpartsright); 145 } 146 147 public function test_get_num_parts_right_fourmc() { 148 // Create a multianswer question with four mcq. 149 $question = \test_question_maker::make_question('multianswer', 'fourmc'); 150 $question->start_attempt(new question_attempt_step(), 1); 151 152 $response = array('sub1_answer' => '1', 'sub2_answer' => '1', 153 'sub3_answer' => '1', 'sub4_answer' => '1'); 154 list($numpartsright, $numparts) = $question->get_num_parts_right($response); 155 $this->assertEquals(2, $numpartsright); 156 } 157 158 public function test_clear_wrong_from_response() { 159 $question = \test_question_maker::make_question('multianswer'); 160 $question->start_attempt(new question_attempt_step(), 1); 161 162 $rightchoice = $question->subquestions[2]->get_correct_response(); 163 $right = reset($rightchoice); 164 165 $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right); 166 $this->assertEquals($question->clear_wrong_from_response($response), 167 array('sub1_answer' => '', 'sub2_answer' => $right)); 168 $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right); 169 $this->assertEquals($question->clear_wrong_from_response($response), 170 array('sub1_answer' => 'Owl', 'sub2_answer' => $right)); 171 $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3); 172 $this->assertEquals($question->clear_wrong_from_response($response), 173 array('sub1_answer' => '', 'sub2_answer' => '')); 174 $response = array('sub1_answer' => 'Owl'); 175 $this->assertEquals($question->clear_wrong_from_response($response), 176 array('sub1_answer' => 'Owl')); 177 $response = array('sub2_answer' => $right); 178 $this->assertEquals($question->clear_wrong_from_response($response), 179 array('sub2_answer' => $right)); 180 } 181 182 public function test_compute_final_grade() { 183 $question = \test_question_maker::make_question('multianswer'); 184 // Set penalty to 0.2 to ease calculations. 185 $question->penalty = 0.2; 186 // Set subquestion 2 defaultmark to 2, to make it a better test, 187 // even thought (at the moment) that never happens for real. 188 $question->subquestions[2]->defaultmark = 2; 189 190 $question->start_attempt(new question_attempt_step(), 1); 191 192 // Compute right and wrong response for subquestion 2. 193 $rightchoice = $question->subquestions[2]->get_correct_response(); 194 $right = reset($rightchoice); 195 $wrong = ($right + 1) % 3; 196 197 // Get subquestion 1 right at 2nd try and subquestion 2 right at 3rd try. 198 $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong), 199 1 => array('sub1_answer' => 'Owl', 'sub2_answer' => $wrong), 200 2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), 201 ); 202 $finalgrade = $question->compute_final_grade($responses, 1); 203 $this->assertEqualsWithDelta(1 / 3 * (1 - 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA); 204 205 // Get subquestion 1 right at 3rd try and subquestion 2 right at 2nd try. 206 $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong), 207 1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $right), 208 2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), 209 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), 210 ); 211 $finalgrade = $question->compute_final_grade($responses, 1); 212 $this->assertEqualsWithDelta(1 / 3 * (1 - 2 * 0.2) + 2 / 3 * (1 - 0.2), $finalgrade, question_testcase::GRADE_DELTA); 213 214 // Get subquestion 1 right at 4th try and subquestion 2 right at 1st try. 215 $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right), 216 1 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right), 217 2 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right), 218 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), 219 ); 220 $finalgrade = $question->compute_final_grade($responses, 1); 221 $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3, $finalgrade, question_testcase::GRADE_DELTA); 222 223 // Get subquestion 1 right at 4th try and subquestion 2 right 3rd try. 224 // Subquestion 2 was right at 1st try, but last change is at 3rd try. 225 $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right), 226 1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $wrong), 227 2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right), 228 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), 229 ); 230 $finalgrade = $question->compute_final_grade($responses, 1); 231 $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA); 232 233 // Incomplete responses. Subquestion 1 is right at 4th try and subquestion 2 at 3rd try. 234 $responses = array(0 => array('sub1_answer' => 'Dog'), 235 1 => array('sub1_answer' => 'Cat'), 236 2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right), 237 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right), 238 ); 239 $finalgrade = $question->compute_final_grade($responses, 1); 240 $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA); 241 } 242 243 /** 244 * test_get_question_definition_for_external_rendering 245 */ 246 public function test_get_question_definition_for_external_rendering() { 247 $this->resetAfterTest(); 248 249 $question = \test_question_maker::make_question('multianswer'); 250 $question->start_attempt(new question_attempt_step(), 1); 251 $qa = \test_question_maker::get_a_qa($question); 252 $displayoptions = new question_display_options(); 253 254 $options = $question->get_question_definition_for_external_rendering($qa, $displayoptions); 255 $this->assertNull($options); 256 } 257 258 /** 259 * Helper method to make a simulated second version of the standard multianswer test question. 260 * 261 * The key think is that all the answer ids are changed (increased by 20). 262 * 263 * @param \qtype_multianswer_question $question 264 * @return \qtype_multianswer_question 265 */ 266 protected function make_second_version( 267 \qtype_multianswer_question $question): \qtype_multianswer_question { 268 $newquestion = fullclone($question); 269 270 $newquestion->subquestions[1]->answers = [ 271 36 => new \question_answer(16, 'Apple', 0.3333333, 272 'Good', FORMAT_HTML), 273 37 => new \question_answer(17, 'Burger', -0.5, 274 '', FORMAT_HTML), 275 38 => new \question_answer(18, 'Hot dog', -0.5, 276 'Not a fruit', FORMAT_HTML), 277 39 => new \question_answer(19, 'Pizza', -0.5, 278 '', FORMAT_HTML), 279 40 => new \question_answer(20, 'Orange', 0.3333333, 280 'Correct', FORMAT_HTML), 281 41 => new \question_answer(21, 'Banana', 0.3333333, 282 '', FORMAT_HTML), 283 ]; 284 285 $newquestion->subquestions[2]->answers = [ 286 42 => new \question_answer(22, 'Raddish', 0.5, 287 'Good', FORMAT_HTML), 288 43 => new \question_answer(23, 'Chocolate', -0.5, 289 '', FORMAT_HTML), 290 44 => new \question_answer(24, 'Biscuit', -0.5, 291 'Not a vegetable', FORMAT_HTML), 292 45 => new \question_answer(25, 'Cheese', -0.5, 293 '', FORMAT_HTML), 294 46 => new \question_answer(26, 'Carrot', 0.5, 295 'Correct', FORMAT_HTML), 296 ]; 297 298 return $newquestion; 299 } 300 301 public function test_validate_can_regrade_with_other_version_ok() { 302 /** @var \qtype_multianswer_question $question */ 303 $question = \test_question_maker::make_question('multianswer', 'multiple'); 304 305 $newquestion = $this->make_second_version($question); 306 307 $this->assertNull($newquestion->validate_can_regrade_with_other_version($question)); 308 } 309 310 public function test_validate_can_regrade_with_other_version_wrong_subquestions() { 311 /** @var \qtype_multianswer_question $question */ 312 $question = \test_question_maker::make_question('multianswer', 'multiple'); 313 314 $newquestion = $this->make_second_version($question); 315 unset($newquestion->subquestions[2]); 316 317 $this->assertEquals( 318 get_string('regradeissuenumsubquestionschanged', 'qtype_multianswer'), 319 $newquestion->validate_can_regrade_with_other_version($question)); 320 } 321 322 public function test_validate_can_regrade_with_other_version_one_wrong_subquestion() { 323 /** @var \qtype_multianswer_question $question */ 324 $question = \test_question_maker::make_question('multianswer', 'multiple'); 325 326 $newquestion = $this->make_second_version($question); 327 unset($newquestion->subquestions[1]->answers[41]); 328 329 $this->assertEquals( 330 get_string('regradeissuenumchoiceschanged', 'qtype_multichoice'), 331 $newquestion->validate_can_regrade_with_other_version($question)); 332 } 333 334 public function test_update_attempt_state_date_from_old_version_ok() { 335 /** @var \qtype_multianswer_question $question */ 336 $question = \test_question_maker::make_question('multianswer', 'multiple'); 337 338 $newquestion = $this->make_second_version($question); 339 340 $oldstep = new question_attempt_step(); 341 $oldstep->set_qt_var('_sub1_order', '16,17,18,19,20,21'); 342 $oldstep->set_qt_var('_sub2_order', '22,23,24,25,26'); 343 344 $expected = [ 345 '_sub1_order' => '36,37,38,39,40,41', 346 '_sub2_order' => '42,43,44,45,46', 347 ]; 348 349 $this->assertEquals($expected, 350 $newquestion->update_attempt_state_data_for_new_version($oldstep, $question)); 351 } 352 353 /** 354 * Test functions work with zero weight. 355 * This is used for testing the MDL-77378 bug. 356 */ 357 public function test_zeroweight() { 358 $this->resetAfterTest(); 359 /** @var \qtype_multianswer_question $question */ 360 $question = \test_question_maker::make_question('multianswer', 'zeroweight'); 361 $question->start_attempt(new question_attempt_step(), 1); 362 363 $this->assertEquals([null, question_state::$gradedright], $question->grade_response( 364 ['sub1_answer' => 'Something'])); 365 $this->assertEquals([null, question_state::$gradedwrong], $question->grade_response( 366 ['sub1_answer' => 'Input box'])); 367 368 $this->assertEquals(1, $question->get_max_fraction()); 369 $this->assertEquals(0, $question->get_min_fraction()); 370 } 371 372 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body