Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   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  /**
  18   * Unit tests for the parts of {@link question_engine_data_mapper} related to reporting.
  19   *
  20   * @package   core_question
  21   * @category  test
  22   * @copyright 2013 The Open University
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  global $CFG;
  30  require_once (__DIR__ . '/../lib.php');
  31  require_once (__DIR__ . '/helpers.php');
  32  
  33  
  34  /**
  35   * Unit tests for the parts of {@link question_engine_data_mapper} related to reporting.
  36   *
  37   * @copyright 2013 The Open University
  38   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class question_engine_data_mapper_reporting_testcase extends qbehaviour_walkthrough_test_base {
  41  
  42      /** @var question_engine_data_mapper */
  43      protected $dm;
  44  
  45      /** @var qtype_shortanswer_question */
  46      protected $sa;
  47  
  48      /** @var qtype_essay_question */
  49      protected $essay;
  50  
  51      /** @var array */
  52      protected $usageids = array();
  53  
  54      /** @var qubaid_condition */
  55      protected $bothusages;
  56  
  57      /** @var array */
  58      protected $allslots = array();
  59  
  60      /**
  61       * Test the various methods that load data for reporting.
  62       *
  63       * Since these methods need an expensive set-up, and then only do read-only
  64       * operations on the data, we use a single method to do the set-up, which
  65       * calls diffents methods to test each query.
  66       */
  67      public function test_reporting_queries() {
  68          // We create two usages, each with two questions, a short-answer marked
  69          // out of 5, and and essay marked out of 10.
  70          //
  71          // In the first usage, the student answers the short-answer
  72          // question correctly, and enters something in the essay.
  73          //
  74          // In the second useage, the student answers the short-answer question
  75          // wrongly, and leaves the essay blank.
  76          $this->resetAfterTest();
  77          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
  78          $cat = $generator->create_question_category();
  79          $this->sa = $generator->create_question('shortanswer', null,
  80                  array('category' => $cat->id));
  81          $this->essay = $generator->create_question('essay', null,
  82                  array('category' => $cat->id));
  83  
  84          $this->usageids = array();
  85  
  86          // Create the first usage.
  87          $q = question_bank::load_question($this->sa->id);
  88          $this->start_attempt_at_question($q, 'interactive', 5);
  89          $this->allslots[] = $this->slot;
  90          $this->process_submission(array('answer' => 'cat'));
  91          $this->process_submission(array('answer' => 'frog', '-submit' => 1));
  92  
  93          $q = question_bank::load_question($this->essay->id);
  94          $this->start_attempt_at_question($q, 'interactive', 10);
  95          $this->allslots[] = $this->slot;
  96          $this->process_submission(array('answer' => '<p>The cat sat on the mat.</p>', 'answerformat' => FORMAT_HTML));
  97  
  98          $this->finish();
  99          $this->save_quba();
 100          $this->usageids[] = $this->quba->get_id();
 101  
 102          // Create the second usage.
 103          $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
 104                  context_system::instance());
 105  
 106          $q = question_bank::load_question($this->sa->id);
 107          $this->start_attempt_at_question($q, 'interactive', 5);
 108          $this->process_submission(array('answer' => 'fish'));
 109  
 110          $q = question_bank::load_question($this->essay->id);
 111          $this->start_attempt_at_question($q, 'interactive', 10);
 112  
 113          $this->finish();
 114          $this->save_quba();
 115          $this->usageids[] = $this->quba->get_id();
 116  
 117          // Set up some things the tests will need.
 118          $this->dm = new question_engine_data_mapper();
 119          $this->bothusages = new qubaid_list($this->usageids);
 120  
 121          // Now test the various queries.
 122          $this->dotest_load_questions_usages_latest_steps($this->allslots);
 123          $this->dotest_load_questions_usages_latest_steps(null);
 124          $this->dotest_load_questions_usages_question_state_summary($this->allslots);
 125          $this->dotest_load_questions_usages_question_state_summary(null);
 126          $this->dotest_load_questions_usages_where_question_in_state();
 127          $this->dotest_load_average_marks($this->allslots);
 128          $this->dotest_load_average_marks(null);
 129          $this->dotest_sum_usage_marks_subquery();
 130          $this->dotest_question_attempt_latest_state_view();
 131      }
 132  
 133      /**
 134       * This test is executed by {@link test_reporting_queries()}.
 135       *
 136       * @param array|null $slots list of slots to use in the call.
 137       */
 138      protected function dotest_load_questions_usages_latest_steps($slots) {
 139          $rawstates = $this->dm->load_questions_usages_latest_steps($this->bothusages, $slots,
 140                  'qa.id AS questionattemptid, qa.questionusageid, qa.slot, ' .
 141                  'qa.questionid, qa.maxmark, qas.sequencenumber, qas.state');
 142  
 143          $states = array();
 144          foreach ($rawstates as $state) {
 145              $states[$state->questionusageid][$state->slot] = $state;
 146              unset($state->questionattemptid);
 147              unset($state->questionusageid);
 148              unset($state->slot);
 149          }
 150  
 151          $state = $states[$this->usageids[0]][$this->allslots[0]];
 152          $this->assertEquals((object) array(
 153              'questionid'     => $this->sa->id,
 154              'maxmark'        => 5.0,
 155              'sequencenumber' => 2,
 156              'state'          => (string) question_state::$gradedright,
 157          ), $state);
 158  
 159          $state = $states[$this->usageids[0]][$this->allslots[1]];
 160          $this->assertEquals((object) array(
 161              'questionid'     => $this->essay->id,
 162              'maxmark'        => 10.0,
 163              'sequencenumber' => 2,
 164              'state'          => (string) question_state::$needsgrading,
 165          ), $state);
 166  
 167          $state = $states[$this->usageids[1]][$this->allslots[0]];
 168          $this->assertEquals((object) array(
 169              'questionid'     => $this->sa->id,
 170              'maxmark'        => 5.0,
 171              'sequencenumber' => 2,
 172              'state'          => (string) question_state::$gradedwrong,
 173          ), $state);
 174  
 175          $state = $states[$this->usageids[1]][$this->allslots[1]];
 176          $this->assertEquals((object) array(
 177              'questionid'     => $this->essay->id,
 178              'maxmark'        => 10.0,
 179              'sequencenumber' => 1,
 180              'state'          => (string) question_state::$gaveup,
 181          ), $state);
 182      }
 183  
 184      /**
 185       * This test is executed by {@link test_reporting_queries()}.
 186       *
 187       * @param array|null $slots list of slots to use in the call.
 188       */
 189      protected function dotest_load_questions_usages_question_state_summary($slots) {
 190          $summary = $this->dm->load_questions_usages_question_state_summary(
 191                  $this->bothusages, $slots);
 192  
 193          $this->assertEquals($summary[$this->allslots[0] . ',' . $this->sa->id],
 194                  (object) array(
 195                      'slot' => $this->allslots[0],
 196                      'questionid' => $this->sa->id,
 197                      'name' => $this->sa->name,
 198                      'inprogress' => 0,
 199                      'needsgrading' => 0,
 200                      'autograded' => 2,
 201                      'manuallygraded' => 0,
 202                      'all' => 2,
 203                  ));
 204          $this->assertEquals($summary[$this->allslots[1] . ',' . $this->essay->id],
 205                  (object) array(
 206                      'slot' => $this->allslots[1],
 207                      'questionid' => $this->essay->id,
 208                      'name' => $this->essay->name,
 209                      'inprogress' => 0,
 210                      'needsgrading' => 1,
 211                      'autograded' => 1,
 212                      'manuallygraded' => 0,
 213                      'all' => 2,
 214                  ));
 215      }
 216  
 217      /**
 218       * This test is executed by {@link test_reporting_queries()}.
 219       */
 220      protected function dotest_load_questions_usages_where_question_in_state() {
 221          $this->assertEquals(
 222                  array(array($this->usageids[0], $this->usageids[1]), 2),
 223                  $this->dm->load_questions_usages_where_question_in_state($this->bothusages,
 224                  'all', $this->allslots[1], null, 'questionusageid'));
 225  
 226          $this->assertEquals(
 227                  array(array($this->usageids[0], $this->usageids[1]), 2),
 228                  $this->dm->load_questions_usages_where_question_in_state($this->bothusages,
 229                  'autograded', $this->allslots[0], null, 'questionusageid'));
 230  
 231          $this->assertEquals(
 232                  array(array($this->usageids[0]), 1),
 233                  $this->dm->load_questions_usages_where_question_in_state($this->bothusages,
 234                  'needsgrading', $this->allslots[1], null, 'questionusageid'));
 235      }
 236  
 237      /**
 238       * This test is executed by {@link test_reporting_queries()}.
 239       *
 240       * @param array|null $slots list of slots to use in the call.
 241       */
 242      protected function dotest_load_average_marks($slots) {
 243          $averages = $this->dm->load_average_marks($this->bothusages, $slots);
 244  
 245          $this->assertEquals(array(
 246              $this->allslots[0] => (object) array(
 247                  'slot'            => $this->allslots[0],
 248                  'averagefraction' => 0.5,
 249                  'numaveraged'     => 2,
 250              ),
 251              $this->allslots[1] => (object) array(
 252                  'slot'            => $this->allslots[1],
 253                  'averagefraction' => 0,
 254                  'numaveraged'     => 1,
 255              ),
 256          ), $averages);
 257      }
 258  
 259      /**
 260       * This test is executed by {@link test_reporting_queries()}.
 261       */
 262      protected function dotest_sum_usage_marks_subquery() {
 263          global $DB;
 264  
 265          $totals = $DB->get_records_sql_menu("SELECT qu.id, ({$this->dm->sum_usage_marks_subquery('qu.id')}) AS totalmark
 266                    FROM {question_usages} qu
 267                   WHERE qu.id IN ({$this->usageids[0]}, {$this->usageids[1]})");
 268  
 269          $this->assertNull($totals[$this->usageids[0]]); // Since a question requires grading.
 270  
 271          $this->assertNotNull($totals[$this->usageids[1]]); // Grrr! PHP null == 0 makes this hard.
 272          $this->assertEquals(0, $totals[$this->usageids[1]]);
 273      }
 274  
 275      /**
 276       * This test is executed by {@link test_reporting_queries()}.
 277       */
 278      protected function dotest_question_attempt_latest_state_view() {
 279          global $DB;
 280  
 281          list($inlineview, $viewparams) = $this->dm->question_attempt_latest_state_view(
 282                  'lateststate', $this->bothusages);
 283  
 284          $rawstates = $DB->get_records_sql("
 285                  SELECT lateststate.questionattemptid,
 286                         qu.id AS questionusageid,
 287                         lateststate.slot,
 288                         lateststate.questionid,
 289                         lateststate.maxmark,
 290                         lateststate.sequencenumber,
 291                         lateststate.state
 292                    FROM {question_usages} qu
 293               LEFT JOIN $inlineview ON lateststate.questionusageid = qu.id
 294                   WHERE qu.id IN ({$this->usageids[0]}, {$this->usageids[1]})", $viewparams);
 295  
 296          $states = array();
 297          foreach ($rawstates as $state) {
 298              $states[$state->questionusageid][$state->slot] = $state;
 299              unset($state->questionattemptid);
 300              unset($state->questionusageid);
 301              unset($state->slot);
 302          }
 303  
 304          $state = $states[$this->usageids[0]][$this->allslots[0]];
 305          $this->assertEquals((object) array(
 306              'questionid'     => $this->sa->id,
 307              'maxmark'        => 5.0,
 308              'sequencenumber' => 2,
 309              'state'          => (string) question_state::$gradedright,
 310          ), $state);
 311  
 312          $state = $states[$this->usageids[0]][$this->allslots[1]];
 313          $this->assertEquals((object) array(
 314              'questionid'     => $this->essay->id,
 315              'maxmark'        => 10.0,
 316              'sequencenumber' => 2,
 317              'state'          => (string) question_state::$needsgrading,
 318          ), $state);
 319  
 320          $state = $states[$this->usageids[1]][$this->allslots[0]];
 321          $this->assertEquals((object) array(
 322              'questionid'     => $this->sa->id,
 323              'maxmark'        => 5.0,
 324              'sequencenumber' => 2,
 325              'state'          => (string) question_state::$gradedwrong,
 326          ), $state);
 327  
 328          $state = $states[$this->usageids[1]][$this->allslots[1]];
 329          $this->assertEquals((object) array(
 330              'questionid'     => $this->essay->id,
 331              'maxmark'        => 10.0,
 332              'sequencenumber' => 1,
 333              'state'          => (string) question_state::$gaveup,
 334          ), $state);
 335      }
 336  }