Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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  namespace qbank_statistics;
  18  
  19  use core_question\statistics\questions\all_calculated_for_qubaid_condition;
  20  use quiz;
  21  use question_engine;
  22  use quiz_attempt;
  23  
  24  /**
  25   * Tests for question statistics.
  26   *
  27   * @package    qbank_statistics
  28   * @copyright  2021 Catalyst IT Australia Pty Ltd
  29   * @author     Nathan Nguyen <nathannguyen@catalyst-au.net>
  30   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  class helper_test extends \advanced_testcase {
  33  
  34      /**
  35       * Test quizzes that contain a specified question.
  36       *
  37       * @covers ::get_all_places_where_questions_were_attempted
  38       * @throws \coding_exception
  39       * @throws \dml_exception
  40       */
  41      public function test_get_all_places_where_questions_were_attempted(): void {
  42          $this->resetAfterTest();
  43          $this->setAdminUser();
  44  
  45          $rcm = new \ReflectionMethod(helper::class, 'get_all_places_where_questions_were_attempted');
  46          $rcm->setAccessible(true);
  47  
  48          // Create a course.
  49          $course = $this->getDataGenerator()->create_course();
  50  
  51          // Create three quizzes.
  52          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
  53          $quiz1 = $quizgenerator->create_instance([
  54              'course' => $course->id,
  55              'grade' => 100.0, 'sumgrades' => 2,
  56              'layout' => '1,2,0'
  57          ]);
  58          $quiz1context = \context_module::instance($quiz1->cmid);
  59  
  60          $quiz2 = $quizgenerator->create_instance([
  61              'course' => $course->id,
  62              'grade' => 100.0, 'sumgrades' => 2,
  63              'layout' => '1,2,0'
  64          ]);
  65          $quiz2context = \context_module::instance($quiz2->cmid);
  66  
  67          $quiz3 = $quizgenerator->create_instance([
  68              'course' => $course->id,
  69              'grade' => 100.0, 'sumgrades' => 2,
  70              'layout' => '1,2,0'
  71          ]);
  72          $quiz3context = \context_module::instance($quiz3->cmid);
  73  
  74          // Create questions.
  75          /** @var \core_question_generator $questiongenerator */
  76          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
  77          $cat = $questiongenerator->create_question_category();
  78          $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
  79          $question2 = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
  80  
  81          // Add question 1 to quiz 1 and make an attempt.
  82          quiz_add_quiz_question($question1->id, $quiz1);
  83          // Quiz 1 attempt.
  84          $this->submit_quiz($quiz1, [1 => ['answer' => 'frog']]);
  85  
  86          // Add questions 1 and 2 to quiz 2.
  87          quiz_add_quiz_question($question1->id, $quiz2);
  88          quiz_add_quiz_question($question2->id, $quiz2);
  89          $this->submit_quiz($quiz2, [1 => ['answer' => 'frog'], 2 => ['answer' => 10]]);
  90  
  91          // Checking quizzes that use question 1.
  92          $q1places = $rcm->invoke(null, [$question1->id]);
  93          $this->assertCount(2, $q1places);
  94          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz1context->id], $q1places[0]);
  95          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q1places[1]);
  96  
  97          // Checking quizzes that contain question 2.
  98          $q2places = $rcm->invoke(null, [$question2->id]);
  99          $this->assertCount(1, $q2places);
 100          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q2places[0]);
 101  
 102          // Add a random question to quiz3.
 103          quiz_add_random_questions($quiz3, 0, $cat->id, 1, false);
 104          $this->submit_quiz($quiz3, [1 => ['answer' => 'willbewrong']]);
 105  
 106          // Quiz 3 will now be in one of these arrays.
 107          $q1places = $rcm->invoke(null, [$question1->id]);
 108          $q2places = $rcm->invoke(null, [$question2->id]);
 109          if (count($q1places) == 3) {
 110              $newplace = end($q1places);
 111          } else {
 112              $newplace = end($q2places);
 113          }
 114          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz3context->id], $newplace);
 115      }
 116  
 117      /**
 118       * Create 2 quizzes.
 119       *
 120       * @return array return 2 quizzes
 121       * @throws \coding_exception
 122       */
 123      private function prepare_quizzes(): array {
 124          // Create a course.
 125          $course = $this->getDataGenerator()->create_course();
 126  
 127          // Make 2 quizzes.
 128          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
 129          $layout = '1,2,0,3,4,0';
 130          $quiz1 = $quizgenerator->create_instance([
 131              'course' => $course->id,
 132              'grade' => 100.0, 'sumgrades' => 2,
 133              'layout' => $layout
 134          ]);
 135  
 136          $quiz2 = $quizgenerator->create_instance([
 137              'course' => $course->id,
 138              'grade' => 100.0, 'sumgrades' => 2,
 139              'layout' => $layout
 140          ]);
 141  
 142          /** @var \core_question_generator $questiongenerator */
 143          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
 144          $cat = $questiongenerator->create_question_category();
 145  
 146          $page = 1;
 147          $questions = [];
 148          foreach (explode(',', $layout) as $slot) {
 149              if ($slot == 0) {
 150                  $page += 1;
 151                  continue;
 152              }
 153  
 154              $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
 155              $questions[$slot] = $question;
 156              quiz_add_quiz_question($question->id, $quiz1, $page);
 157              quiz_add_quiz_question($question->id, $quiz2, $page);
 158          }
 159  
 160          return [$quiz1, $quiz2, $questions];
 161      }
 162  
 163      /**
 164       * Submit quiz answers
 165       *
 166       * @param object $quiz
 167       * @param array $answers
 168       * @throws \moodle_exception
 169       */
 170      private function submit_quiz(object $quiz, array $answers): void {
 171          // Create user.
 172          $user = $this->getDataGenerator()->create_user();
 173          // Create attempt.
 174          $quizobj = quiz::create($quiz->id, $user->id);
 175          $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
 176          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
 177          $timenow = time();
 178          $attempt = quiz_create_attempt($quizobj, 1, null, $timenow, false, $user->id);
 179          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
 180          quiz_attempt_save_started($quizobj, $quba, $attempt);
 181          // Submit attempt.
 182          $attemptobj = quiz_attempt::create($attempt->id);
 183          $attemptobj->process_submitted_actions($timenow, false, $answers);
 184          $attemptobj->process_finish($timenow, false);
 185      }
 186  
 187      /**
 188       * Generate attempt answers.
 189       *
 190       * @param array $correctanswerflags array of 1 or 0
 191       * 1 : generate correct answer
 192       * 0 : generate wrong answer
 193       *
 194       * @return array
 195       */
 196      private function generate_attempt_answers(array $correctanswerflags): array {
 197          $attempt = [];
 198          for ($i = 1; $i <= 4; $i++) {
 199              if (isset($correctanswerflags) && $correctanswerflags[$i - 1] == 1) {
 200                  // Correct answer.
 201                  $attempt[$i] = ['answer' => 'frog'];
 202              } else {
 203                  $attempt[$i] = ['answer' => 'false'];
 204              }
 205          }
 206          return $attempt;
 207      }
 208  
 209      /**
 210       *
 211       * Generate quizzes and submit answers.
 212       *
 213       * @param array $quiz1attempts quiz 1 attempts
 214       * @param array $quiz2attempts quiz 2 attempts
 215       *
 216       * @return array
 217       */
 218      private function prepare_and_submit_quizzes(array $quiz1attempts, array $quiz2attempts): array {
 219          list($quiz1, $quiz2, $questions) = $this->prepare_quizzes();
 220          // Submit attempts of quiz1.
 221          foreach ($quiz1attempts as $attempt) {
 222              $this->submit_quiz($quiz1, $attempt);
 223          }
 224          // Submit attempts of quiz2.
 225          foreach ($quiz2attempts as $attempt) {
 226              $this->submit_quiz($quiz2, $attempt);
 227          }
 228  
 229          // Calculate the statistics.
 230          $this->expectOutputRegex('~.*Calculations completed.*~');
 231          $statisticstask = new \quiz_statistics\task\recalculate();
 232          $statisticstask->execute();
 233  
 234          return [$quiz1, $quiz2, $questions];
 235      }
 236  
 237      /**
 238       * To use private helper::extract_item_value function.
 239       *
 240       * @param all_calculated_for_qubaid_condition $statistics the batch of statistics.
 241       * @param int $questionid a question id.
 242       * @param string $item ane of the field names in all_calculated_for_qubaid_condition, e.g. 'facility'.
 243       * @return float|null the required value.
 244       */
 245      private function extract_item_value(all_calculated_for_qubaid_condition $statistics,
 246                                          int $questionid, string $item): ?float {
 247          $rcm = new \ReflectionMethod(helper::class, 'extract_item_value');
 248          $rcm->setAccessible(true);
 249          return $rcm->invoke(null, $statistics, $questionid, $item);
 250      }
 251  
 252      /**
 253       * To use private helper::load_statistics_for_place function (with mod_quiz component).
 254       *
 255       * @param \context $context the context to load the statistics for.
 256       * @return all_calculated_for_qubaid_condition|null question statistics.
 257       */
 258      private function load_quiz_statistics_for_place(\context $context): ?all_calculated_for_qubaid_condition {
 259          $rcm = new \ReflectionMethod(helper::class, 'load_statistics_for_place');
 260          $rcm->setAccessible(true);
 261          return $rcm->invoke(null, 'mod_quiz', $context);
 262      }
 263  
 264      /**
 265       * Data provider for {@see test_load_question_facility()}.
 266       *
 267       * @return \Generator
 268       */
 269      public function load_question_facility_provider(): \Generator {
 270          yield 'Facility case 1' => [
 271              'Quiz 1 attempts' => [
 272                  $this->generate_attempt_answers([1, 0, 0, 0]),
 273              ],
 274              'Expected quiz 1 facilities' => ['100.00%', '0.00%', '0.00%', '0.00%'],
 275              'Quiz 2 attempts' => [
 276                  $this->generate_attempt_answers([1, 0, 0, 0]),
 277                  $this->generate_attempt_answers([1, 1, 0, 0]),
 278              ],
 279              'Expected quiz 2 facilities' => ['100.00%', '50.00%', '0.00%', '0.00%'],
 280              'Expected average facilities' => ['100.00%', '25.00%', '0.00%', '0.00%'],
 281          ];
 282          yield 'Facility case 2' => [
 283              'Quiz 1 attempts' => [
 284                  $this->generate_attempt_answers([1, 0, 0, 0]),
 285                  $this->generate_attempt_answers([1, 1, 0, 0]),
 286                  $this->generate_attempt_answers([1, 1, 1, 0]),
 287              ],
 288              'Expected quiz 1 facilities' => ['100.00%', '66.67%', '33.33%', '0.00%'],
 289              'Quiz 2 attempts' => [
 290                  $this->generate_attempt_answers([1, 0, 0, 0]),
 291                  $this->generate_attempt_answers([1, 1, 0, 0]),
 292                  $this->generate_attempt_answers([1, 1, 1, 0]),
 293                  $this->generate_attempt_answers([1, 1, 1, 1]),
 294              ],
 295              'Expected quiz 2 facilities' => ['100.00%', '75.00%', '50.00%', '25.00%'],
 296              'Expected average facilities' => ['100.00%', '70.83%', '41.67%', '12.50%'],
 297          ];
 298      }
 299  
 300      /**
 301       * Test question facility
 302       *
 303       * @dataProvider load_question_facility_provider
 304       *
 305       * @param array $quiz1attempts quiz 1 attempts
 306       * @param array $expectedquiz1facilities expected quiz 1 facilities
 307       * @param array $quiz2attempts quiz 2 attempts
 308       * @param array $expectedquiz2facilities  expected quiz 2 facilities
 309       * @param array $expectedaveragefacilities expected average facilities
 310       */
 311      public function test_load_question_facility(
 312          array $quiz1attempts,
 313          array $expectedquiz1facilities,
 314          array $quiz2attempts,
 315          array $expectedquiz2facilities,
 316          array $expectedaveragefacilities)
 317      : void {
 318          $this->resetAfterTest();
 319  
 320          list($quiz1, $quiz2, $questions) = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
 321  
 322          // Quiz 1 facilities.
 323          $stats = $this->load_quiz_statistics_for_place(\context_module::instance($quiz1->cmid));
 324          $quiz1facility1 = $this->extract_item_value($stats, $questions[1]->id, 'facility');
 325          $quiz1facility2 = $this->extract_item_value($stats, $questions[2]->id, 'facility');
 326          $quiz1facility3 = $this->extract_item_value($stats, $questions[3]->id, 'facility');
 327          $quiz1facility4 = $this->extract_item_value($stats, $questions[4]->id, 'facility');
 328  
 329          $this->assertEquals($expectedquiz1facilities[0], helper::format_percentage($quiz1facility1));
 330          $this->assertEquals($expectedquiz1facilities[1], helper::format_percentage($quiz1facility2));
 331          $this->assertEquals($expectedquiz1facilities[2], helper::format_percentage($quiz1facility3));
 332          $this->assertEquals($expectedquiz1facilities[3], helper::format_percentage($quiz1facility4));
 333  
 334          // Quiz 2 facilities.
 335          $stats = $this->load_quiz_statistics_for_place(\context_module::instance($quiz2->cmid));
 336          $quiz2facility1 = $this->extract_item_value($stats, $questions[1]->id, 'facility');
 337          $quiz2facility2 = $this->extract_item_value($stats, $questions[2]->id, 'facility');
 338          $quiz2facility3 = $this->extract_item_value($stats, $questions[3]->id, 'facility');
 339          $quiz2facility4 = $this->extract_item_value($stats, $questions[4]->id, 'facility');
 340  
 341          $this->assertEquals($expectedquiz2facilities[0], helper::format_percentage($quiz2facility1));
 342          $this->assertEquals($expectedquiz2facilities[1], helper::format_percentage($quiz2facility2));
 343          $this->assertEquals($expectedquiz2facilities[2], helper::format_percentage($quiz2facility3));
 344          $this->assertEquals($expectedquiz2facilities[3], helper::format_percentage($quiz2facility4));
 345  
 346          // Average question facilities.
 347          $averagefacility1 = helper::calculate_average_question_facility($questions[1]->id);
 348          $averagefacility2 = helper::calculate_average_question_facility($questions[2]->id);
 349          $averagefacility3 = helper::calculate_average_question_facility($questions[3]->id);
 350          $averagefacility4 = helper::calculate_average_question_facility($questions[4]->id);
 351  
 352          $this->assertEquals($expectedaveragefacilities[0], helper::format_percentage($averagefacility1));
 353          $this->assertEquals($expectedaveragefacilities[1], helper::format_percentage($averagefacility2));
 354          $this->assertEquals($expectedaveragefacilities[2], helper::format_percentage($averagefacility3));
 355          $this->assertEquals($expectedaveragefacilities[3], helper::format_percentage($averagefacility4));
 356      }
 357  
 358      /**
 359       * Data provider for {@see test_load_question_discriminative_efficiency()}.
 360       * @return \Generator
 361       */
 362      public function load_question_discriminative_efficiency_provider(): \Generator {
 363          yield 'Discriminative efficiency' => [
 364              'Quiz 1 attempts' => [
 365                  $this->generate_attempt_answers([1, 0, 0, 0]),
 366                  $this->generate_attempt_answers([1, 1, 0, 0]),
 367                  $this->generate_attempt_answers([1, 0, 1, 0]),
 368                  $this->generate_attempt_answers([1, 1, 1, 1]),
 369              ],
 370              'Expected quiz 1 discriminative efficiency' => ['N/A', '33.33%', '33.33%', '100.00%'],
 371              'Quiz 2 attempts' => [
 372                  $this->generate_attempt_answers([1, 1, 1, 1]),
 373                  $this->generate_attempt_answers([0, 0, 0, 0]),
 374                  $this->generate_attempt_answers([1, 0, 0, 1]),
 375                  $this->generate_attempt_answers([0, 1, 1, 0]),
 376              ],
 377              'Expected quiz 2 discriminative efficiency' => ['50.00%', '50.00%', '50.00%', '50.00%'],
 378              'Expected average discriminative efficiency' => ['50.00%', '41.67%', '41.67%', '75.00%'],
 379          ];
 380      }
 381  
 382      /**
 383       * Test discriminative efficiency
 384       *
 385       * @dataProvider load_question_discriminative_efficiency_provider
 386       *
 387       * @param array $quiz1attempts quiz 1 attempts
 388       * @param array $expectedquiz1discriminativeefficiency expected quiz 1 discriminative efficiency
 389       * @param array $quiz2attempts quiz 2 attempts
 390       * @param array $expectedquiz2discriminativeefficiency expected quiz 2 discriminative efficiency
 391       * @param array $expectedaveragediscriminativeefficiency expected average discriminative efficiency
 392       */
 393      public function test_load_question_discriminative_efficiency(
 394          array $quiz1attempts,
 395          array $expectedquiz1discriminativeefficiency,
 396          array $quiz2attempts,
 397          array $expectedquiz2discriminativeefficiency,
 398          array $expectedaveragediscriminativeefficiency
 399      ): void {
 400          $this->resetAfterTest();
 401  
 402          list($quiz1, $quiz2, $questions) = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
 403  
 404          // Quiz 1 discriminative efficiency.
 405          $stats = $this->load_quiz_statistics_for_place(\context_module::instance($quiz1->cmid));
 406          $discriminativeefficiency1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminativeefficiency');
 407          $discriminativeefficiency2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminativeefficiency');
 408          $discriminativeefficiency3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminativeefficiency');
 409          $discriminativeefficiency4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminativeefficiency');
 410  
 411          $this->assertEquals($expectedquiz1discriminativeefficiency[0],
 412              helper::format_percentage($discriminativeefficiency1, false),
 413              "Failure in quiz 1 - question 1 discriminative efficiency");
 414          $this->assertEquals($expectedquiz1discriminativeefficiency[1],
 415              helper::format_percentage($discriminativeefficiency2, false),
 416              "Failure in quiz 1 - question 2 discriminative efficiency");
 417          $this->assertEquals($expectedquiz1discriminativeefficiency[2],
 418              helper::format_percentage($discriminativeefficiency3, false),
 419              "Failure in quiz 1 - question 3 discriminative efficiency");
 420          $this->assertEquals($expectedquiz1discriminativeefficiency[3],
 421              helper::format_percentage($discriminativeefficiency4, false),
 422              "Failure in quiz 1 - question 4 discriminative efficiency");
 423  
 424          // Quiz 2 discriminative efficiency.
 425          $stats = $this->load_quiz_statistics_for_place(\context_module::instance($quiz2->cmid));
 426          $discriminativeefficiency1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminativeefficiency');
 427          $discriminativeefficiency2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminativeefficiency');
 428          $discriminativeefficiency3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminativeefficiency');
 429          $discriminativeefficiency4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminativeefficiency');
 430  
 431          $this->assertEquals($expectedquiz2discriminativeefficiency[0],
 432              helper::format_percentage($discriminativeefficiency1, false),
 433              "Failure in quiz 2 - question 1 discriminative efficiency");
 434          $this->assertEquals($expectedquiz2discriminativeefficiency[1],
 435              helper::format_percentage($discriminativeefficiency2, false),
 436              "Failure in quiz 2 - question 2 discriminative efficiency");
 437          $this->assertEquals($expectedquiz2discriminativeefficiency[2],
 438              helper::format_percentage($discriminativeefficiency3, false),
 439              "Failure in quiz 2 - question 3 discriminative efficiency");
 440          $this->assertEquals($expectedquiz2discriminativeefficiency[3],
 441              helper::format_percentage($discriminativeefficiency4, false),
 442              "Failure in quiz 2 - question 4 discriminative efficiency");
 443  
 444          // Average question discriminative efficiency.
 445          $avgdiscriminativeefficiency1 = helper::calculate_average_question_discriminative_efficiency($questions[1]->id);
 446          $avgdiscriminativeefficiency2 = helper::calculate_average_question_discriminative_efficiency($questions[2]->id);
 447          $avgdiscriminativeefficiency3 = helper::calculate_average_question_discriminative_efficiency($questions[3]->id);
 448          $avgdiscriminativeefficiency4 = helper::calculate_average_question_discriminative_efficiency($questions[4]->id);
 449  
 450          $this->assertEquals($expectedaveragediscriminativeefficiency[0],
 451              helper::format_percentage($avgdiscriminativeefficiency1, false),
 452              "Failure in question 1 average discriminative efficiency");
 453          $this->assertEquals($expectedaveragediscriminativeefficiency[1],
 454              helper::format_percentage($avgdiscriminativeefficiency2, false),
 455              "Failure in question 2 average discriminative efficiency");
 456          $this->assertEquals($expectedaveragediscriminativeefficiency[2],
 457              helper::format_percentage($avgdiscriminativeefficiency3, false),
 458              "Failure in question 3 average discriminative efficiency");
 459          $this->assertEquals($expectedaveragediscriminativeefficiency[3],
 460              helper::format_percentage($avgdiscriminativeefficiency4, false),
 461              "Failure in question 4 average discriminative efficiency");
 462      }
 463  
 464      /**
 465       * Data provider for {@see test_load_question_discrimination_index()}.
 466       * @return \Generator
 467       */
 468      public function load_question_discrimination_index_provider(): \Generator {
 469          yield 'Discrimination Index' => [
 470              'Quiz 1 attempts' => [
 471                  $this->generate_attempt_answers([1, 0, 0, 0]),
 472                  $this->generate_attempt_answers([1, 1, 0, 0]),
 473                  $this->generate_attempt_answers([1, 0, 1, 0]),
 474                  $this->generate_attempt_answers([1, 1, 1, 1]),
 475              ],
 476              'Expected quiz 1 Discrimination Index' => ['N/A', '30.15%', '30.15%', '81.65%'],
 477              'Quiz 2 attempts' => [
 478                  $this->generate_attempt_answers([1, 1, 1, 1]),
 479                  $this->generate_attempt_answers([0, 0, 0, 0]),
 480                  $this->generate_attempt_answers([1, 0, 0, 1]),
 481                  $this->generate_attempt_answers([0, 1, 1, 0]),
 482              ],
 483              'Expected quiz 2 discrimination Index' => ['44.72%', '44.72%', '44.72%', '44.72%'],
 484              'Expected average discrimination Index' => ['44.72%', '37.44%', '37.44%', '63.19%'],
 485          ];
 486      }
 487  
 488      /**
 489       * Test discrimination index
 490       *
 491       * @dataProvider load_question_discrimination_index_provider
 492       *
 493       * @param array $quiz1attempts quiz 1 attempts
 494       * @param array $expectedquiz1discriminationindex expected quiz 1 discrimination index
 495       * @param array $quiz2attempts quiz 2 attempts
 496       * @param array $expectedquiz2discriminationindex expected quiz 2 discrimination index
 497       * @param array $expectedaveragediscriminationindex expected average discrimination index
 498       */
 499      public function test_load_question_discrimination_index(
 500          array $quiz1attempts,
 501          array $expectedquiz1discriminationindex,
 502          array $quiz2attempts,
 503          array $expectedquiz2discriminationindex,
 504          array $expectedaveragediscriminationindex
 505      ): void {
 506          $this->resetAfterTest();
 507  
 508          list($quiz1, $quiz2, $questions) = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
 509  
 510          // Quiz 1 discrimination index.
 511          $stats = $this->load_quiz_statistics_for_place(\context_module::instance($quiz1->cmid));
 512          $discriminationindex1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminationindex');
 513          $discriminationindex2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminationindex');
 514          $discriminationindex3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminationindex');
 515          $discriminationindex4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminationindex');
 516  
 517          $this->assertEquals($expectedquiz1discriminationindex[0],
 518              helper::format_percentage($discriminationindex1, false),
 519              "Failure in quiz 1 - question 1 discrimination index");
 520          $this->assertEquals($expectedquiz1discriminationindex[1],
 521              helper::format_percentage($discriminationindex2, false),
 522              "Failure in quiz 1 - question 2 discrimination index");
 523          $this->assertEquals($expectedquiz1discriminationindex[2],
 524              helper::format_percentage($discriminationindex3, false),
 525              "Failure in quiz 1 - question 3 discrimination index");
 526          $this->assertEquals($expectedquiz1discriminationindex[3],
 527              helper::format_percentage($discriminationindex4, false),
 528              "Failure in quiz 1 - question 4 discrimination index");
 529  
 530          // Quiz 2 discrimination index.
 531          $stats = $this->load_quiz_statistics_for_place(\context_module::instance($quiz2->cmid));
 532          $discriminationindex1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminationindex');
 533          $discriminationindex2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminationindex');
 534          $discriminationindex3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminationindex');
 535          $discriminationindex4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminationindex');
 536  
 537          $this->assertEquals($expectedquiz2discriminationindex[0],
 538              helper::format_percentage($discriminationindex1, false),
 539              "Failure in quiz 2 - question 1 discrimination index");
 540          $this->assertEquals($expectedquiz2discriminationindex[1],
 541              helper::format_percentage($discriminationindex2, false),
 542              "Failure in quiz 2 - question 2 discrimination index");
 543          $this->assertEquals($expectedquiz2discriminationindex[2],
 544              helper::format_percentage($discriminationindex3, false),
 545              "Failure in quiz 2 - question 3 discrimination index");
 546          $this->assertEquals($expectedquiz2discriminationindex[3],
 547              helper::format_percentage($discriminationindex4, false),
 548              "Failure in quiz 2 - question 4 discrimination index");
 549  
 550          // Average question discrimination index.
 551          $avgdiscriminationindex1 = helper::calculate_average_question_discrimination_index($questions[1]->id);
 552          $avgdiscriminationindex2 = helper::calculate_average_question_discrimination_index($questions[2]->id);
 553          $avgdiscriminationindex3 = helper::calculate_average_question_discrimination_index($questions[3]->id);
 554          $avgdiscriminationindex4 = helper::calculate_average_question_discrimination_index($questions[4]->id);
 555  
 556          $this->assertEquals($expectedaveragediscriminationindex[0],
 557              helper::format_percentage($avgdiscriminationindex1, false),
 558              "Failure in question 1 average discrimination index");
 559          $this->assertEquals($expectedaveragediscriminationindex[1],
 560              helper::format_percentage($avgdiscriminationindex2, false),
 561              "Failure in question 2 average discrimination index");
 562          $this->assertEquals($expectedaveragediscriminationindex[2],
 563              helper::format_percentage($avgdiscriminationindex3, false),
 564              "Failure in question 3 average discrimination index");
 565          $this->assertEquals($expectedaveragediscriminationindex[3],
 566              helper::format_percentage($avgdiscriminationindex4, false),
 567              "Failure in question 4 average discrimination index");
 568      }
 569  }