Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
namespace core_question\local\statistics;
18
 
19
use advanced_testcase;
20
use context;
21
use context_module;
22
use core_question\statistics\questions\all_calculated_for_qubaid_condition;
23
use quiz_statistics\tests\statistics_helper;
24
use core_question_generator;
25
use Generator;
26
use mod_quiz\quiz_attempt;
27
use mod_quiz\quiz_settings;
28
use question_engine;
29
use ReflectionMethod;
30
 
31
/**
32
 * Tests for question statistics.
33
 *
34
 * @package   core_question
35
 * @copyright 2021 Catalyst IT Australia Pty Ltd
36
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 * @covers \core_question\local\statistics\statistics_bulk_loader
38
 */
1441 ariadna 39
final class statistics_bulk_loader_test extends advanced_testcase {
40
    use \mod_quiz\tests\question_helper_test_trait;
1 efrain 41
 
42
    /** @var float Delta used when comparing statistics values out-of 1. */
43
    protected const DELTA = 0.00005;
44
 
45
    /** @var float Delta used when comparing statistics values out-of 100. */
46
    protected const PERCENT_DELTA = 0.005;
47
 
48
    /**
49
     * Test quizzes that contain a specified question.
50
     *
51
     * @covers ::get_all_places_where_questions_were_attempted
52
     */
53
    public function test_get_all_places_where_questions_were_attempted(): void {
54
        global $DB;
55
        $this->resetAfterTest();
56
        $this->setAdminUser();
57
 
58
        $rcm = new ReflectionMethod(statistics_bulk_loader::class, 'get_all_places_where_questions_were_attempted');
59
 
60
        // Create a course.
61
        $course = $this->getDataGenerator()->create_course();
62
 
63
        // Create three quizzes.
64
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
65
        $quiz1 = $quizgenerator->create_instance([
66
            'course' => $course->id,
67
            'grade' => 100.0, 'sumgrades' => 2,
1441 ariadna 68
            'layout' => '1,2,0',
1 efrain 69
        ]);
70
        $quiz1context = context_module::instance($quiz1->cmid);
71
 
72
        $quiz2 = $quizgenerator->create_instance([
73
            'course' => $course->id,
74
            'grade' => 100.0, 'sumgrades' => 2,
1441 ariadna 75
            'layout' => '1,2,0',
1 efrain 76
        ]);
77
        $quiz2context = context_module::instance($quiz2->cmid);
78
 
79
        $quiz3 = $quizgenerator->create_instance([
80
            'course' => $course->id,
81
            'grade' => 100.0, 'sumgrades' => 2,
1441 ariadna 82
            'layout' => '1,2,0',
1 efrain 83
        ]);
84
        $quiz3context = context_module::instance($quiz3->cmid);
85
 
86
        // Create questions.
87
        /** @var core_question_generator $questiongenerator */
88
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
89
        $cat = $questiongenerator->create_question_category();
90
        $question1 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
91
        $question2 = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
92
 
93
        // Add question 1 to quiz 1 and make an attempt.
94
        quiz_add_quiz_question($question1->id, $quiz1);
95
        // Quiz 1 attempt.
96
        $this->submit_quiz($quiz1, [1 => ['answer' => 'frog']]);
97
 
98
        // Add questions 1 and 2 to quiz 2.
99
        quiz_add_quiz_question($question1->id, $quiz2);
100
        quiz_add_quiz_question($question2->id, $quiz2);
101
        $this->submit_quiz($quiz2, [1 => ['answer' => 'frog'], 2 => ['answer' => 10]]);
102
 
103
        // Checking quizzes that use question 1.
104
        $q1places = $rcm->invoke(null, [$question1->id]);
105
        $this->assertCount(2, $q1places);
106
        $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz1context->id], $q1places[0]);
107
        $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q1places[1]);
108
 
109
        // Checking quizzes that contain question 2.
110
        $q2places = $rcm->invoke(null, [$question2->id]);
111
        $this->assertCount(1, $q2places);
112
        $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q2places[0]);
113
 
114
        // Add a random question to quiz3.
115
        $this->add_random_questions($quiz3->id, 0, $cat->id, 1, false);
116
        $this->submit_quiz($quiz3, [1 => ['answer' => 'willbewrong']]);
117
 
118
        // Quiz 3 will now be in one of these arrays.
119
        $q1places = $rcm->invoke(null, [$question1->id]);
120
        $q2places = $rcm->invoke(null, [$question2->id]);
121
        if (count($q1places) == 3) {
122
            $newplace = end($q1places);
123
        } else {
124
            $newplace = end($q2places);
125
        }
126
        $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz3context->id], $newplace);
127
 
128
        // Simulate the situation where the context for quiz3 is gone from the database, without
129
        // the corresponding attempt data being properly cleaned up. Ensure this does not cause errors.
130
        $DB->delete_records('context', ['id' => context_module::instance($quiz3->cmid)->id]);
131
        accesslib_clear_all_caches_for_unit_testing();
132
        // Same asserts as above, before we added quiz3.
133
        $q1places = $rcm->invoke(null, [$question1->id]);
134
        $this->assertCount(2, $q1places);
135
        $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz1context->id], $q1places[0]);
136
        $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q1places[1]);
137
        $q2places = $rcm->invoke(null, [$question2->id]);
138
        $this->assertCount(1, $q2places);
139
        $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q2places[0]);
140
    }
141
 
142
    /**
143
     * Create 2 quizzes.
144
     *
145
     * @return array return 2 quizzes
146
     */
147
    private function prepare_quizzes(): array {
148
        // Create a course.
149
        $course = $this->getDataGenerator()->create_course();
150
 
151
        // Make 2 quizzes.
152
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
153
        $layout = '1,2,0,3,4,0';
154
        $quiz1 = $quizgenerator->create_instance([
155
            'course' => $course->id,
156
            'grade' => 100.0, 'sumgrades' => 2,
1441 ariadna 157
            'layout' => $layout,
1 efrain 158
        ]);
159
 
160
        $quiz2 = $quizgenerator->create_instance([
161
            'course' => $course->id,
162
            'grade' => 100.0, 'sumgrades' => 2,
1441 ariadna 163
            'layout' => $layout,
1 efrain 164
        ]);
165
 
166
        /** @var core_question_generator $questiongenerator */
167
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
168
        $cat = $questiongenerator->create_question_category();
169
 
170
        $page = 1;
171
        $questions = [];
172
        foreach (explode(',', $layout) as $slot) {
173
            if ($slot == 0) {
174
                $page += 1;
175
                continue;
176
            }
177
 
178
            $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
179
            $questions[$slot] = $question;
180
            quiz_add_quiz_question($question->id, $quiz1, $page);
181
            quiz_add_quiz_question($question->id, $quiz2, $page);
182
        }
183
 
184
        return [$quiz1, $quiz2, $questions];
185
    }
186
 
187
    /**
188
     * Submit quiz answers
189
     *
190
     * @param object $quiz
191
     * @param array $answers
192
     */
193
    private function submit_quiz(object $quiz, array $answers): void {
194
        // Create user.
195
        $user = $this->getDataGenerator()->create_user();
196
        // Create attempt.
197
        $quizobj = quiz_settings::create($quiz->id, $user->id);
198
        $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
199
        $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
200
        $timenow = time();
201
        $attempt = quiz_create_attempt($quizobj, 1, null, $timenow, false, $user->id);
202
        quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
203
        quiz_attempt_save_started($quizobj, $quba, $attempt);
204
        // Submit attempt.
205
        $attemptobj = quiz_attempt::create($attempt->id);
206
        $attemptobj->process_submitted_actions($timenow, false, $answers);
1441 ariadna 207
        $attemptobj->process_submit($timenow, false);
208
        $attemptobj->process_grade_submission($timenow);
1 efrain 209
    }
210
 
211
    /**
212
     * Generate attempt answers.
213
     *
214
     * @param array $correctanswerflags array of 1 or 0
215
     * 1 : generate correct answer
216
     * 0 : generate wrong answer
217
     *
218
     * @return array
219
     */
1441 ariadna 220
    private static function generate_attempt_answers(array $correctanswerflags): array {
1 efrain 221
        $attempt = [];
222
        for ($i = 1; $i <= 4; $i++) {
223
            if (isset($correctanswerflags) && $correctanswerflags[$i - 1] == 1) {
224
                // Correct answer.
225
                $attempt[$i] = ['answer' => 'frog'];
226
            } else {
227
                $attempt[$i] = ['answer' => 'false'];
228
            }
229
        }
230
        return $attempt;
231
    }
232
 
233
    /**
234
     * Generate quizzes and submit answers.
235
     *
236
     * @param array $quiz1attempts quiz 1 attempts
237
     * @param array $quiz2attempts quiz 2 attempts
238
     *
239
     * @return array
240
     */
241
    private function prepare_and_submit_quizzes(array $quiz1attempts, array $quiz2attempts): array {
1441 ariadna 242
        [$quiz1, $quiz2, $questions] = $this->prepare_quizzes();
1 efrain 243
        // Submit attempts of quiz1.
244
        foreach ($quiz1attempts as $attempt) {
245
            $this->submit_quiz($quiz1, $attempt);
246
        }
247
        // Submit attempts of quiz2.
248
        foreach ($quiz2attempts as $attempt) {
249
            $this->submit_quiz($quiz2, $attempt);
250
        }
251
 
252
        // Calculate the statistics.
253
        $this->expectOutputRegex('~.*Calculations completed.*~');
254
        statistics_helper::run_pending_recalculation_tasks();
255
 
256
        return [$quiz1, $quiz2, $questions];
257
    }
258
 
259
    /**
260
     * To use private helper::extract_item_value function.
261
     *
262
     * @param all_calculated_for_qubaid_condition $statistics the batch of statistics.
263
     * @param int $questionid a question id.
264
     * @param string $item one of the field names in all_calculated_for_qubaid_condition, e.g. 'facility'.
265
     * @return float|null the required value.
266
     */
1441 ariadna 267
    private function extract_item_value(
268
        all_calculated_for_qubaid_condition $statistics,
269
        int $questionid,
270
        string $item
271
    ): ?float {
1 efrain 272
        $rcm = new ReflectionMethod(statistics_bulk_loader::class, 'extract_item_value');
273
        return $rcm->invoke(null, $statistics, $questionid, $item);
274
    }
275
 
276
    /**
277
     * To use private helper::load_statistics_for_place function (with mod_quiz component).
278
     *
279
     * @param context $context the context to load the statistics for.
280
     * @return all_calculated_for_qubaid_condition|null question statistics.
281
     */
282
    private function load_quiz_statistics_for_place(context $context): ?all_calculated_for_qubaid_condition {
283
        $rcm = new ReflectionMethod(statistics_bulk_loader::class, 'load_statistics_for_place');
284
        return $rcm->invoke(null, 'mod_quiz', $context);
285
    }
286
 
287
    /**
288
     * Data provider for {@see test_load_question_facility()}.
289
     *
290
     * @return Generator
291
     */
1441 ariadna 292
    public static function load_question_facility_provider(): Generator {
1 efrain 293
        yield 'Facility case 1' => [
1441 ariadna 294
            // Quiz 1 attempts.
295
            [
296
                self::generate_attempt_answers([1, 0, 0, 0]),
1 efrain 297
            ],
1441 ariadna 298
            // Expected quiz 1 facilities.
299
            [1.0, 0.0, 0.0, 0.0],
300
            // Quiz 2 attempts.
301
            [
302
                self::generate_attempt_answers([1, 0, 0, 0]),
303
                self::generate_attempt_answers([1, 1, 0, 0]),
1 efrain 304
            ],
1441 ariadna 305
            // Expected quiz 2 facilities.
306
            [1.0, 0.5, 0.0, 0.0],
307
            // Expected average facilities.
308
            [1.0, 0.25, 0.0, 0.0],
1 efrain 309
        ];
310
        yield 'Facility case 2' => [
1441 ariadna 311
            // Quiz 1 attempts.
312
            [
313
                self::generate_attempt_answers([1, 0, 0, 0]),
314
                self::generate_attempt_answers([1, 1, 0, 0]),
315
                self::generate_attempt_answers([1, 1, 1, 0]),
1 efrain 316
            ],
1441 ariadna 317
            // Expected quiz 1 facilities.
318
            [1.0, 0.6667, 0.3333, 0.0],
319
            // Quiz 2 attempts.
320
            [
321
                self::generate_attempt_answers([1, 0, 0, 0]),
322
                self::generate_attempt_answers([1, 1, 0, 0]),
323
                self::generate_attempt_answers([1, 1, 1, 0]),
324
                self::generate_attempt_answers([1, 1, 1, 1]),
1 efrain 325
            ],
1441 ariadna 326
            // Expected quiz 2 facilities.
327
            [1.0, 0.75, 0.5, 0.25],
328
            // Expected average facilities.
329
            [1.0, 0.7083, 0.4167, 0.1250],
1 efrain 330
        ];
331
    }
332
 
333
    /**
334
     * Test question facility
335
     *
336
     * @dataProvider load_question_facility_provider
337
     *
338
     * @param array $quiz1attempts quiz 1 attempts
339
     * @param array $expectedquiz1facilities expected quiz 1 facilities
340
     * @param array $quiz2attempts quiz 2 attempts
341
     * @param array $expectedquiz2facilities  expected quiz 2 facilities
342
     * @param array $expectedaveragefacilities expected average facilities
343
     */
344
    public function test_load_question_facility(
345
        array $quiz1attempts,
346
        array $expectedquiz1facilities,
347
        array $quiz2attempts,
348
        array $expectedquiz2facilities,
349
        array $expectedaveragefacilities
350
    ): void {
351
        $this->resetAfterTest();
352
 
1441 ariadna 353
        [$quiz1, $quiz2, $questions] = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
1 efrain 354
 
355
        // Quiz 1 facilities.
356
        $stats = $this->load_quiz_statistics_for_place(context_module::instance($quiz1->cmid));
357
        $quiz1facility1 = $this->extract_item_value($stats, $questions[1]->id, 'facility');
358
        $quiz1facility2 = $this->extract_item_value($stats, $questions[2]->id, 'facility');
359
        $quiz1facility3 = $this->extract_item_value($stats, $questions[3]->id, 'facility');
360
        $quiz1facility4 = $this->extract_item_value($stats, $questions[4]->id, 'facility');
361
 
362
        $this->assertEqualsWithDelta($expectedquiz1facilities[0], $quiz1facility1, self::DELTA);
363
        $this->assertEqualsWithDelta($expectedquiz1facilities[1], $quiz1facility2, self::DELTA);
364
        $this->assertEqualsWithDelta($expectedquiz1facilities[2], $quiz1facility3, self::DELTA);
365
        $this->assertEqualsWithDelta($expectedquiz1facilities[3], $quiz1facility4, self::DELTA);
366
 
367
        // Quiz 2 facilities.
368
        $stats = $this->load_quiz_statistics_for_place(context_module::instance($quiz2->cmid));
369
        $quiz2facility1 = $this->extract_item_value($stats, $questions[1]->id, 'facility');
370
        $quiz2facility2 = $this->extract_item_value($stats, $questions[2]->id, 'facility');
371
        $quiz2facility3 = $this->extract_item_value($stats, $questions[3]->id, 'facility');
372
        $quiz2facility4 = $this->extract_item_value($stats, $questions[4]->id, 'facility');
373
 
374
        $this->assertEqualsWithDelta($expectedquiz2facilities[0], $quiz2facility1, self::DELTA);
375
        $this->assertEqualsWithDelta($expectedquiz2facilities[1], $quiz2facility2, self::DELTA);
376
        $this->assertEqualsWithDelta($expectedquiz2facilities[2], $quiz2facility3, self::DELTA);
377
        $this->assertEqualsWithDelta($expectedquiz2facilities[3], $quiz2facility4, self::DELTA);
378
 
379
        // Average question facilities.
380
        $stats = statistics_bulk_loader::load_aggregate_statistics(
381
            [$questions[1]->id, $questions[2]->id, $questions[3]->id, $questions[4]->id],
382
            ['facility']
383
        );
384
 
1441 ariadna 385
        $this->assertEqualsWithDelta(
386
            $expectedaveragefacilities[0],
387
            $stats[$questions[1]->id]['facility'],
388
            self::DELTA
389
        );
390
        $this->assertEqualsWithDelta(
391
            $expectedaveragefacilities[1],
392
            $stats[$questions[2]->id]['facility'],
393
            self::DELTA
394
        );
395
        $this->assertEqualsWithDelta(
396
            $expectedaveragefacilities[2],
397
            $stats[$questions[3]->id]['facility'],
398
            self::DELTA
399
        );
400
        $this->assertEqualsWithDelta(
401
            $expectedaveragefacilities[3],
402
            $stats[$questions[4]->id]['facility'],
403
            self::DELTA
404
        );
1 efrain 405
    }
406
 
407
    /**
408
     * Data provider for {@see test_load_question_discriminative_efficiency()}.
409
     * @return Generator
410
     */
1441 ariadna 411
    public static function load_question_discriminative_efficiency_provider(): Generator {
1 efrain 412
        yield 'Discriminative efficiency' => [
1441 ariadna 413
            // Quiz 1 attempts.
414
            [
415
                self::generate_attempt_answers([1, 0, 0, 0]),
416
                self::generate_attempt_answers([1, 1, 0, 0]),
417
                self::generate_attempt_answers([1, 0, 1, 0]),
418
                self::generate_attempt_answers([1, 1, 1, 1]),
1 efrain 419
            ],
1441 ariadna 420
            // Expected quiz 1 discriminative efficiency.
421
            [null, 33.33, 33.33, 100.00],
422
            // Quiz 2 attempts.
423
            [
424
                self::generate_attempt_answers([1, 1, 1, 1]),
425
                self::generate_attempt_answers([0, 0, 0, 0]),
426
                self::generate_attempt_answers([1, 0, 0, 1]),
427
                self::generate_attempt_answers([0, 1, 1, 0]),
1 efrain 428
            ],
1441 ariadna 429
            // Expected quiz 2 discriminative efficiency.
430
            [50.00, 50.00, 50.00, 50.00],
431
            // Expected average discriminative efficiency.
432
            [50.00, 41.67, 41.67, 75.00],
1 efrain 433
        ];
434
    }
435
 
436
    /**
437
     * Test discriminative efficiency
438
     *
439
     * @dataProvider load_question_discriminative_efficiency_provider
440
     *
441
     * @param array $quiz1attempts quiz 1 attempts
442
     * @param array $expectedquiz1discriminativeefficiency expected quiz 1 discriminative efficiency
443
     * @param array $quiz2attempts quiz 2 attempts
444
     * @param array $expectedquiz2discriminativeefficiency expected quiz 2 discriminative efficiency
445
     * @param array $expectedaveragediscriminativeefficiency expected average discriminative efficiency
446
     */
447
    public function test_load_question_discriminative_efficiency(
448
        array $quiz1attempts,
449
        array $expectedquiz1discriminativeefficiency,
450
        array $quiz2attempts,
451
        array $expectedquiz2discriminativeefficiency,
452
        array $expectedaveragediscriminativeefficiency
453
    ): void {
454
        $this->resetAfterTest();
455
 
1441 ariadna 456
        [$quiz1, $quiz2, $questions] = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
1 efrain 457
 
458
        // Quiz 1 discriminative efficiency.
459
        $stats = $this->load_quiz_statistics_for_place(context_module::instance($quiz1->cmid));
460
        $discriminativeefficiency1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminativeefficiency');
461
        $discriminativeefficiency2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminativeefficiency');
462
        $discriminativeefficiency3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminativeefficiency');
463
        $discriminativeefficiency4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminativeefficiency');
464
 
1441 ariadna 465
        $this->assertEqualsWithDelta(
466
            $expectedquiz1discriminativeefficiency[0],
467
            $discriminativeefficiency1,
468
            self::PERCENT_DELTA
469
        );
470
        $this->assertEqualsWithDelta(
471
            $expectedquiz1discriminativeefficiency[1],
472
            $discriminativeefficiency2,
473
            self::PERCENT_DELTA
474
        );
475
        $this->assertEqualsWithDelta(
476
            $expectedquiz1discriminativeefficiency[2],
477
            $discriminativeefficiency3,
478
            self::PERCENT_DELTA
479
        );
480
        $this->assertEqualsWithDelta(
481
            $expectedquiz1discriminativeefficiency[3],
482
            $discriminativeefficiency4,
483
            self::PERCENT_DELTA
484
        );
1 efrain 485
 
486
        // Quiz 2 discriminative efficiency.
487
        $stats = $this->load_quiz_statistics_for_place(context_module::instance($quiz2->cmid));
488
        $discriminativeefficiency1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminativeefficiency');
489
        $discriminativeefficiency2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminativeefficiency');
490
        $discriminativeefficiency3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminativeefficiency');
491
        $discriminativeefficiency4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminativeefficiency');
492
 
1441 ariadna 493
        $this->assertEqualsWithDelta(
494
            $expectedquiz2discriminativeefficiency[0],
495
            $discriminativeefficiency1,
496
            self::PERCENT_DELTA
497
        );
498
        $this->assertEqualsWithDelta(
499
            $expectedquiz2discriminativeefficiency[1],
500
            $discriminativeefficiency2,
501
            self::PERCENT_DELTA
502
        );
503
        $this->assertEqualsWithDelta(
504
            $expectedquiz2discriminativeefficiency[2],
505
            $discriminativeefficiency3,
506
            self::PERCENT_DELTA
507
        );
508
        $this->assertEqualsWithDelta(
509
            $expectedquiz2discriminativeefficiency[3],
510
            $discriminativeefficiency4,
511
            self::PERCENT_DELTA
512
        );
1 efrain 513
 
514
        // Average question discriminative efficiency.
515
        $stats = statistics_bulk_loader::load_aggregate_statistics(
516
            [$questions[1]->id, $questions[2]->id, $questions[3]->id, $questions[4]->id],
517
            ['discriminativeefficiency']
518
        );
519
 
1441 ariadna 520
        $this->assertEqualsWithDelta(
521
            $expectedaveragediscriminativeefficiency[0],
522
            $stats[$questions[1]->id]['discriminativeefficiency'],
523
            self::PERCENT_DELTA
524
        );
525
        $this->assertEqualsWithDelta(
526
            $expectedaveragediscriminativeefficiency[1],
527
            $stats[$questions[2]->id]['discriminativeefficiency'],
528
            self::PERCENT_DELTA
529
        );
530
        $this->assertEqualsWithDelta(
531
            $expectedaveragediscriminativeefficiency[2],
532
            $stats[$questions[3]->id]['discriminativeefficiency'],
533
            self::PERCENT_DELTA
534
        );
535
        $this->assertEqualsWithDelta(
536
            $expectedaveragediscriminativeefficiency[3],
537
            $stats[$questions[4]->id]['discriminativeefficiency'],
538
            self::PERCENT_DELTA
539
        );
1 efrain 540
    }
541
 
542
    /**
543
     * Data provider for {@see test_load_question_discrimination_index()}.
544
     * @return Generator
545
     */
1441 ariadna 546
    public static function load_question_discrimination_index_provider(): Generator {
1 efrain 547
        yield 'Discrimination Index' => [
1441 ariadna 548
            // Quiz 1 attempts.
549
            [
550
                self::generate_attempt_answers([1, 0, 0, 0]),
551
                self::generate_attempt_answers([1, 1, 0, 0]),
552
                self::generate_attempt_answers([1, 0, 1, 0]),
553
                self::generate_attempt_answers([1, 1, 1, 1]),
1 efrain 554
            ],
1441 ariadna 555
            // Expected quiz 1 Discrimination Index.
556
            [null, 30.15, 30.15, 81.65],
557
            // Quiz 2 attempts.
558
            [
559
                self::generate_attempt_answers([1, 1, 1, 1]),
560
                self::generate_attempt_answers([0, 0, 0, 0]),
561
                self::generate_attempt_answers([1, 0, 0, 1]),
562
                self::generate_attempt_answers([0, 1, 1, 0]),
1 efrain 563
            ],
1441 ariadna 564
            // Expected quiz 2 discrimination Index.
565
            [44.72, 44.72, 44.72, 44.72],
566
            // Expected average discrimination Index.
567
            [44.72, 37.44, 37.44, 63.19],
1 efrain 568
        ];
569
    }
570
 
571
    /**
572
     * Test discrimination index
573
     *
574
     * @dataProvider load_question_discrimination_index_provider
575
     *
576
     * @param array $quiz1attempts quiz 1 attempts
577
     * @param array $expectedquiz1discriminationindex expected quiz 1 discrimination index
578
     * @param array $quiz2attempts quiz 2 attempts
579
     * @param array $expectedquiz2discriminationindex expected quiz 2 discrimination index
580
     * @param array $expectedaveragediscriminationindex expected average discrimination index
581
     */
582
    public function test_load_question_discrimination_index(
583
        array $quiz1attempts,
584
        array $expectedquiz1discriminationindex,
585
        array $quiz2attempts,
586
        array $expectedquiz2discriminationindex,
587
        array $expectedaveragediscriminationindex
588
    ): void {
589
        $this->resetAfterTest();
590
 
1441 ariadna 591
        [$quiz1, $quiz2, $questions] = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
1 efrain 592
 
593
        // Quiz 1 discrimination index.
594
        $stats = $this->load_quiz_statistics_for_place(context_module::instance($quiz1->cmid));
595
        $discriminationindex1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminationindex');
596
        $discriminationindex2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminationindex');
597
        $discriminationindex3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminationindex');
598
        $discriminationindex4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminationindex');
599
 
1441 ariadna 600
        $this->assertEqualsWithDelta(
601
            $expectedquiz1discriminationindex[0],
602
            $discriminationindex1,
603
            self::PERCENT_DELTA
604
        );
605
        $this->assertEqualsWithDelta(
606
            $expectedquiz1discriminationindex[1],
607
            $discriminationindex2,
608
            self::PERCENT_DELTA
609
        );
610
        $this->assertEqualsWithDelta(
611
            $expectedquiz1discriminationindex[2],
612
            $discriminationindex3,
613
            self::PERCENT_DELTA
614
        );
615
        $this->assertEqualsWithDelta(
616
            $expectedquiz1discriminationindex[3],
617
            $discriminationindex4,
618
            self::PERCENT_DELTA
619
        );
1 efrain 620
 
621
        // Quiz 2 discrimination index.
622
        $stats = $this->load_quiz_statistics_for_place(context_module::instance($quiz2->cmid));
623
        $discriminationindex1 = $this->extract_item_value($stats, $questions[1]->id, 'discriminationindex');
624
        $discriminationindex2 = $this->extract_item_value($stats, $questions[2]->id, 'discriminationindex');
625
        $discriminationindex3 = $this->extract_item_value($stats, $questions[3]->id, 'discriminationindex');
626
        $discriminationindex4 = $this->extract_item_value($stats, $questions[4]->id, 'discriminationindex');
627
 
1441 ariadna 628
        $this->assertEqualsWithDelta(
629
            $expectedquiz2discriminationindex[0],
630
            $discriminationindex1,
631
            self::PERCENT_DELTA
632
        );
633
        $this->assertEqualsWithDelta(
634
            $expectedquiz2discriminationindex[1],
635
            $discriminationindex2,
636
            self::PERCENT_DELTA
637
        );
638
        $this->assertEqualsWithDelta(
639
            $expectedquiz2discriminationindex[2],
640
            $discriminationindex3,
641
            self::PERCENT_DELTA
642
        );
643
        $this->assertEqualsWithDelta(
644
            $expectedquiz2discriminationindex[3],
645
            $discriminationindex4,
646
            self::PERCENT_DELTA
647
        );
1 efrain 648
 
649
        // Average question discrimination index.
650
        $stats = statistics_bulk_loader::load_aggregate_statistics(
651
            [$questions[1]->id, $questions[2]->id, $questions[3]->id, $questions[4]->id],
652
            ['discriminationindex']
653
        );
654
 
1441 ariadna 655
        $this->assertEqualsWithDelta(
656
            $expectedaveragediscriminationindex[0],
657
            $stats[$questions[1]->id]['discriminationindex'],
658
            self::PERCENT_DELTA
659
        );
660
        $this->assertEqualsWithDelta(
661
            $expectedaveragediscriminationindex[1],
662
            $stats[$questions[2]->id]['discriminationindex'],
663
            self::PERCENT_DELTA
664
        );
665
        $this->assertEqualsWithDelta(
666
            $expectedaveragediscriminationindex[2],
667
            $stats[$questions[3]->id]['discriminationindex'],
668
            self::PERCENT_DELTA
669
        );
670
        $this->assertEqualsWithDelta(
671
            $expectedaveragediscriminationindex[3],
672
            $stats[$questions[4]->id]['discriminationindex'],
673
            self::PERCENT_DELTA
674
        );
1 efrain 675
    }
676
 
677
    /**
678
     * Test with question statistics disabled
679
     */
680
    public function test_statistics_disabled(): void {
681
        $this->resetAfterTest();
682
 
683
        // Prepare some quizzes and attempts. Exactly what is not important to this test.
1441 ariadna 684
        $quiz1attempts = [self::generate_attempt_answers([1, 0, 0, 0])];
685
        $quiz2attempts = [self::generate_attempt_answers([1, 1, 1, 1])];
1 efrain 686
        [, , $questions] = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
687
 
688
        // Prepare some useful arrays.
689
        $expectedstats = [
690
            $questions[1]->id => [],
691
            $questions[2]->id => [],
692
            $questions[3]->id => [],
693
            $questions[4]->id => [],
694
        ];
695
        $questionids = array_keys($expectedstats);
696
 
697
        // Ask to load no statistics at all.
698
        $stats = statistics_bulk_loader::load_aggregate_statistics($questionids, []);
699
 
700
        // Verify we got the right thing.
701
        $this->assertEquals($expectedstats, $stats);
702
    }
703
}