Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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 quiz_statistics\tests\statistics_helper;
  22  use question_engine;
  23  use quiz_attempt;
  24  
  25  /**
  26   * Tests for question statistics.
  27   *
  28   * @package    qbank_statistics
  29   * @copyright  2021 Catalyst IT Australia Pty Ltd
  30   * @author     Nathan Nguyen <nathannguyen@catalyst-au.net>
  31   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32   */
  33  class helper_test extends \advanced_testcase {
  34  
  35      /**
  36       * Test quizzes that contain a specified question.
  37       *
  38       * @covers ::get_all_places_where_questions_were_attempted
  39       * @throws \coding_exception
  40       * @throws \dml_exception
  41       */
  42      public function test_get_all_places_where_questions_were_attempted(): void {
  43          $this->resetAfterTest();
  44          $this->setAdminUser();
  45  
  46          $rcm = new \ReflectionMethod(helper::class, 'get_all_places_where_questions_were_attempted');
  47          $rcm->setAccessible(true);
  48  
  49          // Create a course.
  50          $course = $this->getDataGenerator()->create_course();
  51  
  52          // Create three quizzes.
  53          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
  54          $quiz1 = $quizgenerator->create_instance([
  55              'course' => $course->id,
  56              'grade' => 100.0, 'sumgrades' => 2,
  57              'layout' => '1,2,0'
  58          ]);
  59          $quiz1context = \context_module::instance($quiz1->cmid);
  60  
  61          $quiz2 = $quizgenerator->create_instance([
  62              'course' => $course->id,
  63              'grade' => 100.0, 'sumgrades' => 2,
  64              'layout' => '1,2,0'
  65          ]);
  66          $quiz2context = \context_module::instance($quiz2->cmid);
  67  
  68          $quiz3 = $quizgenerator->create_instance([
  69              'course' => $course->id,
  70              'grade' => 100.0, 'sumgrades' => 2,
  71              'layout' => '1,2,0'
  72          ]);
  73          $quiz3context = \context_module::instance($quiz3->cmid);
  74  
  75          // Create questions.
  76          /** @var \core_question_generator $questiongenerator */
  77          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
  78          $cat = $questiongenerator->create_question_category();
  79          $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
  80          $question2 = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
  81  
  82          // Add question 1 to quiz 1 and make an attempt.
  83          quiz_add_quiz_question($question1->id, $quiz1);
  84          // Quiz 1 attempt.
  85          $this->submit_quiz($quiz1, [1 => ['answer' => 'frog']]);
  86  
  87          // Add questions 1 and 2 to quiz 2.
  88          quiz_add_quiz_question($question1->id, $quiz2);
  89          quiz_add_quiz_question($question2->id, $quiz2);
  90          $this->submit_quiz($quiz2, [1 => ['answer' => 'frog'], 2 => ['answer' => 10]]);
  91  
  92          // Checking quizzes that use question 1.
  93          $q1places = $rcm->invoke(null, [$question1->id]);
  94          $this->assertCount(2, $q1places);
  95          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz1context->id], $q1places[0]);
  96          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q1places[1]);
  97  
  98          // Checking quizzes that contain question 2.
  99          $q2places = $rcm->invoke(null, [$question2->id]);
 100          $this->assertCount(1, $q2places);
 101          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q2places[0]);
 102  
 103          // Add a random question to quiz3.
 104          quiz_add_random_questions($quiz3, 0, $cat->id, 1, false);
 105          $this->submit_quiz($quiz3, [1 => ['answer' => 'willbewrong']]);
 106  
 107          // Quiz 3 will now be in one of these arrays.
 108          $q1places = $rcm->invoke(null, [$question1->id]);
 109          $q2places = $rcm->invoke(null, [$question2->id]);
 110          if (count($q1places) == 3) {
 111              $newplace = end($q1places);
 112          } else {
 113              $newplace = end($q2places);
 114          }
 115          $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz3context->id], $newplace);
 116      }
 117  
 118      /**
 119       * Create 2 quizzes.
 120       *
 121       * @return array return 2 quizzes
 122       * @throws \coding_exception
 123       */
 124      private function prepare_quizzes(): array {
 125          // Create a course.
 126          $course = $this->getDataGenerator()->create_course();
 127  
 128          // Make 2 quizzes.
 129          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
 130          $layout = '1,2,0,3,4,0';
 131          $quiz1 = $quizgenerator->create_instance([
 132              'course' => $course->id,
 133              'grade' => 100.0, 'sumgrades' => 2,
 134              'layout' => $layout
 135          ]);
 136  
 137          $quiz2 = $quizgenerator->create_instance([
 138              'course' => $course->id,
 139              'grade' => 100.0, 'sumgrades' => 2,
 140              'layout' => $layout
 141          ]);
 142  
 143          /** @var \core_question_generator $questiongenerator */
 144          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
 145          $cat = $questiongenerator->create_question_category();
 146  
 147          $page = 1;
 148          $questions = [];
 149          foreach (explode(',', $layout) as $slot) {
 150              if ($slot == 0) {
 151                  $page += 1;
 152                  continue;
 153              }
 154  
 155              $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
 156              $questions[$slot] = $question;
 157              quiz_add_quiz_question($question->id, $quiz1, $page);
 158              quiz_add_quiz_question($question->id, $quiz2, $page);
 159          }
 160  
 161          return [$quiz1, $quiz2, $questions];
 162      }
 163  
 164      /**
 165       * Submit quiz answers
 166       *
 167       * @param object $quiz
 168       * @param array $answers
 169       * @throws \moodle_exception
 170       */
 171      private function submit_quiz(object $quiz, array $answers): void {
 172          // Create user.
 173          $user = $this->getDataGenerator()->create_user();
 174          // Create attempt.
 175          $quizobj = quiz::create($quiz->id, $user->id);
 176          $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
 177          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
 178          $timenow = time();
 179          $attempt = quiz_create_attempt($quizobj, 1, null, $timenow, false, $user->id);
 180          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
 181          quiz_attempt_save_started($quizobj, $quba, $attempt);
 182          // Submit attempt.
 183          $attemptobj = quiz_attempt::create($attempt->id);
 184          $attemptobj->process_submitted_actions($timenow, false, $answers);
 185          $attemptobj->process_finish($timenow, false);
 186      }
 187  
 188      /**
 189       * Generate attempt answers.
 190       *
 191       * @param array $correctanswerflags array of 1 or 0
 192       * 1 : generate correct answer
 193       * 0 : generate wrong answer
 194       *
 195       * @return array
 196       */
 197      private function generate_attempt_answers(array $correctanswerflags): array {
 198          $attempt = [];
 199          for ($i = 1; $i <= 4; $i++) {
 200              if (isset($correctanswerflags) && $correctanswerflags[$i - 1] == 1) {
 201                  // Correct answer.
 202                  $attempt[$i] = ['answer' => 'frog'];
 203              } else {
 204                  $attempt[$i] = ['answer' => 'false'];
 205              }
 206          }
 207          return $attempt;
 208      }
 209  
 210      /**
 211       *
 212       * Generate quizzes and submit answers.
 213       *
 214       * @param array $quiz1attempts quiz 1 attempts
 215       * @param array $quiz2attempts quiz 2 attempts
 216       *
 217       * @return array
 218       */
 219      private function prepare_and_submit_quizzes(array $quiz1attempts, array $quiz2attempts): array {
 220          list($quiz1, $quiz2, $questions) = $this->prepare_quizzes();
 221          // Submit attempts of quiz1.
 222          foreach ($quiz1attempts as $attempt) {
 223              $this->submit_quiz($quiz1, $attempt);
 224          }
 225          // Submit attempts of quiz2.
 226          foreach ($quiz2attempts as $attempt) {
 227              $this->submit_quiz($quiz2, $attempt);
 228          }
 229  
 230          // Calculate the statistics.
 231          $this->expectOutputRegex('~.*Calculations completed.*~');
 232          statistics_helper::run_pending_recalculation_tasks();
 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  }