MDL-64693 analytics: new course_competencies target
[moodle.git] / lib / tests / targets_test.php
CommitLineData
03fce0a7
VDF
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 core targets.
19 *
20 * @package core
21 * @category analytics
22 * @copyright 2019 Victor Deniz <victor@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27global $CFG;
28
29require_once($CFG->dirroot . '/completion/criteria/completion_criteria.php');
30require_once($CFG->dirroot . '/completion/criteria/completion_criteria_activity.php');
31
32/**
33 * Unit tests for core targets.
34 *
35 * @package core
36 * @category analytics
37 * @copyright 2019 Victor Deniz <victor@moodle.com>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 */
40class core_analytics_targets_testcase extends advanced_testcase {
41
42 /**
43 * Provides course params for the {@link self::test_core_target_course_completion_analysable()} method.
44 *
45 * @return array
46 */
47 public function analysable_provider() {
48
49 $now = new DateTime("now", core_date::get_server_timezone_object());
50 $year = $now->format('Y');
51 $month = $now->format('m');
52
53 return [
54 'coursenotyetstarted' => [
55 'params' => [
56 'enablecompletion' => 1,
57 'startdate' => mktime(0, 0, 0, 10, 24, $year + 1)
58 ],
59 'isvalid' => get_string('coursenotyetstarted')
60 ],
61 'coursenostudents' => [
62 'params' => [
63 'enablecompletion' => 1,
64 'startdate' => mktime(0, 0, 0, 10, 24, $year - 2),
65 'enddate' => mktime(0, 0, 0, 10, 24, $year - 1)
66 ],
67 'isvalid' => get_string('nocoursestudents')
68 ],
69 'coursenosections' => [
70 'params' => [
71 'enablecompletion' => 1,
72 'format' => 'social',
73 'students' => true
74 ],
75 'isvalid' => get_string('nocoursesections')
76 ],
77 'coursenoendtime' => [
78 'params' => [
79 'enablecompletion' => 1,
80 'format' => 'topics',
81 'enddate' => 0,
82 'students' => true
83 ],
84 'isvalid' => get_string('nocourseendtime')
85 ],
86 'courseendbeforestart' => [
87 'params' => [
88 'enablecompletion' => 1,
89 'enddate' => mktime(0, 0, 0, 10, 23, $year - 2),
90 'students' => true
91 ],
92 'isvalid' => get_string('errorendbeforestart', 'analytics')
93 ],
94 'coursetoolong' => [
95 'params' => [
96 'enablecompletion' => 1,
97 'startdate' => mktime(0, 0, 0, 10, 24, $year - 2),
98 'enddate' => mktime(0, 0, 0, 10, 23, $year),
99 'students' => true
100 ],
101 'isvalid' => get_string('coursetoolong', 'analytics')
102 ],
103 'coursealreadyfinished' => [
104 'params' => [
105 'enablecompletion' => 1,
106 'startdate' => mktime(0, 0, 0, 10, 24, $year - 2),
107 'enddate' => mktime(0, 0, 0, 10, 23, $year - 1),
108 'students' => true
109 ],
110 'isvalid' => get_string('coursealreadyfinished'),
111 'fortraining' => false
112 ],
113 'coursenotyetfinished' => [
114 'params' => [
115 'enablecompletion' => 1,
116 'startdate' => mktime(0, 0, 0, $month - 1, 24, $year),
117 'enddate' => mktime(0, 0, 0, $month + 2, 23, $year),
118 'students' => true
119 ],
120 'isvalid' => get_string('coursenotyetfinished')
121 ],
122 'coursenocompletion' => [
123 'params' => [
124 'enablecompletion' => 0,
125 'startdate' => mktime(0, 0, 0, $month - 2, 24, $year),
126 'enddate' => mktime(0, 0, 0, $month - 1, 23, $year),
127 'students' => true
128 ],
129 'isvalid' => get_string('completionnotenabledforcourse', 'completion')
130 ],
131 ];
132 }
133
134 /**
135 * Provides enrolment params for the {@link self::test_core_target_course_completion_samples()} method.
136 *
137 * @return array
138 */
139 public function sample_provider() {
140 $now = time();
141 return [
142 'enrolmentendbeforecourse' => [
143 'coursestart' => $now,
144 'courseend' => $now + (WEEKSECS * 8),
145 'timestart' => $now,
146 'timeend' => $now - DAYSECS,
147 'isvalid' => false
148 ],
149 'enrolmenttoolong' => [
150 'coursestart' => $now,
151 'courseend' => $now + (WEEKSECS * 8),
152 'timestart' => $now - (YEARSECS + (WEEKSECS * 8)),
153 'timeend' => $now + (WEEKSECS * 8),
154 'isvalid' => false
155 ],
156 'enrolmentstartaftercourse' => [
157 'coursestart' => $now,
158 'courseend' => $now + (WEEKSECS * 8),
159 'timestart' => $now + (WEEKSECS * 9),
160 'timeend' => $now + (WEEKSECS * 10),
161 'isvalid' => false
162 ],
163 ];
164 }
165
166 /**
525cee73 167 * Test the conditions of a valid analysable, both common and specific to this target (course_completion).
03fce0a7
VDF
168 *
169 * @dataProvider analysable_provider
170 * @param mixed $courseparams Course data
171 * @param true|string $isvalid True when analysable is valid, string when it is not
172 * @param boolean $fortraining True if the course is for training the model
173 */
174 public function test_core_target_course_completion_analysable($courseparams, $isvalid, $fortraining = true) {
175 global $DB;
176
177 $this->resetAfterTest(true);
178
179 try {
180 $course = $this->getDataGenerator()->create_course($courseparams);
181 } catch (moodle_exception $e) {
182 $course = $this->getDataGenerator()->create_course();
183 $courserecord = $courseparams;
184 $courserecord['id'] = $course->id;
185 unset($courserecord['students']);
186
187 $DB->update_record_raw('course', $courserecord);
188 $course = get_course($course->id);
189 }
190 $user = $this->getDataGenerator()->create_user();
191
192 if (!empty($courseparams['enablecompletion'])) {
193 $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'completion' => 1]);
194 $cm = get_coursemodule_from_id('assign', $assign->cmid);
195
196 $criteriadata = (object) [
197 'id' => $course->id,
198 'criteria_activity' => [
199 $cm->id => 1
200 ]
201 ];
202 $criterion = new completion_criteria_activity();
203 $criterion->update_config($criteriadata);
204 }
205
206 $target = new \core\analytics\target\course_completion();
207
208 // Test valid analysables.
209
210 if (!empty($courseparams['students'])) {
211 // Enroll user in course.
212 $this->getDataGenerator()->enrol_user($user->id, $course->id);
213 }
214
215 $analysable = new \core_analytics\course($course);
216 $this->assertEquals($isvalid, $target->is_valid_analysable($analysable, $fortraining));
217 }
218
219 /**
525cee73 220 * Test the conditions of a valid sample, both common and specific to this target (course_completion).
03fce0a7
VDF
221 *
222 * @dataProvider sample_provider
223 * @param int $coursestart Course start date
224 * @param int $courseend Course end date
225 * @param int $timestart Enrol start date
226 * @param int $timeend Enrol end date
227 * @param boolean $isvalid True when sample is valid, false when it is not
228 */
229 public function test_core_target_course_completion_samples($coursestart, $courseend, $timestart, $timeend, $isvalid) {
230
231 $this->resetAfterTest(true);
232
233 $courserecord = new stdClass();
234 $courserecord->startdate = $coursestart;
235 $courserecord->enddate = $courseend;
236
237 $user = $this->getDataGenerator()->create_user();
238 $course = $this->getDataGenerator()->create_course($courserecord);
239 $this->getDataGenerator()->enrol_user($user->id, $course->id, null, 'manual', $timestart, $timeend);
240
241 $target = new \core\analytics\target\course_completion();
242 $analyser = new \core\analytics\analyser\student_enrolments(1, $target, [], [], []);
243 $analysable = new \core_analytics\course($course);
244
245 $class = new ReflectionClass('\core\analytics\analyser\student_enrolments');
246 $method = $class->getMethod('get_all_samples');
247 $method->setAccessible(true);
248
249 list($sampleids, $samplesdata) = $method->invoke($analyser, $analysable);
250 $target->add_sample_data($samplesdata);
251 $sampleid = reset($sampleids);
252
253 $this->assertEquals($isvalid, $target->is_valid_sample($sampleid, $analysable));
254 }
525cee73
VDF
255
256 /**
257 * Setup user, framework, competencies and course competencies.
258 */
259 protected function setup_competencies_environment() {
260 $this->resetAfterTest(true);
261 $now = time();
262 $this->setAdminUser();
263 $dg = $this->getDataGenerator();
264 $lpg = $dg->get_plugin_generator('core_competency');
265
266 $course = $dg->create_course(array('startdate' => $now - WEEKSECS, 'enddate' => $now - DAYSECS));
267 $coursenocompetencies = $dg->create_course(array('startdate' => $now - WEEKSECS, 'enddate' => $now - DAYSECS));
268
269 $u1 = $dg->create_user();
270 $this->getDataGenerator()->enrol_user($u1->id, $course->id);
271 $this->getDataGenerator()->enrol_user($u1->id, $coursenocompetencies->id);
272 $f1 = $lpg->create_framework();
273 $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
274 $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
275 $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
276 $c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
277 $cc1 = $lpg->create_course_competency(array('competencyid' => $c1->get('id'), 'courseid' => $course->id,
278 'ruleoutcome' => \core_competency\course_competency::OUTCOME_NONE));
279 $cc2 = $lpg->create_course_competency(array('competencyid' => $c2->get('id'), 'courseid' => $course->id,
280 'ruleoutcome' => \core_competency\course_competency::OUTCOME_EVIDENCE));
281 $cc3 = $lpg->create_course_competency(array('competencyid' => $c3->get('id'), 'courseid' => $course->id,
282 'ruleoutcome' => \core_competency\course_competency::OUTCOME_RECOMMEND));
283 $cc4 = $lpg->create_course_competency(array('competencyid' => $c4->get('id'), 'courseid' => $course->id,
284 'ruleoutcome' => \core_competency\course_competency::OUTCOME_COMPLETE));
285
286 return array(
287 'course' => $course,
288 'coursenocompetencies' => $coursenocompetencies,
289 'user' => $u1,
290 'course_competencies' => array($cc1, $cc2, $cc3, $cc4)
291 );
292 }
293
294 /**
295 * Test the specific conditions of a valid analysable for the course_competencies target.
296 */
297 public function test_core_target_course_competencies_analysable() {
298
299 $data = $this->setup_competencies_environment();
300
301 $analysable = new \core_analytics\course($data['course']);
302 $target = new \core\analytics\target\course_competencies();
303
304 $this->assertTrue($target->is_valid_analysable($analysable));
305
306 $analysable = new \core_analytics\course($data['coursenocompetencies']);
307 $this->assertEquals(get_string('nocompetenciesincourse', 'tool_lp'), $target->is_valid_analysable($analysable));
308 }
309
310 /**
311 * Test the target value calculation.
312 */
313 public function test_core_target_course_competencies_calculate() {
314
315 $data = $this->setup_competencies_environment();
316
317 $target = new \core\analytics\target\course_competencies();
318 $analyser = new \core\analytics\analyser\student_enrolments(1, $target, [], [], []);
319 $analysable = new \core_analytics\course($data['course']);
320
321 $class = new ReflectionClass('\core\analytics\analyser\student_enrolments');
322 $method = $class->getMethod('get_all_samples');
323 $method->setAccessible(true);
324
325 list($sampleids, $samplesdata) = $method->invoke($analyser, $analysable);
326 $target->add_sample_data($samplesdata);
327 $sampleid = reset($sampleids);
328
329 $class = new ReflectionClass('\core\analytics\target\course_competencies');
330 $method = $class->getMethod('calculate_sample');
331 $method->setAccessible(true);
332
333 // Method calculate_sample() returns 1 when the user has not achieved all the competencies assigned to the course.
334 $this->assertEquals(1, $method->invoke($target, $sampleid, $analysable));
335
336 // Grading of all the competences assigned to the course, in such way that the user achieves them all.
337 foreach ($data['course_competencies'] as $competency) {
338 \core_competency\api::grade_competency_in_course($data['course']->id, $data['user']->id,
339 $competency->get('competencyid'), 3, 'Unit test');
340 }
341 // Method calculate_sample() returns 0 when the user has achieved all the competencies assigned to the course.
342 $this->assertEquals(0, $method->invoke($target, $sampleid, $analysable));
343 }
03fce0a7 344}