Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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   */
  37  class question_test extends \advanced_testcase {
  38      public function test_get_expected_data() {
  39          $question = \test_question_maker::make_question('multianswer');
  40          $this->assertEquals(array('sub1_answer' => PARAM_RAW_TRIMMED,
  41                  'sub2_answer' => PARAM_RAW), $question->get_expected_data());
  42      }
  43  
  44      public function test_is_complete_response() {
  45          $question = \test_question_maker::make_question('multianswer');
  46  
  47          $this->assertFalse($question->is_complete_response(array()));
  48          $this->assertTrue($question->is_complete_response(array('sub1_answer' => 'Owl',
  49                  'sub2_answer' => '2')));
  50          $this->assertTrue($question->is_complete_response(array('sub1_answer' => '0',
  51                  'sub2_answer' => 0)));
  52          $this->assertFalse($question->is_complete_response(array('sub1_answer' => 'Owl')));
  53      }
  54  
  55      public function test_is_gradable_response() {
  56          $question = \test_question_maker::make_question('multianswer');
  57  
  58          $this->assertFalse($question->is_gradable_response(array()));
  59          $this->assertTrue($question->is_gradable_response(array('sub1_answer' => 'Owl',
  60                  'sub2_answer' => '2')));
  61          $this->assertTrue($question->is_gradable_response(array('sub1_answer' => '0',
  62                  'sub2_answer' => 0)));
  63          $this->assertTrue($question->is_gradable_response(array('sub1_answer' => 'Owl')));
  64      }
  65  
  66      public function test_grading() {
  67          $question = \test_question_maker::make_question('multianswer');
  68          $question->start_attempt(new question_attempt_step(), 1);
  69  
  70          $rightchoice = $question->subquestions[2]->get_correct_response();
  71  
  72          $this->assertEquals(array(1, question_state::$gradedright), $question->grade_response(
  73                  array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice))));
  74          $this->assertEquals(array(0.5, question_state::$gradedpartial), $question->grade_response(
  75                  array('sub1_answer' => 'Owl')));
  76          $this->assertEquals(array(0.5, question_state::$gradedpartial), $question->grade_response(
  77                  array('sub1_answer' => 'Goat', 'sub2_answer' => reset($rightchoice))));
  78          $this->assertEquals(array(0, question_state::$gradedwrong), $question->grade_response(
  79                  array('sub1_answer' => 'Dog')));
  80      }
  81  
  82      public function test_get_correct_response() {
  83          $question = \test_question_maker::make_question('multianswer');
  84          $question->start_attempt(new question_attempt_step(), 1);
  85  
  86          $rightchoice = $question->subquestions[2]->get_correct_response();
  87  
  88          $this->assertEquals(array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice)),
  89                  $question->get_correct_response());
  90      }
  91  
  92      public function test_get_question_summary() {
  93          $question = \test_question_maker::make_question('multianswer');
  94  
  95          // Bit of a hack to make testing easier.
  96          $question->subquestions[2]->shuffleanswers = false;
  97  
  98          $question->start_attempt(new question_attempt_step(), 1);
  99  
 100          $qsummary = $question->get_question_summary();
 101          $this->assertEquals('Complete this opening line of verse: "The _____ and the ' .
 102                  '{Bow-wow; Wiggly worm; Pussy-cat} went to sea".', $qsummary);
 103      }
 104  
 105      public function test_summarise_response() {
 106          $question = \test_question_maker::make_question('multianswer');
 107          $question->start_attempt(new question_attempt_step(), 1);
 108  
 109          $rightchoice = $question->subquestions[2]->get_correct_response();
 110  
 111          $this->assertEquals(get_string('subqresponse', 'qtype_multianswer',
 112                  array('i' => 1, 'response' => 'Owl')) . '; ' .
 113                  get_string('subqresponse', 'qtype_multianswer',
 114                  array('i' => 2, 'response' => 'Pussy-cat')), $question->summarise_response(
 115                  array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice))));
 116      }
 117  
 118      public function test_get_num_parts_right() {
 119          $question = \test_question_maker::make_question('multianswer');
 120          $question->start_attempt(new question_attempt_step(), 1);
 121  
 122          $rightchoice = $question->subquestions[2]->get_correct_response();
 123          $right = reset($rightchoice);
 124  
 125          $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right);
 126          list($numpartsright, $numparts) = $question->get_num_parts_right($response);
 127          $this->assertEquals(1, $numpartsright);
 128          $this->assertEquals(2, $numparts);
 129          $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right);
 130          list($numpartsright, $numparts) = $question->get_num_parts_right($response);
 131          $this->assertEquals(2, $numpartsright);
 132          $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3);
 133          list($numpartsright, $numparts) = $question->get_num_parts_right($response);
 134          $this->assertEquals(0, $numpartsright);
 135          $response = array('sub1_answer' => 'Owl');
 136          list($numpartsright, $numparts) = $question->get_num_parts_right($response);
 137          $this->assertEquals(1, $numpartsright);
 138          $response = array('sub1_answer' => 'Dog');
 139          list($numpartsright, $numparts) = $question->get_num_parts_right($response);
 140          $this->assertEquals(0, $numpartsright);
 141          $response = array('sub2_answer' => $right);
 142          list($numpartsright, $numparts) = $question->get_num_parts_right($response);
 143          $this->assertEquals(1, $numpartsright);
 144      }
 145  
 146      public function test_get_num_parts_right_fourmc() {
 147          // Create a multianswer question with four mcq.
 148          $question = \test_question_maker::make_question('multianswer', 'fourmc');
 149          $question->start_attempt(new question_attempt_step(), 1);
 150  
 151          $response = array('sub1_answer' => '1', 'sub2_answer' => '1',
 152                  'sub3_answer' => '1', 'sub4_answer' => '1');
 153          list($numpartsright, $numparts) = $question->get_num_parts_right($response);
 154          $this->assertEquals(2, $numpartsright);
 155      }
 156  
 157      public function test_clear_wrong_from_response() {
 158          $question = \test_question_maker::make_question('multianswer');
 159          $question->start_attempt(new question_attempt_step(), 1);
 160  
 161          $rightchoice = $question->subquestions[2]->get_correct_response();
 162          $right = reset($rightchoice);
 163  
 164          $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right);
 165          $this->assertEquals($question->clear_wrong_from_response($response),
 166                  array('sub1_answer' => '', 'sub2_answer' => $right));
 167          $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right);
 168          $this->assertEquals($question->clear_wrong_from_response($response),
 169                  array('sub1_answer' => 'Owl', 'sub2_answer' => $right));
 170          $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3);
 171          $this->assertEquals($question->clear_wrong_from_response($response),
 172                  array('sub1_answer' => '', 'sub2_answer' => ''));
 173          $response = array('sub1_answer' => 'Owl');
 174          $this->assertEquals($question->clear_wrong_from_response($response),
 175                  array('sub1_answer' => 'Owl'));
 176          $response = array('sub2_answer' => $right);
 177          $this->assertEquals($question->clear_wrong_from_response($response),
 178                  array('sub2_answer' => $right));
 179      }
 180  
 181      public function test_compute_final_grade() {
 182          $question = \test_question_maker::make_question('multianswer');
 183          // Set penalty to 0.2 to ease calculations.
 184          $question->penalty = 0.2;
 185          // Set subquestion 2 defaultmark to 2, to make it a better test,
 186          // even thought (at the moment) that never happens for real.
 187          $question->subquestions[2]->defaultmark = 2;
 188  
 189          $question->start_attempt(new question_attempt_step(), 1);
 190  
 191          // Compute right and wrong response for subquestion 2.
 192          $rightchoice = $question->subquestions[2]->get_correct_response();
 193          $right = reset($rightchoice);
 194          $wrong = ($right + 1) % 3;
 195  
 196          // Get subquestion 1 right at 2nd try and subquestion 2 right at 3rd try.
 197          $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong),
 198                             1 => array('sub1_answer' => 'Owl', 'sub2_answer' => $wrong),
 199                             2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
 200                            );
 201          $finalgrade = $question->compute_final_grade($responses, 1);
 202          $this->assertEqualsWithDelta(1 / 3 * (1 - 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA);
 203  
 204          // Get subquestion 1 right at 3rd try and subquestion 2 right at 2nd try.
 205          $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong),
 206                             1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $right),
 207                             2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
 208                             3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
 209                            );
 210          $finalgrade = $question->compute_final_grade($responses, 1);
 211          $this->assertEqualsWithDelta(1 / 3 * (1 - 2 * 0.2) + 2 / 3 * (1 - 0.2), $finalgrade, question_testcase::GRADE_DELTA);
 212  
 213          // Get subquestion 1 right at 4th try and subquestion 2 right at 1st try.
 214          $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
 215                             1 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
 216                             2 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
 217                             3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
 218                            );
 219          $finalgrade = $question->compute_final_grade($responses, 1);
 220          $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3, $finalgrade, question_testcase::GRADE_DELTA);
 221  
 222          // Get subquestion 1 right at 4th try and subquestion 2 right 3rd try.
 223          // Subquestion 2 was right at 1st try, but last change is at 3rd try.
 224          $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
 225                             1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $wrong),
 226                             2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right),
 227                             3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
 228                            );
 229          $finalgrade = $question->compute_final_grade($responses, 1);
 230          $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA);
 231  
 232          // Incomplete responses. Subquestion 1 is right at 4th try and subquestion 2 at 3rd try.
 233          $responses = array(0 => array('sub1_answer' => 'Dog'),
 234                             1 => array('sub1_answer' => 'Cat'),
 235                             2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right),
 236                             3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
 237                            );
 238          $finalgrade = $question->compute_final_grade($responses, 1);
 239          $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA);
 240      }
 241  
 242      /**
 243       * test_get_question_definition_for_external_rendering
 244       */
 245      public function test_get_question_definition_for_external_rendering() {
 246          $this->resetAfterTest();
 247  
 248          $question = \test_question_maker::make_question('multianswer');
 249          $question->start_attempt(new question_attempt_step(), 1);
 250          $qa = \test_question_maker::get_a_qa($question);
 251          $displayoptions = new question_display_options();
 252  
 253          $options = $question->get_question_definition_for_external_rendering($qa, $displayoptions);
 254          $this->assertNull($options);
 255      }
 256  }