Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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 Random allocation 19 * 20 * @package workshopallocation_random 21 * @category phpunit 22 * @copyright 2009 David Mudrak <david.mudrak@gmail.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 // Include the code to test 29 global $CFG; 30 require_once($CFG->dirroot . '/mod/workshop/locallib.php'); 31 require_once($CFG->dirroot . '/mod/workshop/allocation/random/lib.php'); 32 33 34 class workshopallocation_random_testcase extends advanced_testcase { 35 36 /** workshop instance emulation */ 37 protected $workshop; 38 39 /** allocator instance */ 40 protected $allocator; 41 42 protected function setUp(): void { 43 parent::setUp(); 44 $this->resetAfterTest(); 45 $this->setAdminUser(); 46 $course = $this->getDataGenerator()->create_course(); 47 $workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $course)); 48 $cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id]; 49 $this->workshop = new workshop($workshop, $cm, $course); 50 $this->allocator = new testable_workshop_random_allocator($this->workshop); 51 } 52 53 protected function tearDown(): void { 54 $this->allocator = null; 55 $this->workshop = null; 56 parent::tearDown(); 57 } 58 59 public function test_self_allocation_empty_values() { 60 // fixture setup & exercise SUT & verify 61 $this->assertEquals(array(), $this->allocator->self_allocation()); 62 } 63 64 public function test_self_allocation_equal_user_groups() { 65 // fixture setup 66 $authors = array(0 => array_fill_keys(array(4, 6, 10), new stdclass())); 67 $reviewers = array(0 => array_fill_keys(array(4, 6, 10), new stdclass())); 68 // exercise SUT 69 $newallocations = $this->allocator->self_allocation($authors, $reviewers); 70 // verify 71 $this->assertEquals(array(array(4 => 4), array(6 => 6), array(10 => 10)), $newallocations); 72 } 73 74 public function test_self_allocation_different_user_groups() { 75 // fixture setup 76 $authors = array(0 => array_fill_keys(array(1, 4, 5, 10, 13), new stdclass())); 77 $reviewers = array(0 => array_fill_keys(array(4, 7, 10), new stdclass())); 78 // exercise SUT 79 $newallocations = $this->allocator->self_allocation($authors, $reviewers); 80 // verify 81 $this->assertEquals(array(array(4 => 4), array(10 => 10)), $newallocations); 82 } 83 84 public function test_self_allocation_skip_existing() { 85 // fixture setup 86 $authors = array(0 => array_fill_keys(array(3, 4, 10), new stdclass())); 87 $reviewers = array(0 => array_fill_keys(array(3, 4, 10), new stdclass())); 88 $assessments = array(23 => (object)array('authorid' => 3, 'reviewerid' => 3)); 89 // exercise SUT 90 $newallocations = $this->allocator->self_allocation($authors, $reviewers, $assessments); 91 // verify 92 $this->assertEquals(array(array(4 => 4), array(10 => 10)), $newallocations); 93 } 94 95 public function test_get_author_ids() { 96 // fixture setup 97 $newallocations = array(array(1 => 3), array(2 => 1), array(3 => 1)); 98 // exercise SUT & verify 99 $this->assertEquals(array(3, 1), $this->allocator->get_author_ids($newallocations)); 100 } 101 102 public function test_index_submissions_by_authors() { 103 // fixture setup 104 $submissions = array( 105 676 => (object)array('id' => 676, 'authorid' => 23), 106 121 => (object)array('id' => 121, 'authorid' => 56), 107 ); 108 // exercise SUT 109 $submissions = $this->allocator->index_submissions_by_authors($submissions); 110 // verify 111 $this->assertEquals(array( 112 23 => (object)array('id' => 676, 'authorid' => 23), 113 56 => (object)array('id' => 121, 'authorid' => 56), 114 ), $submissions); 115 } 116 117 public function test_index_submissions_by_authors_duplicate_author() { 118 // fixture setup 119 $submissions = array( 120 14 => (object)array('id' => 676, 'authorid' => 3), 121 87 => (object)array('id' => 121, 'authorid' => 3), 122 ); 123 // exercise SUT 124 $this->expectException(moodle_exception::class); 125 $submissions = $this->allocator->index_submissions_by_authors($submissions); 126 } 127 128 public function test_get_unique_allocations() { 129 // fixture setup 130 $newallocations = array(array(4 => 5), array(6 => 6), array(1 => 16), array(4 => 5), array(16 => 1)); 131 // exercise SUT 132 $newallocations = $this->allocator->get_unique_allocations($newallocations); 133 // verify 134 $this->assertEquals(array(array(4 => 5), array(6 => 6), array(1 => 16), array(16 => 1)), $newallocations); 135 } 136 137 public function test_get_unkept_assessments_no_keep_selfassessments() { 138 // fixture setup 139 $assessments = array( 140 23 => (object)array('authorid' => 3, 'reviewerid' => 3), 141 45 => (object)array('authorid' => 5, 'reviewerid' => 11), 142 12 => (object)array('authorid' => 6, 'reviewerid' => 3), 143 ); 144 $newallocations = array(array(4 => 5), array(11 => 5), array(1 => 16), array(4 => 5), array(16 => 1)); 145 // exercise SUT 146 $delassessments = $this->allocator->get_unkept_assessments($assessments, $newallocations, false); 147 // verify 148 // we want to keep $assessments[45] because it has been re-allocated 149 $this->assertEquals(array(23, 12), $delassessments); 150 } 151 152 public function test_get_unkept_assessments_keep_selfassessments() { 153 // fixture setup 154 $assessments = array( 155 23 => (object)array('authorid' => 3, 'reviewerid' => 3), 156 45 => (object)array('authorid' => 5, 'reviewerid' => 11), 157 12 => (object)array('authorid' => 6, 'reviewerid' => 3), 158 ); 159 $newallocations = array(array(4 => 5), array(11 => 5), array(1 => 16), array(4 => 5), array(16 => 1)); 160 // exercise SUT 161 $delassessments = $this->allocator->get_unkept_assessments($assessments, $newallocations, true); 162 // verify 163 // we want to keep $assessments[45] because it has been re-allocated 164 // we want to keep $assessments[23] because if is self assessment 165 $this->assertEquals(array(12), $delassessments); 166 } 167 168 /** 169 * Aggregates assessment info per author and per reviewer 170 */ 171 public function test_convert_assessments_to_links() { 172 // fixture setup 173 $assessments = array( 174 23 => (object)array('authorid' => 3, 'reviewerid' => 3), 175 45 => (object)array('authorid' => 5, 'reviewerid' => 11), 176 12 => (object)array('authorid' => 5, 'reviewerid' => 3), 177 ); 178 // exercise SUT 179 list($authorlinks, $reviewerlinks) = $this->allocator->convert_assessments_to_links($assessments); 180 // verify 181 $this->assertEquals(array(3 => array(3), 5 => array(11, 3)), $authorlinks); 182 $this->assertEquals(array(3 => array(3, 5), 11 => array(5)), $reviewerlinks); 183 } 184 185 /** 186 * Trivial case 187 */ 188 public function test_convert_assessments_to_links_empty() { 189 // fixture setup 190 $assessments = array(); 191 // exercise SUT 192 list($authorlinks, $reviewerlinks) = $this->allocator->convert_assessments_to_links($assessments); 193 // verify 194 $this->assertEquals(array(), $authorlinks); 195 $this->assertEquals(array(), $reviewerlinks); 196 } 197 198 /** 199 * If there is a single element with the lowest workload, it should be chosen 200 */ 201 public function test_get_element_with_lowest_workload_deterministic() { 202 // fixture setup 203 $workload = array(4 => 6, 9 => 1, 10 => 2); 204 // exercise SUT 205 $chosen = $this->allocator->get_element_with_lowest_workload($workload); 206 // verify 207 $this->assertEquals(9, $chosen); 208 } 209 210 /** 211 * If there are no elements available, must return false 212 */ 213 public function test_get_element_with_lowest_workload_impossible() { 214 // fixture setup 215 $workload = array(); 216 // exercise SUT 217 $chosen = $this->allocator->get_element_with_lowest_workload($workload); 218 // verify 219 $this->assertTrue($chosen === false); 220 } 221 222 /** 223 * If there are several elements with the lowest workload, one of them should be chosen randomly 224 */ 225 public function test_get_element_with_lowest_workload_random() { 226 // fixture setup 227 $workload = array(4 => 6, 9 => 2, 10 => 2); 228 // exercise SUT 229 $elements = $this->allocator->get_element_with_lowest_workload($workload); 230 // verify 231 // in theory, this test can fail even if the function works well. However, the probability of getting 232 // a row of a hundred same ids in this use case is 1/pow(2, 100) 233 // also, this just tests that each of the two elements has been chosen at least once. this is not to 234 // measure the quality or randomness of the algorithm 235 $counts = array(4 => 0, 9 => 0, 10 => 0); 236 for ($i = 0; $i < 100; $i++) { 237 $chosen = $this->allocator->get_element_with_lowest_workload($workload); 238 if (!in_array($chosen, array(4, 9, 10))) { 239 $this->fail('Invalid element ' . var_export($chosen, true) . ' chosen'); 240 break; 241 } else { 242 $counts[$this->allocator->get_element_with_lowest_workload($workload)]++; 243 } 244 } 245 $this->assertTrue(($counts[9] > 0) && ($counts[10] > 0)); 246 } 247 248 /** 249 * Floats should be rounded before they are compared 250 * 251 * This should test 252 */ 253 public function test_get_element_with_lowest_workload_random_floats() { 254 // fixture setup 255 $workload = array(1 => 1/13, 2 => 0.0769230769231); // should be considered as the same value 256 // exercise SUT 257 $elements = $this->allocator->get_element_with_lowest_workload($workload); 258 // verify 259 $counts = array(1 => 0, 2 => 0); 260 for ($i = 0; $i < 100; $i++) { 261 $chosen = $this->allocator->get_element_with_lowest_workload($workload); 262 if (!in_array($chosen, array(1, 2))) { 263 $this->fail('Invalid element ' . var_export($chosen, true) . ' chosen'); 264 break; 265 } else { 266 $counts[$this->allocator->get_element_with_lowest_workload($workload)]++; 267 } 268 } 269 $this->assertTrue(($counts[1] > 0) && ($counts[2] > 0)); 270 271 } 272 273 /** 274 * Filter new assessments so they do not contain existing 275 */ 276 public function test_filter_current_assessments() { 277 // fixture setup 278 $newallocations = array(array(3 => 5), array(11 => 5), array(2 => 9), array(3 => 5)); 279 $assessments = array( 280 23 => (object)array('authorid' => 3, 'reviewerid' => 3), 281 45 => (object)array('authorid' => 5, 'reviewerid' => 11), 282 12 => (object)array('authorid' => 5, 'reviewerid' => 3), 283 ); 284 // exercise SUT 285 $this->allocator->filter_current_assessments($newallocations, $assessments); 286 // verify 287 $this->assertEquals(array_values($newallocations), array(array(2 => 9))); 288 } 289 290 291 } 292 293 294 /** 295 * Make protected methods we want to test public 296 */ 297 class testable_workshop_random_allocator extends workshop_random_allocator { 298 public function self_allocation($authors=array(), $reviewers=array(), $assessments=array()) { 299 return parent::self_allocation($authors, $reviewers, $assessments); 300 } 301 public function get_author_ids($newallocations) { 302 return parent::get_author_ids($newallocations); 303 } 304 public function index_submissions_by_authors($submissions) { 305 return parent::index_submissions_by_authors($submissions); 306 } 307 public function get_unique_allocations($newallocations) { 308 return parent::get_unique_allocations($newallocations); 309 } 310 public function get_unkept_assessments($assessments, $newallocations, $keepselfassessments) { 311 return parent::get_unkept_assessments($assessments, $newallocations, $keepselfassessments); 312 } 313 public function convert_assessments_to_links($assessments) { 314 return parent::convert_assessments_to_links($assessments); 315 } 316 public function get_element_with_lowest_workload($workload) { 317 return parent::get_element_with_lowest_workload($workload); 318 } 319 public function filter_current_assessments(&$newallocations, $assessments) { 320 return parent::filter_current_assessments($newallocations, $assessments); 321 } 322 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body