Differences Between: [Versions 310 and 311] [Versions 39 and 311]
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 * mod_h5pactivity grader tests 19 * 20 * @package mod_h5pactivity 21 * @category test 22 * @copyright 2020 Ferran Recio <ferran@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace mod_h5pactivity\local; 27 28 use grade_item; 29 use stdClass; 30 31 /** 32 * Grader tests class for mod_h5pactivity. 33 * 34 * @package mod_h5pactivity 35 * @category test 36 * @copyright 2020 Ferran Recio <ferran@moodle.com> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class grader_test extends \advanced_testcase { 40 41 /** 42 * Setup to ensure that fixtures are loaded. 43 */ 44 public static function setupBeforeClass(): void { 45 global $CFG; 46 require_once($CFG->libdir.'/gradelib.php'); 47 } 48 49 /** 50 * Test for grade item delete. 51 */ 52 public function test_grade_item_delete() { 53 54 $this->resetAfterTest(); 55 $this->setAdminUser(); 56 57 $course = $this->getDataGenerator()->create_course(); 58 $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); 59 $user = $this->getDataGenerator()->create_and_enrol($course, 'student'); 60 61 $grader = new grader($activity); 62 63 // Force a user grade. 64 $this->generate_fake_attempt($activity, $user, 5, 10); 65 $grader->update_grades($user->id); 66 67 $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); 68 $this->assertNotEquals(0, count($gradeinfo->items)); 69 $this->assertArrayHasKey($user->id, $gradeinfo->items[0]->grades); 70 71 $grader->grade_item_delete(); 72 73 $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); 74 $this->assertEquals(0, count($gradeinfo->items)); 75 } 76 77 /** 78 * Test for grade item update. 79 * 80 * @dataProvider grade_item_update_data 81 * @param int $newgrade new activity grade 82 * @param bool $reset if has to reset grades 83 * @param string $idnumber the new idnumber 84 */ 85 public function test_grade_item_update(int $newgrade, bool $reset, string $idnumber) { 86 87 $this->resetAfterTest(); 88 $this->setAdminUser(); 89 90 $course = $this->getDataGenerator()->create_course(); 91 $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); 92 $user = $this->getDataGenerator()->create_and_enrol($course, 'student'); 93 94 // Force a user initial grade. 95 $grader = new grader($activity); 96 $this->generate_fake_attempt($activity, $user, 5, 10); 97 $grader->update_grades($user->id); 98 99 $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); 100 $this->assertNotEquals(0, count($gradeinfo->items)); 101 $item = array_shift($gradeinfo->items); 102 $this->assertArrayHasKey($user->id, $item->grades); 103 $this->assertEquals(50, round($item->grades[$user->id]->grade)); 104 105 // Module grade value determine the way gradebook acts. That means that the expected 106 // result depends on this value. 107 // - Grade > 0: regular max grade value. 108 // - Grade = 0: no grading is used (but grademax remains the same). 109 // - Grade < 0: a scaleid is used (value = -scaleid). 110 if ($newgrade > 0) { 111 $grademax = $newgrade; 112 $scaleid = null; 113 $usergrade = ($newgrade > 50) ? 50 : $newgrade; 114 } else if ($newgrade == 0) { 115 $grademax = 100; 116 $scaleid = null; 117 $usergrade = null; // No user grades expected. 118 } else if ($newgrade < 0) { 119 $scale = $this->getDataGenerator()->create_scale(array("scale" => "value1, value2, value3")); 120 $newgrade = -1 * $scale->id; 121 $grademax = 3; 122 $scaleid = $scale->id; 123 $usergrade = 3; // 50 value will ve converted to "value 3" on scale. 124 } 125 126 // Update grade item. 127 $activity->grade = $newgrade; 128 129 // In case a reset is need, usergrade will be empty. 130 if ($reset) { 131 $param = 'reset'; 132 $usergrade = null; 133 } else { 134 // Individual user gradings will be tested as a subcall of update_grades. 135 $param = null; 136 } 137 138 $grader = new grader($activity, $idnumber); 139 $grader->grade_item_update($param); 140 141 // Check new grade item and grades. 142 $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); 143 $item = array_shift($gradeinfo->items); 144 $this->assertEquals($scaleid, $item->scaleid); 145 $this->assertEquals($grademax, $item->grademax); 146 $this->assertArrayHasKey($user->id, $item->grades); 147 if ($usergrade) { 148 $this->assertEquals($usergrade, round($item->grades[$user->id]->grade)); 149 } else { 150 $this->assertEmpty($item->grades[$user->id]->grade); 151 } 152 if (!empty($idnumber)) { 153 $gradeitem = grade_item::fetch(['idnumber' => $idnumber, 'courseid' => $course->id]); 154 $this->assertInstanceOf('grade_item', $gradeitem); 155 } 156 } 157 158 /** 159 * Data provider for test_grade_item_update. 160 * 161 * @return array 162 */ 163 public function grade_item_update_data(): array { 164 return [ 165 'Change idnumber' => [ 166 100, false, 'newidnumber' 167 ], 168 'Increase max grade to 110' => [ 169 110, false, '' 170 ], 171 'Decrease max grade to 80' => [ 172 40, false, '' 173 ], 174 'Decrease max grade to 40 (less than actual grades)' => [ 175 40, false, '' 176 ], 177 'Reset grades' => [ 178 100, true, '' 179 ], 180 'Disable grades' => [ 181 0, false, '' 182 ], 183 'Use scales' => [ 184 -1, false, '' 185 ], 186 'Use scales with reset' => [ 187 -1, true, '' 188 ], 189 ]; 190 } 191 192 /** 193 * Test for grade update. 194 * 195 * @dataProvider update_grades_data 196 * @param int $newgrade the new activity grade 197 * @param bool $all if has to be applied to all students or just to one 198 * @param int $completion 1 all student have the activity completed, 0 one have incompleted 199 * @param array $results expected results (user1 grade, user2 grade) 200 */ 201 public function test_update_grades(int $newgrade, bool $all, int $completion, array $results) { 202 203 $this->resetAfterTest(); 204 $this->setAdminUser(); 205 206 $course = $this->getDataGenerator()->create_course(); 207 $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); 208 $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 209 $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); 210 211 // Force a user initial grade. 212 $grader = new grader($activity); 213 $this->generate_fake_attempt($activity, $user1, 5, 10); 214 $this->generate_fake_attempt($activity, $user2, 3, 12, $completion); 215 $grader->update_grades(); 216 217 $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, [$user1->id, $user2->id]); 218 $this->assertNotEquals(0, count($gradeinfo->items)); 219 $item = array_shift($gradeinfo->items); 220 $this->assertArrayHasKey($user1->id, $item->grades); 221 $this->assertArrayHasKey($user2->id, $item->grades); 222 $this->assertEquals(50, $item->grades[$user1->id]->grade); 223 // Uncompleted attempts does not generate grades. 224 if ($completion) { 225 $this->assertEquals(25, $item->grades[$user2->id]->grade); 226 } else { 227 $this->assertNull($item->grades[$user2->id]->grade); 228 229 } 230 231 // Module grade value determine the way gradebook acts. That means that the expected 232 // result depends on this value. 233 // - Grade > 0: regular max grade value. 234 // - Grade <= 0: no grade calculation is used (scale and no grading). 235 if ($newgrade < 0) { 236 $scale = $this->getDataGenerator()->create_scale(array("scale" => "value1, value2, value3")); 237 $activity->grade = -1 * $scale->id; 238 } else { 239 $activity->grade = $newgrade; 240 } 241 242 $userid = ($all) ? 0 : $user1->id; 243 244 $grader = new grader($activity); 245 $grader->update_grades($userid); 246 247 // Check new grade item and grades. 248 $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, [$user1->id, $user2->id]); 249 $item = array_shift($gradeinfo->items); 250 $this->assertArrayHasKey($user1->id, $item->grades); 251 $this->assertArrayHasKey($user2->id, $item->grades); 252 $this->assertEquals($results[0], $item->grades[$user1->id]->grade); 253 $this->assertEquals($results[1], $item->grades[$user2->id]->grade); 254 } 255 256 /** 257 * Data provider for test_grade_item_update. 258 * 259 * @return array 260 */ 261 public function update_grades_data(): array { 262 return [ 263 // Quantitative grade, all attempts completed. 264 'Same grademax, all users, all completed' => [ 265 100, true, 1, [50, 25] 266 ], 267 'Same grademax, one user, all completed' => [ 268 100, false, 1, [50, 25] 269 ], 270 'Increade max, all users, all completed' => [ 271 200, true, 1, [100, 50] 272 ], 273 'Increade max, one user, all completed' => [ 274 200, false, 1, [100, 25] 275 ], 276 'Decrease max, all users, all completed' => [ 277 50, true, 1, [25, 12.5] 278 ], 279 'Decrease max, one user, all completed' => [ 280 50, false, 1, [25, 25] 281 ], 282 // Quantitative grade, some attempts not completed. 283 'Same grademax, all users, not completed' => [ 284 100, true, 0, [50, null] 285 ], 286 'Same grademax, one user, not completed' => [ 287 100, false, 0, [50, null] 288 ], 289 'Increade max, all users, not completed' => [ 290 200, true, 0, [100, null] 291 ], 292 'Increade max, one user, not completed' => [ 293 200, false, 0, [100, null] 294 ], 295 'Decrease max, all users, not completed' => [ 296 50, true, 0, [25, null] 297 ], 298 'Decrease max, one user, not completed' => [ 299 50, false, 0, [25, null] 300 ], 301 // No grade (no grade will be used). 302 'No grade, all users, all completed' => [ 303 0, true, 1, [null, null] 304 ], 305 'No grade, one user, all completed' => [ 306 0, false, 1, [null, null] 307 ], 308 'No grade, all users, not completed' => [ 309 0, true, 0, [null, null] 310 ], 311 'No grade, one user, not completed' => [ 312 0, false, 0, [null, null] 313 ], 314 // Scale (grate item will updated but without regrading). 315 'Scale, all users, all completed' => [ 316 -1, true, 1, [3, 3] 317 ], 318 'Scale, one user, all completed' => [ 319 -1, false, 1, [3, 3] 320 ], 321 'Scale, all users, not completed' => [ 322 -1, true, 0, [3, null] 323 ], 324 'Scale, one user, not completed' => [ 325 -1, false, 0, [3, null] 326 ], 327 ]; 328 } 329 330 /** 331 * Create a fake attempt for a specific user. 332 * 333 * @param stdClass $activity activity instance record. 334 * @param stdClass $user user record 335 * @param int $rawscore score obtained 336 * @param int $maxscore attempt max score 337 * @param int $completion 1 for activity completed, 0 for not completed yet 338 * @return stdClass the attempt record 339 */ 340 private function generate_fake_attempt(stdClass $activity, stdClass $user, 341 int $rawscore, int $maxscore, int $completion = 1): stdClass { 342 global $DB; 343 344 $attempt = (object)[ 345 'h5pactivityid' => $activity->id, 346 'userid' => $user->id, 347 'timecreated' => 10, 348 'timemodified' => 20, 349 'attempt' => 1, 350 'rawscore' => $rawscore, 351 'maxscore' => $maxscore, 352 'duration' => 2, 353 'completion' => $completion, 354 'success' => 0, 355 ]; 356 $attempt->scaled = $attempt->rawscore / $attempt->maxscore; 357 $attempt->id = $DB->insert_record('h5pactivity_attempts', $attempt); 358 return $attempt; 359 } 360 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body