Proyectos de Subversion Moodle

Rev

Rev 11 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 11 Rev 1441
Línea 19... Línea 19...
19
use question_attempt;
19
use question_attempt;
20
use question_bank;
20
use question_bank;
21
use question_finder;
21
use question_finder;
22
use quiz_statistics_report;
22
use quiz_statistics_report;
Línea 23... Línea -...
23
 
-
 
24
defined('MOODLE_INTERNAL') || die();
-
 
25
 
-
 
26
global $CFG;
-
 
27
require_once($CFG->dirroot . '/mod/quiz/tests/attempt_walkthrough_from_csv_test.php');
-
 
28
require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
-
 
29
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
-
 
30
 
23
 
31
/**
24
/**
32
 * Quiz attempt walk through using data from csv file.
25
 * Quiz attempt walk through using data from csv file.
33
 *
26
 *
34
 * The quiz stats below and the question stats found in qstats00.csv were calculated independently in a spreadsheet which is
27
 * The quiz stats below and the question stats found in qstats00.csv were calculated independently in a spreadsheet which is
Línea 43... Línea 36...
43
 * @category   test
36
 * @category   test
44
 * @copyright  2013 The Open University
37
 * @copyright  2013 The Open University
45
 * @author     Jamie Pratt <me@jamiep.org>
38
 * @author     Jamie Pratt <me@jamiep.org>
46
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47
 */
40
 */
48
class stats_from_steps_walkthrough_test extends \mod_quiz\attempt_walkthrough_from_csv_test {
41
final class stats_from_steps_walkthrough_test extends \mod_quiz\tests\attempt_walkthrough_testcase {
49
 
-
 
50
    /**
42
    /**
51
     * @var quiz_statistics_report object to do stats calculations.
43
     * @var quiz_statistics_report object to do stats calculations.
52
     */
44
     */
53
    protected $report;
45
    protected $report;
Línea -... Línea 46...
-
 
46
 
54
 
47
    #[\Override]
-
 
48
    public static function setUpBeforeClass(): void {
-
 
49
        global $CFG;
-
 
50
 
-
 
51
        parent::setUpBeforeClass();
55
    protected function get_full_path_of_csv_file(string $setname, string $test): string {
52
 
56
        // Overridden here so that __DIR__ points to the path of this file.
53
        require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
57
        return  __DIR__."/fixtures/{$setname}{$test}.csv";
54
        require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
Línea 58... Línea 55...
58
    }
55
    }
59
 
56
 
60
    /**
-
 
61
     * @var string[] names of the files which contain the test data.
57
    #[\Override]
-
 
58
    protected static function get_test_files(): array {
Línea 62... Línea 59...
62
     */
59
        return ['questions', 'steps', 'results', 'qstats', 'responsecounts'];
63
    protected $files = ['questions', 'steps', 'results', 'qstats', 'responsecounts'];
60
    }
64
 
61
 
65
    /**
62
    /**
-
 
63
     * Create a quiz add questions to it, walk through quiz attempts and then check results.
66
     * Create a quiz add questions to it, walk through quiz attempts and then check results.
64
     *
67
     *
65
     * @param array $csvdata data read from csv file "questionsXX.csv", "stepsXX.csv" and "resultsXX.csv".
68
     * @param array $csvdata data read from csv file "questionsXX.csv", "stepsXX.csv" and "resultsXX.csv".
66
     * // phpcs:ignore moodle.PHPUnit.TestCaseProvider.dataProviderSyntaxMethodNotFound
69
     * @dataProvider get_data_for_walkthrough
-
 
70
     */
67
     * @dataProvider get_data_for_walkthrough
Línea 71... Línea 68...
71
    public function test_walkthrough_from_csv($quizsettings, $csvdata): void {
68
     */
72
 
69
    public function test_walkthrough_from_csv($quizsettings, $csvdata): void {
73
        $this->create_quiz_simulate_attempts_and_check_results($quizsettings, $csvdata);
70
        $this->create_quiz_simulate_attempts_and_check_results($quizsettings, $csvdata);
74
 
71
 
75
        $whichattempts = QUIZ_GRADEAVERAGE; // All attempts.
72
        $whichattempts = QUIZ_GRADEAVERAGE; // All attempts.
-
 
73
        $whichtries = question_attempt::ALL_TRIES;
-
 
74
        $groupstudentsjoins = new \core\dml\sql_join();
-
 
75
        [$questions, $quizstats, $questionstats, $qubaids] =
76
        $whichtries = question_attempt::ALL_TRIES;
76
                    $this->check_stats_calculations_and_response_analysis(
-
 
77
                        $csvdata,
77
        $groupstudentsjoins = new \core\dml\sql_join();
78
                        $whichattempts,
78
        list($questions, $quizstats, $questionstats, $qubaids) =
79
                        $whichtries,
79
                    $this->check_stats_calculations_and_response_analysis($csvdata,
80
                        $groupstudentsjoins
80
                            $whichattempts, $whichtries, $groupstudentsjoins);
81
                    );
81
        if ($quizsettings['testnumber'] === '00') {
82
        if ($quizsettings['testnumber'] === '00') {
Línea 92... Línea 93...
92
     */
93
     */
93
    protected function check_question_stats($qstats, $questionstats) {
94
    protected function check_question_stats($qstats, $questionstats) {
94
        foreach ($qstats as $slotqstats) {
95
        foreach ($qstats as $slotqstats) {
95
            foreach ($slotqstats as $statname => $slotqstat) {
96
            foreach ($slotqstats as $statname => $slotqstat) {
96
                if (!in_array($statname, ['slot', 'subqname'])  && $slotqstat !== '') {
97
                if (!in_array($statname, ['slot', 'subqname'])  && $slotqstat !== '') {
97
                    $this->assert_stat_equals($slotqstat,
98
                    $this->assert_stat_equals(
-
 
99
                        $slotqstat,
98
                                              $questionstats,
100
                        $questionstats,
99
                                              $slotqstats['slot'],
101
                        $slotqstats['slot'],
100
                                              $slotqstats['subqname'],
102
                        $slotqstats['subqname'],
101
                                              $slotqstats['variant'],
103
                        $slotqstats['variant'],
102
                                              $statname);
104
                        $statname
-
 
105
                    );
103
                }
106
                }
104
            }
107
            }
105
            // Check that sub-question boolean field is correctly set.
108
            // Check that sub-question boolean field is correctly set.
-
 
109
            $this->assert_stat_equals(
106
            $this->assert_stat_equals(!empty($slotqstats['subqname']),
110
                !empty($slotqstats['subqname']),
107
                                      $questionstats,
111
                $questionstats,
108
                                      $slotqstats['slot'],
112
                $slotqstats['slot'],
109
                                      $slotqstats['subqname'],
113
                $slotqstats['subqname'],
110
                                      $slotqstats['variant'],
114
                $slotqstats['variant'],
111
                                      'subquestion');
115
                'subquestion'
-
 
116
            );
112
        }
117
        }
113
    }
118
    }
Línea 114... Línea 119...
114
 
119
 
115
    /**
120
    /**
Línea 136... Línea 141...
136
            $this->assertEquals(null, $actual, $message);
141
            $this->assertEquals(null, $actual, $message);
137
        } else if (is_bool($expected)) {
142
        } else if (is_bool($expected)) {
138
            $this->assertEquals($expected, $actual, $message);
143
            $this->assertEquals($expected, $actual, $message);
139
        } else if (is_numeric($expected)) {
144
        } else if (is_numeric($expected)) {
140
            switch ($statname) {
145
            switch ($statname) {
141
                case 'covariance' :
146
                case 'covariance':
142
                case 'discriminationindex' :
147
                case 'discriminationindex':
143
                case 'discriminativeefficiency' :
148
                case 'discriminativeefficiency':
144
                case 'effectiveweight' :
149
                case 'effectiveweight':
145
                    $precision = 1e-5;
150
                    $precision = 1e-5;
146
                    break;
151
                    break;
147
                default :
152
                default:
148
                    $precision = 1e-6;
153
                    $precision = 1e-6;
149
            }
154
            }
150
            $delta = abs($expected) * $precision;
155
            $delta = abs($expected) * $precision;
151
            $this->assertEqualsWithDelta((float)$expected, $actual, $delta, $message);
156
            $this->assertEqualsWithDelta((float)$expected, $actual, $delta, $message);
152
        } else {
157
        } else {
153
            $this->assertEquals($expected, $actual, $message);
158
            $this->assertEquals($expected, $actual, $message);
154
        }
159
        }
155
    }
160
    }
Línea -... Línea 161...
-
 
161
 
-
 
162
    /**
-
 
163
     * Assertion helper to check that response counts are as expected.
-
 
164
     *
-
 
165
     * @param $question
-
 
166
     * @param $qubaids
-
 
167
     * @param $expected
-
 
168
     * @param $whichtries
156
 
169
     */
157
    protected function assert_response_count_equals($question, $qubaids, $expected, $whichtries) {
170
    protected function assert_response_count_equals($question, $qubaids, $expected, $whichtries): void {
158
        $responesstats = new \core_question\statistics\responses\analyser($question);
171
        $responesstats = new \core_question\statistics\responses\analyser($question);
159
        $analysis = $responesstats->load_cached($qubaids, $whichtries);
172
        $analysis = $responesstats->load_cached($qubaids, $whichtries);
160
        if (!isset($expected['subpart'])) {
173
        if (!isset($expected['subpart'])) {
161
            $subpart = 1;
174
            $subpart = 1;
162
        } else {
175
        } else {
163
            $subpart = $expected['subpart'];
176
            $subpart = $expected['subpart'];
164
        }
177
        }
-
 
178
        [$subpartid, $responseclassid] = $this->get_response_subpart_and_class_id(
165
        list($subpartid, $responseclassid) = $this->get_response_subpart_and_class_id($question,
179
            $question,
166
                                                                                      $subpart,
180
            $subpart,
-
 
181
            $expected['modelresponse']
Línea 167... Línea 182...
167
                                                                                      $expected['modelresponse']);
182
        );
168
 
183
 
169
        $subpartanalysis = $analysis->get_analysis_for_subpart($expected['variant'], $subpartid);
184
        $subpartanalysis = $analysis->get_analysis_for_subpart($expected['variant'], $subpartid);
Línea 170... Línea 185...
170
        $responseclassanalysis = $subpartanalysis->get_response_class($responseclassid);
185
        $responseclassanalysis = $subpartanalysis->get_response_class($responseclassid);
171
        $actualresponsecounts = $responseclassanalysis->data_for_question_response_table('', '');
186
        $actualresponsecounts = $responseclassanalysis->data_for_question_response_table('', '');
172
 
187
 
173
        foreach ($actualresponsecounts as $actualresponsecount) {
188
        foreach ($actualresponsecounts as $actualresponsecount) {
174
            if ($actualresponsecount->response == $expected['actualresponse'] || count($actualresponsecounts) == 1) {
189
            if ($actualresponsecount->response == $expected['actualresponse'] || count($actualresponsecounts) == 1) {
175
                $i = 1;
190
                $i = 1;
176
                $partofanalysis = " slot {$expected['slot']}, rand q '{$expected['randq']}', variant {$expected['variant']}, ".
191
                $partofanalysis = " slot {$expected['slot']}, rand q '{$expected['randq']}', variant {$expected['variant']}, " .
177
                                    "for expected model response {$expected['modelresponse']}, ".
192
                                    "for expected model response {$expected['modelresponse']}, " .
-
 
193
                                    "actual response {$expected['actualresponse']}";
178
                                    "actual response {$expected['actualresponse']}";
194
                while (isset($expected['count' . $i])) {
179
                while (isset($expected['count'.$i])) {
195
                    if ($expected['count' . $i] != 0) {
-
 
196
                        $this->assertTrue(
-
 
197
                            isset($actualresponsecount->trycount[$i]),
-
 
198
                            "There is no count at all for try $i on " . $partofanalysis
180
                    if ($expected['count'.$i] != 0) {
199
                        );
181
                        $this->assertTrue(isset($actualresponsecount->trycount[$i]),
200
                        $this->assertEquals(
-
 
201
                            $expected['count' . $i],
182
                            "There is no count at all for try $i on ".$partofanalysis);
202
                            $actualresponsecount->trycount[$i],
183
                        $this->assertEquals($expected['count'.$i], $actualresponsecount->trycount[$i],
203
                            "Count for try $i on " . $partofanalysis
184
                                            "Count for try $i on ".$partofanalysis);
204
                        );
185
                    }
205
                    }
-
 
206
                    $i++;
-
 
207
                }
186
                    $i++;
208
                if (isset($expected['totalcount'])) {
187
                }
209
                    $this->assertEquals(
-
 
210
                        $expected['totalcount'],
188
                if (isset($expected['totalcount'])) {
211
                        $actualresponsecount->totalcount,
189
                    $this->assertEquals($expected['totalcount'], $actualresponsecount->totalcount,
212
                        "Total count on " . $partofanalysis
190
                                        "Total count on ".$partofanalysis);
213
                    );
191
                }
214
                }
192
                return;
215
                return;
193
            }
216
            }
Línea -... Línea 217...
-
 
217
        }
-
 
218
        throw new \coding_exception("Expected response '{$expected['actualresponse']}' not found.");
-
 
219
    }
-
 
220
 
-
 
221
    /**
-
 
222
     * Get the subpart id and response class id for a given subpart and model response.
-
 
223
     *
-
 
224
     * @param $question
194
        }
225
     * @param $subpart
195
        throw new \coding_exception("Expected response '{$expected['actualresponse']}' not found.");
226
     * @param $modelresponse
196
    }
227
     * @return array
197
 
228
     */
198
    protected function get_response_subpart_and_class_id($question, $subpart, $modelresponse) {
229
    protected function get_response_subpart_and_class_id($question, $subpart, $modelresponse): array {
199
        $qtypeobj = question_bank::get_qtype($question->qtype, false);
230
        $qtypeobj = question_bank::get_qtype($question->qtype, false);
200
        $possibleresponses = $qtypeobj->get_possible_responses($question);
231
        $possibleresponses = $qtypeobj->get_possible_responses($question);
201
        $possibleresponsesubpartids = array_keys($possibleresponses);
232
        $possibleresponsesubpartids = array_keys($possibleresponses);
Línea 202... Línea 233...
202
        if (!isset($possibleresponsesubpartids[$subpart - 1])) {
233
        if (!isset($possibleresponsesubpartids[$subpart - 1])) {
203
            throw new \coding_exception("Subpart '{$subpart}' not found.");
234
            throw new \coding_exception("Subpart '{$subpart}' not found.");
204
        }
-
 
205
        $subpartid = $possibleresponsesubpartids[$subpart - 1];
235
        }
206
 
236
        $subpartid = $possibleresponsesubpartids[$subpart - 1];
207
        if ($modelresponse == '[NO RESPONSE]') {
237
 
Línea 208... Línea 238...
208
            return [$subpartid, null];
238
        if ($modelresponse == '[NO RESPONSE]') {
Línea 219... Línea 249...
219
        $responseclassid = array_search($modelresponse, $modelresponses);
249
        $responseclassid = array_search($modelresponse, $modelresponses);
220
        return [$subpartid, $responseclassid];
250
        return [$subpartid, $responseclassid];
221
    }
251
    }
Línea 222... Línea 252...
222
 
252
 
-
 
253
    /**
-
 
254
     * Assertion helper to check that response counts are as expected.
223
    /**
255
     *
224
     * @param $responsecounts
256
     * @param $responsecounts
225
     * @param $qubaids
257
     * @param $qubaids
226
     * @param $questions
258
     * @param $questions
227
     * @param $whichtries
259
     * @param $whichtries
Línea 243... Línea 275...
243
            $this->assert_response_count_equals($question, $qubaids, $expected, $whichtries);
275
            $this->assert_response_count_equals($question, $qubaids, $expected, $whichtries);
244
        }
276
        }
245
    }
277
    }
Línea 246... Línea 278...
246
 
278
 
-
 
279
    /**
-
 
280
     * Assertion helper to check that variant counts are as expected for quiz 00.
247
    /**
281
     *
248
     * @param $questions
282
     * @param $questions
249
     * @param $questionstats
283
     * @param $questionstats
250
     * @param $whichtries
284
     * @param $whichtries
251
     * @param $qubaids
285
     * @param $qubaids
Línea 273... Línea 307...
273
            } else {
307
            } else {
274
                $this->assertEquals([1], $variantsnos);
308
                $this->assertEquals([1], $variantsnos);
275
            }
309
            }
276
            $totalspervariantno = [];
310
            $totalspervariantno = [];
277
            foreach ($variantsnos as $variantno) {
311
            foreach ($variantsnos as $variantno) {
278
 
-
 
279
                $subpartids = $analysis->get_subpart_ids($variantno);
312
                $subpartids = $analysis->get_subpart_ids($variantno);
280
                foreach ($subpartids as $subpartid) {
313
                foreach ($subpartids as $subpartid) {
281
                    if (!isset($totalspervariantno[$subpartid])) {
314
                    if (!isset($totalspervariantno[$subpartid])) {
282
                        $totalspervariantno[$subpartid] = [];
315
                        $totalspervariantno[$subpartid] = [];
283
                    }
316
                    }
Línea 304... Línea 337...
304
                // not counted in response analysis for this question type.
337
                // not counted in response analysis for this question type.
305
                foreach ($totalspervariantno as $totalpervariantno) {
338
                foreach ($totalspervariantno as $totalpervariantno) {
306
                    if (isset($expectedvariantcounts[$slot])) {
339
                    if (isset($expectedvariantcounts[$slot])) {
307
                        // If we know how many attempts there are at each variant we can check
340
                        // If we know how many attempts there are at each variant we can check
308
                        // that we have counted the correct amount of responses for each variant.
341
                        // that we have counted the correct amount of responses for each variant.
309
                        $this->assertEqualsCanonicalizing($expectedvariantcounts[$slot],
342
                        $this->assertEqualsCanonicalizing(
-
 
343
                            $expectedvariantcounts[$slot],
310
                                            $totalpervariantno,
344
                            $totalpervariantno,
311
                                            "Totals responses do not add up in response analysis for slot {$slot}.");
345
                            "Totals responses do not add up in response analysis for slot {$slot}."
-
 
346
                        );
312
                    } else {
347
                    } else {
313
                        $this->assertEquals(25,
348
                        $this->assertEquals(
-
 
349
                            25,
314
                                            array_sum($totalpervariantno),
350
                            array_sum($totalpervariantno),
315
                                            "Totals responses do not add up in response analysis for slot {$slot}.");
351
                            "Totals responses do not add up in response analysis for slot {$slot}."
-
 
352
                        );
316
                    }
353
                    }
317
                }
354
                }
318
            }
355
            }
319
        }
356
        }
Línea 324... Línea 361...
324
            }
361
            }
325
        }
362
        }
326
    }
363
    }
Línea 327... Línea 364...
327
 
364
 
-
 
365
    /**
-
 
366
     * Assertion helper to ensure that quiz states are as expected.
328
    /**
367
     *
329
     * @param $quizstats
368
     * @param $quizstats
330
     */
369
     */
331
    protected function check_quiz_stats_for_quiz_00($quizstats) {
370
    protected function check_quiz_stats_for_quiz_00($quizstats) {
332
        $quizstatsexpected = [
371
        $quizstatsexpected = [
Línea 338... Línea 377...
338
            'standarddeviation'  => 0.8117265554,
377
            'standarddeviation'  => 0.8117265554,
339
            'skewness'           => -0.092502502,
378
            'skewness'           => -0.092502502,
340
            'kurtosis'           => -0.7073968557,
379
            'kurtosis'           => -0.7073968557,
341
            'cic'                => -87.2230935542,
380
            'cic'                => -87.2230935542,
342
            'errorratio'         => 136.8294900795,
381
            'errorratio'         => 136.8294900795,
343
            'standarderror'      => 1.1106813066
382
            'standarderror'      => 1.1106813066,
344
        ];
383
        ];
Línea 345... Línea 384...
345
 
384
 
346
        foreach ($quizstatsexpected as $statname => $statvalue) {
385
        foreach ($quizstatsexpected as $statname => $statvalue) {
347
            $this->assertEqualsWithDelta($statvalue, $quizstats->$statname, abs($statvalue) * 1.5e-5, $quizstats->$statname);
386
            $this->assertEqualsWithDelta($statvalue, $quizstats->$statname, abs($statvalue) * 1.5e-5, $quizstats->$statname);
Línea 356... Línea 395...
356
     * @param string $whichtries
395
     * @param string $whichtries
357
     * @param \core\dml\sql_join $groupstudentsjoins
396
     * @param \core\dml\sql_join $groupstudentsjoins
358
     * @return array with contents 0 => $questions, 1 => $quizstats, 2 => $questionstats, 3 => $qubaids Might be needed for further
397
     * @return array with contents 0 => $questions, 1 => $quizstats, 2 => $questionstats, 3 => $qubaids Might be needed for further
359
     *               testing.
398
     *               testing.
360
     */
399
     */
361
    protected function check_stats_calculations_and_response_analysis($csvdata, $whichattempts, $whichtries,
400
    protected function check_stats_calculations_and_response_analysis(
-
 
401
        $csvdata,
-
 
402
        $whichattempts,
-
 
403
        $whichtries,
362
            \core\dml\sql_join $groupstudentsjoins) {
404
        \core\dml\sql_join $groupstudentsjoins
-
 
405
    ) {
363
        $this->report = new quiz_statistics_report();
406
        $this->report = new quiz_statistics_report();
364
        $questions = $this->report->load_and_initialise_questions_for_calculations($this->quiz);
407
        $questions = $this->report->load_and_initialise_questions_for_calculations($this->quiz);
365
        list($quizstats, $questionstats) = $this->report->get_all_stats_and_analysis($this->quiz,
408
        [$quizstats, $questionstats] = $this->report->get_all_stats_and_analysis(
-
 
409
            $this->quiz,
366
                                                                                     $whichattempts,
410
            $whichattempts,
367
                                                                                     $whichtries,
411
            $whichtries,
368
                                                                                     $groupstudentsjoins,
412
            $groupstudentsjoins,
369
                                                                                     $questions);
413
            $questions
-
 
414
        );
Línea 370... Línea 415...
370
 
415
 
Línea 371... Línea 416...
371
        $qubaids = quiz_statistics_qubaids_condition($this->quiz->id, $groupstudentsjoins, $whichattempts);
416
        $qubaids = quiz_statistics_qubaids_condition($this->quiz->id, $groupstudentsjoins, $whichattempts);
372
 
417
 
Línea 386... Línea 431...
386
            $this->check_question_stats($csvdata['qstats'], $questionstats);
431
            $this->check_question_stats($csvdata['qstats'], $questionstats);
387
            return [$questions, $quizstats, $questionstats, $qubaids];
432
            return [$questions, $quizstats, $questionstats, $qubaids];
388
        }
433
        }
389
        return [$questions, $quizstats, $questionstats, $qubaids];
434
        return [$questions, $quizstats, $questionstats, $qubaids];
390
    }
435
    }
391
 
-
 
392
}
436
}