Proyectos de Subversion Moodle

Rev

| 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
/**
18
 * Quiz activity-related unit tests for Completion Progress block.
19
 *
20
 * @package    block_completion_progress
21
 * @copyright  2020 Jonathon Fowler <fowlerj@usq.edu.au>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace block_completion_progress;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
global $CFG;
30
require_once($CFG->dirroot.'/mod/quiz/lib.php');
31
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
32
 
33
use block_completion_progress\completion_progress;
34
use block_completion_progress\defaults;
35
 
36
if (!class_exists('mod_quiz\quiz_settings')) {
37
    // Moodle 4.1 or earlier.
38
    class_alias('quiz', 'mod_quiz\quiz_settings', false);
39
    class_alias('quiz_attempt', 'mod_quiz\quiz_attempt', false);
40
}
41
 
42
/**
43
 * Quiz activity-related unit tests for Completion Progress block.
44
 *
45
 * @package    block_completion_progress
46
 * @copyright  2020 Jonathon Fowler <fowlerj@usq.edu.au>
47
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48
 */
49
class quiz_completion_test extends \block_completion_progress\tests\completion_testcase {
50
    /**
51
     * A data provider supplying each of the possible quiz grade methods.
52
     * @return array
53
     */
54
    public function grademethod_provider(): array {
55
        return [
56
            'QUIZ_GRADEHIGHEST' => [ QUIZ_GRADEHIGHEST, ],
57
            'QUIZ_GRADEAVERAGE' => [ QUIZ_GRADEAVERAGE, ],
58
            'QUIZ_ATTEMPTFIRST' => [ QUIZ_ATTEMPTFIRST, ],
59
            'QUIZ_ATTEMPTLAST' => [ QUIZ_ATTEMPTLAST, ],
60
        ];
61
    }
62
 
63
    /**
64
     * Test completion determination in a Quiz activity with
65
     * pass/fail enabled.
66
     *
67
     * @param integer $grademethod
68
     *
69
     * @covers \block_completion_progress\completion_progress
70
     * @dataProvider grademethod_provider
71
     */
72
    public function test_quiz_passfail($grademethod) {
73
        $generator = $this->getDataGenerator();
74
 
75
        $instance = $generator->create_module('quiz', [
76
            'course' => $this->course->id,
77
            'grade' => 100,
78
            'sumgrades' => 100,
79
            'layout' => '1,0',  // One question.
80
            'attempts' => -1,
81
            'grademethod' => $grademethod,
82
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
83
            'completionusegrade' => 1,      // Student must receive a grade to complete.
84
            'completionexpected' => time() - DAYSECS,
85
        ]);
86
        $cm = get_coursemodule_from_id('quiz', $instance->cmid);
87
 
88
        // Set the passing grade.
89
        $item = \grade_item::fetch(['courseid' => $this->course->id, 'itemtype' => 'mod',
90
            'itemmodule' => 'quiz', 'iteminstance' => $instance->id, 'outcomeid' => null]);
91
        $item->gradepass = 50;
92
        $item->update();
93
 
94
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
95
        $cat = $questiongenerator->create_question_category();
96
        $question = $questiongenerator->create_question('essay', null, [
97
            'category' => $cat->id,
98
            'name' => 'Pass-fail essay question',
99
            'defaultmark' => 100,
100
            'responserequired' => 1,
101
            'attachmentsrequired' => 0,
102
            'responseformat' => 'editor',
103
        ]);
104
        quiz_add_quiz_question($question->id, $instance, 1);
105
 
106
        $teacher = $generator->create_and_enrol($this->course, 'editingteacher');
107
 
108
        // Student 1 submits to the activity and gets graded correctly.
109
        $student1 = $generator->create_and_enrol($this->course, 'student');
110
        $this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
111
        $attempt = $this->submit_for_student($student1, $instance, 1);
112
        $this->assert_progress_completion($student1, $cm, 'submitted');
113
        $this->mark_student($attempt, $teacher, 75);      // Pass.
114
        $this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE_PASS);
115
 
116
        // Student 2 submits to the activity and gets graded incorrectly.
117
        $student2 = $generator->create_and_enrol($this->course, 'student');
118
        $this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
119
        $attempt = $this->submit_for_student($student2, $instance, 1);
120
        $this->assert_progress_completion($student2, $cm, 'submitted');
121
        $this->mark_student($attempt, $teacher, 25);      // Fail.
122
        $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE_FAIL);
123
 
124
        // Student 2 then submits again.
125
        $attempt = $this->submit_for_student($student2, $instance, 2);
126
        switch ($grademethod) {
127
            case QUIZ_GRADEHIGHEST:
128
                $this->assert_progress_completion($student2, $cm, 'submitted');
129
                break;
130
            case QUIZ_GRADEAVERAGE:
131
                $this->assert_progress_completion($student2, $cm, 'submitted');
132
                break;
133
            case QUIZ_ATTEMPTFIRST:
134
                $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE_FAIL);
135
                break;
136
            case QUIZ_ATTEMPTLAST:
137
                $this->assert_progress_completion($student2, $cm, 'submitted');
138
                break;
139
        }
140
    }
141
 
142
    /**
143
     * Test completion determination in an Assignment activity with basic completion.
144
     *
145
     * @param integer $grademethod
146
     *
147
     * @covers \block_completion_progress\completion_progress
148
     * @dataProvider grademethod_provider
149
     */
150
    public function test_quiz_basic($grademethod) {
151
        $generator = $this->getDataGenerator();
152
 
153
        $instance = $generator->create_module('quiz', [
154
            'course' => $this->course->id,
155
            'grade' => 100,
156
            'sumgrades' => 100,
157
            'layout' => '1,0',  // One question.
158
            'attempts' => -1,
159
            'grademethod' => $grademethod,
160
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
161
            'completionusegrade' => 1,      // Student must receive a grade to complete.
162
            'completionexpected' => time() - DAYSECS,
163
        ]);
164
        $cm = get_coursemodule_from_id('quiz', $instance->cmid);
165
 
166
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
167
        $cat = $questiongenerator->create_question_category();
168
        $question = $questiongenerator->create_question('essay', null, [
169
            'category' => $cat->id,
170
            'name' => 'Basic essay question',
171
            'defaultmark' => 100,
172
            'responserequired' => 1,
173
            'attachmentsrequired' => 0,
174
            'responseformat' => 'editor',
175
        ]);
176
        quiz_add_quiz_question($question->id, $instance, 1);
177
 
178
        $teacher = $generator->create_and_enrol($this->course, 'editingteacher');
179
 
180
        // Student 1 submits to the activity and gets graded correct.
181
        $student1 = $generator->create_and_enrol($this->course, 'student');
182
        $this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
183
        $attempt = $this->submit_for_student($student1, $instance, 1);
184
        $this->assert_progress_completion($student1, $cm, 'submitted');
185
        $this->mark_student($attempt, $teacher, 75);      // Pass.
186
        $this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE);
187
 
188
        // Student 2 submits to the activity and gets graded incorrect.
189
        $student2 = $generator->create_and_enrol($this->course, 'student');
190
        $this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
191
        $attempt = $this->submit_for_student($student2, $instance, 1);
192
        $this->assert_progress_completion($student2, $cm, 'submitted');
193
        $this->mark_student($attempt, $teacher, 25);      // Fail.
194
        $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
195
 
196
        // Student 2 then submits again.
197
        $attempt = $this->submit_for_student($student2, $instance, 2);
198
        switch ($grademethod) {
199
            case QUIZ_GRADEHIGHEST:
200
                $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
201
                break;
202
            case QUIZ_GRADEAVERAGE:
203
                $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
204
                break;
205
            case QUIZ_ATTEMPTFIRST:
206
                $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
207
                break;
208
            case QUIZ_ATTEMPTLAST:
209
                $this->assert_progress_completion($student2, $cm, 'submitted');
210
                break;
211
        }
212
    }
213
 
214
    /**
215
     * Submit a quiz attempt.
216
     * @param object $student
217
     * @param object $quiz
218
     * @param integer $attemptnumber
219
     * @return quiz_attempt
220
     */
221
    private function submit_for_student($student, $quiz, $attemptnumber) {
222
        $quizobj = \mod_quiz\quiz_settings::create($quiz->id, $student->id);
223
        $attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, null, false, [], [], $student->id);
224
        $attemptobj = \mod_quiz\quiz_attempt::create($attempt->id);
225
 
226
        // Save a response for the essay in the first slot.
227
        $qa = $attemptobj->get_question_attempt(1);
228
        $qa->process_action([
229
            'answer'         => 'Response',
230
            'answerformat'   => FORMAT_HTML,
231
        ], null, $student->id);
232
 
233
        // Finish the attempt.
234
        $attemptobj->process_attempt(time(), true, false, 1);
235
 
236
        return $attemptobj;
237
    }
238
 
239
    /**
240
     * Mark the first question of an attempt.
241
     * @param quiz_attempt $attemptobj
242
     * @param object $teacher
243
     * @param integer $mark
244
     */
245
    private function mark_student($attemptobj, $teacher, $mark) {
246
        global $DB;
247
 
248
        $this->setUser($teacher);
249
 
250
        $quba = $attemptobj->get_question_usage();
251
        $quba->get_question_attempt(1)->manual_grade(
252
                'Comment', $mark, FORMAT_HTML);
253
        \question_engine::save_questions_usage_by_activity($quba);
254
 
255
        $update = new \stdClass();
256
        $update->id = $attemptobj->get_attemptid();
257
        $update->timemodified = time();
258
        $update->sumgrades = $quba->get_total_mark();
259
        $DB->update_record('quiz_attempts', $update);
260
 
261
        if (class_exists('mod_quiz\grade_calculator')) {
262
            $attemptobj->get_quizobj()->get_grade_calculator()->recompute_final_grade($attemptobj->get_userid());
263
        } else {
264
            quiz_save_best_grade($attemptobj->get_quiz(), $attemptobj->get_userid());
265
        }
266
 
267
        $this->setUser(null);
268
    }
269
}