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