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