Search moodle.org's
Developer Documentation

  • 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 37 and 311] [Versions 38 and 311] [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  /**
      18   * Unit tests for (some of) /question/engine/statistics.php
      19   *
      20   * @package   quiz_statistics
      21   * @category  phpunit
      22   * @copyright 2008 Jamie Pratt
      23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      24   */
      25  
      26  defined('MOODLE_INTERNAL') || die();
      27  
      28  global $CFG;
      29  require_once($CFG->libdir . '/questionlib.php');
      30  require_once($CFG->dirroot . '/mod/quiz/locallib.php');
      31  require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
      32  
      33  class testable_all_calculated_for_qubaid_condition extends \core_question\statistics\questions\all_calculated_for_qubaid_condition {
      34  
      35      /**
      36       * Disabling caching in tests so we are always sure to force the calculation of stats right then and there.
      37       *
      38       * @param qubaid_condition $qubaids
      39       */
      40      public function cache($qubaids) {
      41  
      42      }
      43  }
      44  
      45  /**
      46   * Test helper subclass of question_statistics
      47   *
      48   * @copyright 2010 The Open University
      49   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      50   */
      51  class testable_question_statistics extends \core_question\statistics\questions\calculator {
      52  
      53      /**
      54       * @var object[]
      55       */
      56      protected $lateststeps;
      57  
      58      protected $statscollectionclassname = 'testable_all_calculated_for_qubaid_condition';
      59  
      60      public function set_step_data($states) {
      61          $this->lateststeps = $states;
      62      }
      63  
      64      protected function get_random_guess_score($questiondata) {
      65          return 0;
      66      }
      67  
      68      /**
      69       * @param $qubaids qubaid_condition is ignored in this test
      70       * @return array with two items
      71       *              - $lateststeps array of latest step data for the question usages
      72       *              - $summarks    array of total marks for each usage, indexed by usage id
      73       */
      74      protected function get_latest_steps($qubaids) {
      75          $summarks = array();
      76          $fakeusageid = 0;
      77          foreach ($this->lateststeps as $step) {
      78              // The same 'sumgrades' field is available in step data for every slot, we will ignore all slots but slot 1.
      79              // The step for slot 1 is always the first one in the csv file for each usage, we will use that to separate steps from
      80              // each usage.
      81              if ($step->slot == 1) {
      82                  $fakeusageid++;
      83                  $summarks[$fakeusageid] = $step->sumgrades;
      84              }
      85              unset($step->sumgrades);
      86              $step->questionusageid = $fakeusageid;
      87          }
      88  
      89          return array($this->lateststeps, $summarks);
      90      }
      91  
      92      protected function cache_stats($qubaids) {
      93          // No caching wanted for tests.
      94      }
      95  }
      96  /**
      97   * Unit tests for (some of) question_statistics.
      98   *
      99   * @copyright 2008 Jamie Pratt
     100   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     101   */
     102  class quiz_statistics_question_stats_testcase extends basic_testcase {
     103      /** @var testable_all_calculated_for_qubaid_condition object created to test class. */
     104      protected $qstats;
     105  
     106      public function test_qstats() {
     107          global $CFG;
     108          // Data is taken from randomly generated attempts data generated by
     109          // contrib/tools/generators/qagenerator/.
     110          $steps = $this->get_records_from_csv(__DIR__.'/fixtures/mdl_question_states.csv');
     111          // Data is taken from questions mostly generated by
     112          // contrib/tools/generators/generator.php.
     113          $questions = $this->get_records_from_csv(__DIR__.'/fixtures/mdl_question.csv');
     114          $calculator = new testable_question_statistics($questions);
     115          $calculator->set_step_data($steps);
     116          $this->qstats = $calculator->calculate(null);
     117  
     118          // Values expected are taken from contrib/tools/quiz_tools/stats.xls.
     119          $facility = array(0, 0, 0, 0, null, null, null, 41.19318182, 81.36363636,
     120              71.36363636, 65.45454545, 65.90909091, 36.36363636, 59.09090909, 50,
     121              59.09090909, 63.63636364, 45.45454545, 27.27272727, 50);
     122          $this->qstats_q_fields('facility', $facility, 100);
     123          $sd = array(0, 0, 0, 0, null, null, null, 1912.733589, 251.2738111,
     124              322.6312277, 333.4199022, 337.5811591, 492.3659639, 503.2362797,
     125              511.7663157, 503.2362797, 492.3659639, 509.6471914, 455.8423058, 511.7663157);
     126          $this->qstats_q_fields('sd', $sd, 1000);
     127          $effectiveweight = array(0, 0, 0, 0, 0, 0, 0, 26.58464457, 3.368456046,
     128              3.253955259, 7.584083694, 3.79658376, 3.183278505, 4.532356904,
     129              7.78856243, 10.08351572, 8.381139345, 8.727645713, 7.946277111, 4.769500946);
     130          $this->qstats_q_fields('effectiveweight', $effectiveweight);
     131          $discriminationindex = array(null, null, null, null, null, null, null,
     132              25.88327077, 1.170256965, -4.207816809, 28.16930644, -2.513606859,
     133              -12.99017581, -8.900638238, 8.670004606, 29.63337745, 15.18945843,
     134              16.21079629, 15.52451404, -8.396734802);
     135          $this->qstats_q_fields('discriminationindex', $discriminationindex);
     136          $discriminativeefficiency = array(null, null, null, null, null, null, null,
     137              27.23492723, 1.382386552, -4.691171307, 31.12404354, -2.877487579,
     138              -17.5074184, -10.27568922, 10.86956522, 34.58997279, 17.4790556,
     139              20.14359793, 22.06477733, -10);
     140          $this->qstats_q_fields('discriminativeefficiency', $discriminativeefficiency);
     141      }
     142  
     143      public function qstats_q_fields($fieldname, $values, $multiplier=1) {
     144          foreach ($this->qstats->get_all_slots() as $slot) {
     145              $value = array_shift($values);
     146              if ($value !== null) {
     147                  $this->assertEqualsWithDelta($value, $this->qstats->for_slot($slot)->{$fieldname} * $multiplier, 1E-6);
     148              } else {
     149                  $this->assertEquals($value, $this->qstats->for_slot($slot)->{$fieldname} * $multiplier);
     150              }
     151          }
     152      }
     153  
     154      public function get_fields_from_csv($line) {
     155          $line = trim($line);
     156          $items = preg_split('!,!', $line);
     157          $cnt = count($items);
     158          for ($key = 0; $key < $cnt; $key++) {
     159              if ($items[$key]!='') {
     160                  if ($start = ($items[$key][0]=='"')) {
     161                      $items[$key] = substr($items[$key], 1);
     162                      while (!$end = ($items[$key][strlen($items[$key])-1]=='"')) {
     163                          $item = $items[$key];
     164                          unset($items[$key]);
     165                          $key++;
     166                          $items[$key] = $item . ',' . $items[$key];
     167                      }
     168                      $items[$key] = substr($items[$key], 0, strlen($items[$key])-1);
     169                  }
     170  
     171              }
     172          }
     173          return $items;
     174      }
     175  
     176      public function get_records_from_csv($filename) {
     177          $filecontents = file($filename, FILE_IGNORE_NEW_LINES);
     178          $records = array();
     179          // Skip the first line containing field names.
     180          $keys = $this->get_fields_from_csv(array_shift($filecontents));
     181          while (null !== ($line = array_shift($filecontents))) {
     182              $data = $this->get_fields_from_csv($line);
     183              $arraykey = reset($data);
     184              $object = new stdClass();
     185              foreach ($keys as $key) {
     186                  $value = array_shift($data);
     187                  if ($value !== null) {
     188                      $object->{$key} = $value;
     189                  } else {
     190                      $object->{$key} = '';
     191                  }
     192              }
     193              $records[$arraykey] = $object;
     194          }
     195          return $records;
     196      }
     197  }