PageRenderTime 49ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/grade/tests/grade_category_test.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 798 lines | 562 code | 128 blank | 108 comment | 3 complexity | 1a9f31a4f4c294227cc2bf0912d91817 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0
  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. * @package core_grades
  18. * @category phpunit
  19. * @copyright nicolas@moodle.com
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. defined('MOODLE_INTERNAL') || die();
  23. require_once(__DIR__.'/fixtures/lib.php');
  24. class core_grade_category_testcase extends grade_base_testcase {
  25. public function test_grade_category() {
  26. $this->sub_test_grade_category_construct();
  27. $this->sub_test_grade_category_build_path();
  28. $this->sub_test_grade_category_fetch();
  29. $this->sub_test_grade_category_fetch_all();
  30. $this->sub_test_grade_category_update();
  31. $this->sub_test_grade_category_delete();
  32. $this->sub_test_grade_category_insert();
  33. $this->sub_test_grade_category_qualifies_for_regrading();
  34. $this->sub_test_grade_category_force_regrading();
  35. $this->sub_test_grade_category_aggregate_grades();
  36. $this->sub_test_grade_category_apply_limit_rules();
  37. $this->sub_test_grade_category_is_aggregationcoef_used();
  38. $this->sub_test_grade_category_aggregation_uses_aggregationcoef();
  39. $this->sub_test_grade_category_fetch_course_tree();
  40. $this->sub_test_grade_category_get_children();
  41. $this->sub_test_grade_category_load_grade_item();
  42. $this->sub_test_grade_category_get_grade_item();
  43. $this->sub_test_grade_category_load_parent_category();
  44. $this->sub_test_grade_category_get_parent_category();
  45. $this->sub_test_grade_category_get_name();
  46. $this->sub_test_grade_category_set_parent();
  47. $this->sub_test_grade_category_get_final();
  48. $this->sub_test_grade_category_get_sortorder();
  49. $this->sub_test_grade_category_set_sortorder();
  50. $this->sub_test_grade_category_is_editable();
  51. $this->sub_test_grade_category_move_after_sortorder();
  52. $this->sub_test_grade_category_is_course_category();
  53. $this->sub_test_grade_category_fetch_course_category();
  54. $this->sub_test_grade_category_is_locked();
  55. $this->sub_test_grade_category_set_locked();
  56. $this->sub_test_grade_category_is_hidden();
  57. $this->sub_test_grade_category_set_hidden();
  58. $this->sub_test_grade_category_can_control_visibility();
  59. // This won't work until MDL-11837 is complete.
  60. // $this->sub_test_grade_category_generate_grades();
  61. // Do this last as adding a second course category messes up the data.
  62. $this->sub_test_grade_category_insert_course_category();
  63. $this->sub_test_grade_category_is_extracredit_used();
  64. $this->sub_test_grade_category_aggregation_uses_extracredit();
  65. }
  66. // Adds 3 new grade categories at various depths.
  67. protected function sub_test_grade_category_construct() {
  68. $course_category = grade_category::fetch_course_category($this->courseid);
  69. $params = new stdClass();
  70. $params->courseid = $this->courseid;
  71. $params->fullname = 'unittestcategory4';
  72. $grade_category = new grade_category($params, false);
  73. $grade_category->insert();
  74. $this->grade_categories[] = $grade_category;
  75. $this->assertEquals($params->courseid, $grade_category->courseid);
  76. $this->assertEquals($params->fullname, $grade_category->fullname);
  77. $this->assertEquals(2, $grade_category->depth);
  78. $this->assertEquals("/$course_category->id/$grade_category->id/", $grade_category->path);
  79. $parentpath = $grade_category->path;
  80. // Test a child category.
  81. $params->parent = $grade_category->id;
  82. $params->fullname = 'unittestcategory5';
  83. $grade_category = new grade_category($params, false);
  84. $grade_category->insert();
  85. $this->grade_categories[] = $grade_category;
  86. $this->assertEquals(3, $grade_category->depth);
  87. $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path);
  88. $parentpath = $grade_category->path;
  89. // Test a third depth category.
  90. $params->parent = $grade_category->id;
  91. $params->fullname = 'unittestcategory6';
  92. $grade_category = new grade_category($params, false);
  93. $grade_category->insert();
  94. $this->grade_categories[50] = $grade_category;// Going to delete this one later hence the special index.
  95. $this->assertEquals(4, $grade_category->depth);
  96. $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path);
  97. }
  98. protected function sub_test_grade_category_build_path() {
  99. $grade_category = new grade_category($this->grade_categories[1]);
  100. $this->assertTrue(method_exists($grade_category, 'build_path'));
  101. $path = grade_category::build_path($grade_category);
  102. $this->assertEquals($grade_category->path, $path);
  103. }
  104. protected function sub_test_grade_category_fetch() {
  105. $grade_category = new grade_category();
  106. $this->assertTrue(method_exists($grade_category, 'fetch'));
  107. $grade_category = grade_category::fetch(array('id'=>$this->grade_categories[0]->id));
  108. $this->assertEquals($this->grade_categories[0]->id, $grade_category->id);
  109. $this->assertEquals($this->grade_categories[0]->fullname, $grade_category->fullname);
  110. }
  111. protected function sub_test_grade_category_fetch_all() {
  112. $grade_category = new grade_category();
  113. $this->assertTrue(method_exists($grade_category, 'fetch_all'));
  114. $grade_categories = grade_category::fetch_all(array('courseid'=>$this->courseid));
  115. $this->assertEquals(count($this->grade_categories), count($grade_categories)-1);
  116. }
  117. protected function sub_test_grade_category_update() {
  118. global $DB;
  119. $grade_category = new grade_category($this->grade_categories[0]);
  120. $this->assertTrue(method_exists($grade_category, 'update'));
  121. $grade_category->fullname = 'Updated info for this unittest grade_category';
  122. $grade_category->path = null; // Path must be recalculated if missing.
  123. $grade_category->depth = null;
  124. $grade_category->aggregation = GRADE_AGGREGATE_MAX; // Should force regrading.
  125. $grade_item = $grade_category->get_grade_item();
  126. $this->assertEquals(0, $grade_item->needsupdate);
  127. $this->assertTrue($grade_category->update());
  128. $fullname = $DB->get_field('grade_categories', 'fullname', array('id' => $this->grade_categories[0]->id));
  129. $this->assertEquals($grade_category->fullname, $fullname);
  130. $path = $DB->get_field('grade_categories', 'path', array('id' => $this->grade_categories[0]->id));
  131. $this->assertEquals($grade_category->path, $path);
  132. $depth = $DB->get_field('grade_categories', 'depth', array('id' => $this->grade_categories[0]->id));
  133. $this->assertEquals($grade_category->depth, $depth);
  134. $grade_item = $grade_category->get_grade_item();
  135. $this->assertEquals(1, $grade_item->needsupdate);
  136. }
  137. protected function sub_test_grade_category_delete() {
  138. global $DB;
  139. $grade_category = new grade_category($this->grade_categories[50]);
  140. $this->assertTrue(method_exists($grade_category, 'delete'));
  141. $this->assertTrue($grade_category->delete());
  142. $this->assertFalse($DB->get_record('grade_categories', array('id' => $grade_category->id)));
  143. }
  144. protected function sub_test_grade_category_insert() {
  145. $course_category = grade_category::fetch_course_category($this->courseid);
  146. $grade_category = new grade_category();
  147. $this->assertTrue(method_exists($grade_category, 'insert'));
  148. $grade_category->fullname = 'unittestcategory4';
  149. $grade_category->courseid = $this->courseid;
  150. $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
  151. $grade_category->aggregateonlygraded = 1;
  152. $grade_category->keephigh = 100;
  153. $grade_category->droplow = 10;
  154. $grade_category->hidden = 0;
  155. $grade_category->parent = $this->grade_categories[1]->id; // sub_test_grade_category_delete() removed the category at 0.
  156. $grade_category->insert();
  157. $this->assertEquals('/'.$course_category->id.'/'.$this->grade_categories[1]->parent.'/'.$this->grade_categories[1]->id.'/'.$grade_category->id.'/', $grade_category->path);
  158. $this->assertEquals(4, $grade_category->depth);
  159. $last_grade_category = end($this->grade_categories);
  160. $this->assertFalse(empty($grade_category->grade_item));
  161. $this->assertEquals($grade_category->id, $grade_category->grade_item->iteminstance);
  162. $this->assertEquals('category', $grade_category->grade_item->itemtype);
  163. $this->assertEquals($grade_category->id, $last_grade_category->id + 1);
  164. $this->assertFalse(empty($grade_category->timecreated));
  165. $this->assertFalse(empty($grade_category->timemodified));
  166. }
  167. protected function sub_test_grade_category_qualifies_for_regrading() {
  168. $grade_category = new grade_category($this->grade_categories[1]);
  169. $this->assertTrue(method_exists($grade_category, 'qualifies_for_regrading'));
  170. $this->assertFalse($grade_category->qualifies_for_regrading());
  171. $grade_category->aggregation = GRADE_AGGREGATE_MAX;
  172. $this->assertTrue($grade_category->qualifies_for_regrading());
  173. $grade_category = new grade_category($this->grade_categories[1]);
  174. $grade_category->droplow = 99;
  175. $this->assertTrue($grade_category->qualifies_for_regrading());
  176. $grade_category = new grade_category($this->grade_categories[1]);
  177. $grade_category->keephigh = 99;
  178. $this->assertTrue($grade_category->qualifies_for_regrading());
  179. }
  180. protected function sub_test_grade_category_force_regrading() {
  181. $grade_category = new grade_category($this->grade_categories[1]);
  182. $this->assertTrue(method_exists($grade_category, 'force_regrading'));
  183. $grade_category->load_grade_item();
  184. $this->assertEquals(0, $grade_category->grade_item->needsupdate);
  185. $grade_category->force_regrading();
  186. $grade_category->grade_item = null;
  187. $grade_category->load_grade_item();
  188. $this->assertEquals(1, $grade_category->grade_item->needsupdate);
  189. }
  190. /**
  191. * Tests the calculation of grades using the various aggregation methods with and without hidden grades
  192. * This will not work entirely until MDL-11837 is done
  193. */
  194. protected function sub_test_grade_category_generate_grades() {
  195. global $DB;
  196. // Inserting some special grade items to make testing the final grade calculation easier.
  197. $params = new stdClass();
  198. $params->courseid = $this->courseid;
  199. $params->fullname = 'unittestgradecalccategory';
  200. $params->aggregation = GRADE_AGGREGATE_MEAN;
  201. $params->aggregateonlygraded = 0;
  202. $grade_category = new grade_category($params, false);
  203. $grade_category->insert();
  204. $this->assertTrue(method_exists($grade_category, 'generate_grades'));
  205. $grade_category->load_grade_item();
  206. $cgi = $grade_category->get_grade_item();
  207. $cgi->grademin = 0;
  208. $cgi->grademax = 20; // 3 grade items out of 10 but category is out of 20 to force scaling to occur.
  209. $cgi->update();
  210. // 3 grade items each with a maximum grade of 10.
  211. $grade_items = array();
  212. for ($i=0; $i<3; $i++) {
  213. $grade_items[$i] = new grade_item();
  214. $grade_items[$i]->courseid = $this->courseid;
  215. $grade_items[$i]->categoryid = $grade_category->id;
  216. $grade_items[$i]->itemname = 'manual grade_item '.$i;
  217. $grade_items[$i]->itemtype = 'manual';
  218. $grade_items[$i]->itemnumber = 0;
  219. $grade_items[$i]->needsupdate = false;
  220. $grade_items[$i]->gradetype = GRADE_TYPE_VALUE;
  221. $grade_items[$i]->grademin = 0;
  222. $grade_items[$i]->grademax = 10;
  223. $grade_items[$i]->iteminfo = 'Manual grade item used for unit testing';
  224. $grade_items[$i]->timecreated = time();
  225. $grade_items[$i]->timemodified = time();
  226. // Used as the weight by weighted mean and as extra credit by mean with extra credit.
  227. // Will be 0, 1 and 2.
  228. $grade_items[$i]->aggregationcoef = $i;
  229. $grade_items[$i]->insert();
  230. }
  231. // A grade for each grade item.
  232. $grade_grades = array();
  233. for ($i=0; $i<3; $i++) {
  234. $grade_grades[$i] = new grade_grade();
  235. $grade_grades[$i]->itemid = $grade_items[$i]->id;
  236. $grade_grades[$i]->userid = $this->userid;
  237. $grade_grades[$i]->rawgrade = ($i+1)*2; // Produce grade grades of 2, 4 and 6.
  238. $grade_grades[$i]->finalgrade = ($i+1)*2;
  239. $grade_grades[$i]->timecreated = time();
  240. $grade_grades[$i]->timemodified = time();
  241. $grade_grades[$i]->information = '1 of 2 grade_grades';
  242. $grade_grades[$i]->informationformat = FORMAT_PLAIN;
  243. $grade_grades[$i]->feedback = 'Good, but not good enough..';
  244. $grade_grades[$i]->feedbackformat = FORMAT_PLAIN;
  245. $grade_grades[$i]->insert();
  246. }
  247. // 3 grade items with 1 grade_grade each.
  248. // grade grades have the values 2, 4 and 6.
  249. // First correct answer is the aggregate with all 3 grades.
  250. // Second correct answer is with the first grade (value 2) hidden.
  251. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEDIAN, 'GRADE_AGGREGATE_MEDIAN', 8, 8);
  252. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MAX, 'GRADE_AGGREGATE_MAX', 12, 12);
  253. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MODE, 'GRADE_AGGREGATE_MODE', 12, 12);
  254. // Weighted mean. note grade totals are rounded to an int to prevent rounding discrepancies. correct final grade isnt actually exactly 10
  255. // 3 items with grades 2, 4 and 6 with weights 0, 1 and 2 and all out of 10. then doubled to be out of 20.
  256. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN, 'GRADE_AGGREGATE_WEIGHTED_MEAN', 10, 10);
  257. // Simple weighted mean.
  258. // 3 items with grades 2, 4 and 6 equally weighted and all out of 10. then doubled to be out of 20.
  259. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN2, 'GRADE_AGGREGATE_WEIGHTED_MEAN2', 8, 10);
  260. // Mean of grades with extra credit.
  261. // 3 items with grades 2, 4 and 6 with extra credit 0, 1 and 2 equally weighted and all out of 10. then doubled to be out of 20.
  262. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_EXTRACREDIT_MEAN, 'GRADE_AGGREGATE_EXTRACREDIT_MEAN', 10, 13);
  263. // Aggregation tests the are affected by a hidden grade currently dont work as we dont store the altered grade in the database
  264. // instead an in memory recalculation is done. This should be remedied by MDL-11837.
  265. // Fails with 1 grade hidden. still reports 8 as being correct.
  266. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEAN, 'GRADE_AGGREGATE_MEAN', 8, 10);
  267. // Fails with 1 grade hidden. still reports 4 as being correct.
  268. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MIN, 'GRADE_AGGREGATE_MIN', 4, 8);
  269. // Fails with 1 grade hidden. still reports 12 as being correct.
  270. $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_SUM, 'GRADE_AGGREGATE_SUM', 12, 10);
  271. }
  272. /**
  273. * Test grade category aggregation using the supplied grade objects and aggregation method
  274. * @param grade_category $grade_category the category to be tested
  275. * @param array $grade_items array of instance of grade_item
  276. * @param array $grade_grades array of instances of grade_grade
  277. * @param int $aggmethod the aggregation method to apply ie GRADE_AGGREGATE_MEAN
  278. * @param string $aggmethodname the name of the aggregation method to apply. Used to display any test failure messages
  279. * @param int $correct1 the correct final grade for the category with NO items hidden
  280. * @param int $correct2 the correct final grade for the category with the grade at $grade_grades[0] hidden
  281. * @return void
  282. */
  283. protected function helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, $aggmethod, $aggmethodname, $correct1, $correct2) {
  284. $grade_category->aggregation = $aggmethod;
  285. $grade_category->update();
  286. // Check grade_item isnt hidden from a previous test.
  287. $grade_items[0]->set_hidden(0, true);
  288. $this->helper_test_grade_aggregation_result($grade_category, $correct1, 'Testing aggregation method('.$aggmethodname.') with no items hidden %s');
  289. // Hide the grade item with grade of 2.
  290. $grade_items[0]->set_hidden(1, true);
  291. $this->helper_test_grade_aggregation_result($grade_category, $correct2, 'Testing aggregation method('.$aggmethodname.') with 1 item hidden %s');
  292. }
  293. /**
  294. * Verify the value of the category grade item for $this->userid
  295. * @param grade_category $grade_category the category to be tested
  296. * @param int $correctgrade the expected grade
  297. * @param string msg The message that should be displayed if the correct grade is not found
  298. * @return void
  299. */
  300. protected function helper_test_grade_aggregation_result($grade_category, $correctgrade, $msg) {
  301. global $DB;
  302. $category_grade_item = $grade_category->get_grade_item();
  303. // This creates all the grade_grades we need.
  304. grade_regrade_final_grades($this->courseid);
  305. $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid));
  306. $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
  307. $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg);
  308. /*
  309. * TODO this doesnt work as the grade_grades created by $grade_category->generate_grades(); dont
  310. * observe the category's max grade
  311. // delete the grade_grades for the category itself and check they get recreated correctly.
  312. $DB->delete_records('grade_grades', array('itemid'=>$category_grade_item->id));
  313. $grade_category->generate_grades();
  314. $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid));
  315. $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
  316. $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg);
  317. *
  318. */
  319. }
  320. protected function sub_test_grade_category_aggregate_grades() {
  321. $category = new grade_category($this->grade_categories[0]);
  322. $this->assertTrue(method_exists($category, 'aggregate_grades'));
  323. // Tested more fully via test_grade_category_generate_grades().
  324. }
  325. protected function sub_test_grade_category_apply_limit_rules() {
  326. $items[$this->grade_items[0]->id] = new grade_item($this->grade_items[0], false);
  327. $items[$this->grade_items[1]->id] = new grade_item($this->grade_items[1], false);
  328. $items[$this->grade_items[2]->id] = new grade_item($this->grade_items[2], false);
  329. $items[$this->grade_items[4]->id] = new grade_item($this->grade_items[4], false);
  330. // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades.
  331. $category = new grade_category();
  332. $category->droplow = 2;
  333. $grades = array($this->grade_items[0]->id=>5.374,
  334. $this->grade_items[1]->id=>9.4743,
  335. $this->grade_items[2]->id=>2.5474,
  336. $this->grade_items[4]->id=>7.3754);
  337. $category->apply_limit_rules($grades, $items);
  338. $this->assertEquals(count($grades), 2);
  339. $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
  340. $this->assertEquals($grades[$this->grade_items[4]->id], 7.3754);
  341. // Test aggregating only the highest 1 out of 4 grades.
  342. $category = new grade_category();
  343. $category->keephigh = 1;
  344. $category->droplow = 0;
  345. $grades = array($this->grade_items[0]->id=>5.374,
  346. $this->grade_items[1]->id=>9.4743,
  347. $this->grade_items[2]->id=>2.5474,
  348. $this->grade_items[4]->id=>7.3754);
  349. $category->apply_limit_rules($grades, $items);
  350. $this->assertEquals(count($grades), 1);
  351. $grade = reset($grades);
  352. $this->assertEquals(9.4743, $grade);
  353. // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades.
  354. // An extra credit grade item should be kept even if droplow means it would otherwise be excluded.
  355. $category = new grade_category();
  356. $category->droplow = 2;
  357. $category->aggregation = GRADE_AGGREGATE_SUM;
  358. $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit".
  359. $grades = array($this->grade_items[0]->id=>5.374,
  360. $this->grade_items[1]->id=>9.4743,
  361. $this->grade_items[2]->id=>2.5474,
  362. $this->grade_items[4]->id=>7.3754);
  363. $category->apply_limit_rules($grades, $items);
  364. $this->assertEquals(count($grades), 2);
  365. $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
  366. $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
  367. // Test only aggregating the highest 1 out of 4 grades.
  368. // An extra credit grade item is retained in addition to the highest grade.
  369. $category = new grade_category();
  370. $category->keephigh = 1;
  371. $category->droplow = 0;
  372. $category->aggregation = GRADE_AGGREGATE_SUM;
  373. $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit".
  374. $grades = array($this->grade_items[0]->id=>5.374,
  375. $this->grade_items[1]->id=>9.4743,
  376. $this->grade_items[2]->id=>2.5474,
  377. $this->grade_items[4]->id=>7.3754);
  378. $category->apply_limit_rules($grades, $items);
  379. $this->assertEquals(count($grades), 2);
  380. $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
  381. $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
  382. // Test excluding the lowest 1 out of 4 grades from aggregation with two 0 grades.
  383. $items[$this->grade_items[2]->id]->aggregationcoef = 0; // Undo marking grade item 2 as "extra credit".
  384. $category = new grade_category();
  385. $category->droplow = 1;
  386. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
  387. $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
  388. $this->grade_items[1]->id=>5, // 5 out of 100.
  389. $this->grade_items[2]->id=>2, // 0 out of 6.
  390. $this->grade_items[4]->id=>0); // 0 out of 100.
  391. $category->apply_limit_rules($grades, $items);
  392. $this->assertEquals(count($grades), 3);
  393. $this->assertEquals($grades[$this->grade_items[1]->id], 5);
  394. $this->assertEquals($grades[$this->grade_items[2]->id], 2);
  395. $this->assertEquals($grades[$this->grade_items[4]->id], 0);
  396. // Test excluding the lowest 2 out of 4 grades from aggregation with three 0 grades.
  397. $category = new grade_category();
  398. $category->droplow = 2;
  399. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
  400. $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
  401. $this->grade_items[1]->id=>5, // 5 out of 100.
  402. $this->grade_items[2]->id=>0, // 0 out of 6.
  403. $this->grade_items[4]->id=>0); // 0 out of 100. Should be excluded from aggregation.
  404. $category->apply_limit_rules($grades, $items);
  405. $this->assertEquals(count($grades), 2);
  406. $this->assertEquals($grades[$this->grade_items[1]->id], 5);
  407. $this->assertEquals($grades[$this->grade_items[2]->id], 0);
  408. // Test excluding the lowest 5 out of 4 grades from aggregation.
  409. // Just to check we handle this sensibly.
  410. $category = new grade_category();
  411. $category->droplow = 5;
  412. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
  413. $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
  414. $this->grade_items[1]->id=>5, // 5 out of 100.
  415. $this->grade_items[2]->id=>6, // 6 out of 6.
  416. $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation.
  417. $category->apply_limit_rules($grades, $items);
  418. $this->assertEquals(count($grades), 0);
  419. // Test excluding the lowest 4 out of 4 grades from aggregation with one marked as extra credit.
  420. $category = new grade_category();
  421. $category->droplow = 4;
  422. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
  423. $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit".
  424. $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
  425. $this->grade_items[1]->id=>5, // 5 out of 100. Should be excluded from aggregation.
  426. $this->grade_items[2]->id=>6, // 6 out of 6. Extra credit. Should be retained.
  427. $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation.
  428. $category->apply_limit_rules($grades, $items);
  429. $this->assertEquals(count($grades), 1);
  430. $this->assertEquals($grades[$this->grade_items[2]->id], 6);
  431. // MDL-35667 - There was an infinite loop if several items had the same grade and at least one was extra credit.
  432. $category = new grade_category();
  433. $category->droplow = 1;
  434. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
  435. $items[$this->grade_items[1]->id]->aggregationcoef = 1; // Mark grade item 1 as "extra credit".
  436. $grades = array($this->grade_items[0]->id=>1, // 1 out of 110. Should be excluded from aggregation.
  437. $this->grade_items[1]->id=>1, // 1 out of 100. Extra credit. Should be retained.
  438. $this->grade_items[2]->id=>1, // 1 out of 6. Should be retained.
  439. $this->grade_items[4]->id=>1);// 1 out of 100. Should be retained.
  440. $category->apply_limit_rules($grades, $items);
  441. $this->assertEquals(count($grades), 3);
  442. $this->assertEquals($grades[$this->grade_items[1]->id], 1);
  443. $this->assertEquals($grades[$this->grade_items[2]->id], 1);
  444. $this->assertEquals($grades[$this->grade_items[4]->id], 1);
  445. }
  446. protected function sub_test_grade_category_is_aggregationcoef_used() {
  447. $category = new grade_category();
  448. // Following use aggregationcoef.
  449. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
  450. $this->assertTrue($category->is_aggregationcoef_used());
  451. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
  452. $this->assertTrue($category->is_aggregationcoef_used());
  453. $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
  454. $this->assertTrue($category->is_aggregationcoef_used());
  455. $category->aggregation = GRADE_AGGREGATE_SUM;
  456. $this->assertTrue($category->is_aggregationcoef_used());
  457. // Following don't use aggregationcoef.
  458. $category->aggregation = GRADE_AGGREGATE_MAX;
  459. $this->assertFalse($category->is_aggregationcoef_used());
  460. $category->aggregation = GRADE_AGGREGATE_MEAN;
  461. $this->assertFalse($category->is_aggregationcoef_used());
  462. $category->aggregation = GRADE_AGGREGATE_MEDIAN;
  463. $this->assertFalse($category->is_aggregationcoef_used());
  464. $category->aggregation = GRADE_AGGREGATE_MIN;
  465. $this->assertFalse($category->is_aggregationcoef_used());
  466. $category->aggregation = GRADE_AGGREGATE_MODE;
  467. $this->assertFalse($category->is_aggregationcoef_used());
  468. }
  469. protected function sub_test_grade_category_aggregation_uses_aggregationcoef() {
  470. $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN));
  471. $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN2));
  472. $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_EXTRACREDIT_MEAN));
  473. $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_SUM));
  474. $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MAX));
  475. $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEAN));
  476. $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEDIAN));
  477. $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MIN));
  478. $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MODE));
  479. }
  480. protected function sub_test_grade_category_fetch_course_tree() {
  481. $category = new grade_category();
  482. $this->assertTrue(method_exists($category, 'fetch_course_tree'));
  483. // TODO: add some tests.
  484. }
  485. protected function sub_test_grade_category_get_children() {
  486. $course_category = grade_category::fetch_course_category($this->courseid);
  487. $category = new grade_category($this->grade_categories[0]);
  488. $this->assertTrue(method_exists($category, 'get_children'));
  489. $children_array = $category->get_children(0);
  490. $this->assertTrue(is_array($children_array));
  491. $this->assertFalse(empty($children_array[2]));
  492. $this->assertFalse(empty($children_array[2]['object']));
  493. $this->assertFalse(empty($children_array[2]['children']));
  494. $this->assertEquals($this->grade_categories[1]->id, $children_array[2]['object']->id);
  495. $this->assertEquals($this->grade_categories[2]->id, $children_array[5]['object']->id);
  496. $this->assertEquals($this->grade_items[0]->id, $children_array[2]['children'][3]['object']->id);
  497. $this->assertEquals($this->grade_items[1]->id, $children_array[2]['children'][4]['object']->id);
  498. $this->assertEquals($this->grade_items[2]->id, $children_array[5]['children'][6]['object']->id);
  499. }
  500. protected function sub_test_grade_category_load_grade_item() {
  501. $category = new grade_category($this->grade_categories[0]);
  502. $this->assertTrue(method_exists($category, 'load_grade_item'));
  503. $this->assertEquals(null, $category->grade_item);
  504. $category->load_grade_item();
  505. $this->assertEquals($this->grade_items[3]->id, $category->grade_item->id);
  506. }
  507. protected function sub_test_grade_category_get_grade_item() {
  508. $category = new grade_category($this->grade_categories[0]);
  509. $this->assertTrue(method_exists($category, 'get_grade_item'));
  510. $grade_item = $category->get_grade_item();
  511. $this->assertEquals($this->grade_items[3]->id, $grade_item->id);
  512. }
  513. protected function sub_test_grade_category_load_parent_category() {
  514. $category = new grade_category($this->grade_categories[1]);
  515. $this->assertTrue(method_exists($category, 'load_parent_category'));
  516. $this->assertEquals(null, $category->parent_category);
  517. $category->load_parent_category();
  518. $this->assertEquals($this->grade_categories[0]->id, $category->parent_category->id);
  519. }
  520. protected function sub_test_grade_category_get_parent_category() {
  521. $category = new grade_category($this->grade_categories[1]);
  522. $this->assertTrue(method_exists($category, 'get_parent_category'));
  523. $parent_category = $category->get_parent_category();
  524. $this->assertEquals($this->grade_categories[0]->id, $parent_category->id);
  525. }
  526. protected function sub_test_grade_category_get_name() {
  527. $category = new grade_category($this->grade_categories[0]);
  528. $this->assertTrue(method_exists($category, 'get_name'));
  529. $this->assertEquals($this->grade_categories[0]->fullname, $category->get_name());
  530. }
  531. protected function sub_test_grade_category_set_parent() {
  532. $category = new grade_category($this->grade_categories[1]);
  533. $this->assertTrue(method_exists($category, 'set_parent'));
  534. // TODO: implement detailed tests.
  535. $course_category = grade_category::fetch_course_category($this->courseid);
  536. $this->assertTrue($category->set_parent($course_category->id));
  537. $this->assertEquals($course_category->id, $category->parent);
  538. }
  539. protected function sub_test_grade_category_get_final() {
  540. $category = new grade_category($this->grade_categories[0]);
  541. $this->assertTrue(method_exists($category, 'get_final'));
  542. $category->load_grade_item();
  543. $this->assertEquals($category->get_final(), $category->grade_item->get_final());
  544. }
  545. protected function sub_test_grade_category_get_sortorder() {
  546. $category = new grade_category($this->grade_categories[0]);
  547. $this->assertTrue(method_exists($category, 'get_sortorder'));
  548. $category->load_grade_item();
  549. $this->assertEquals($category->get_sortorder(), $category->grade_item->get_sortorder());
  550. }
  551. protected function sub_test_grade_category_set_sortorder() {
  552. $category = new grade_category($this->grade_categories[0]);
  553. $this->assertTrue(method_exists($category, 'set_sortorder'));
  554. $category->load_grade_item();
  555. $this->assertEquals($category->set_sortorder(10), $category->grade_item->set_sortorder(10));
  556. }
  557. protected function sub_test_grade_category_move_after_sortorder() {
  558. $category = new grade_category($this->grade_categories[0]);
  559. $this->assertTrue(method_exists($category, 'move_after_sortorder'));
  560. $category->load_grade_item();
  561. $this->assertEquals($category->move_after_sortorder(10), $category->grade_item->move_after_sortorder(10));
  562. }
  563. protected function sub_test_grade_category_is_course_category() {
  564. $category = grade_category::fetch_course_category($this->courseid);
  565. $this->assertTrue(method_exists($category, 'is_course_category'));
  566. $this->assertTrue($category->is_course_category());
  567. }
  568. protected function sub_test_grade_category_fetch_course_category() {
  569. $category = new grade_category();
  570. $this->assertTrue(method_exists($category, 'fetch_course_category'));
  571. $category = grade_category::fetch_course_category($this->courseid);
  572. $this->assertTrue(empty($category->parent));
  573. }
  574. /**
  575. * TODO implement
  576. */
  577. protected function sub_test_grade_category_is_editable() {
  578. }
  579. protected function sub_test_grade_category_is_locked() {
  580. $category = new grade_category($this->grade_categories[0]);
  581. $this->assertTrue(method_exists($category, 'is_locked'));
  582. $category->load_grade_item();
  583. $this->assertEquals($category->is_locked(), $category->grade_item->is_locked());
  584. }
  585. protected function sub_test_grade_category_set_locked() {
  586. $category = new grade_category($this->grade_categories[0]);
  587. $this->assertTrue(method_exists($category, 'set_locked'));
  588. // Will return false as cannot lock a grade that needs updating.
  589. $this->assertFalse($category->set_locked(1));
  590. grade_regrade_final_grades($this->courseid);
  591. // Get the category from the db again.
  592. $category = new grade_category($this->grade_categories[0]);
  593. $this->assertTrue($category->set_locked(1));
  594. }
  595. protected function sub_test_grade_category_is_hidden() {
  596. $category = new grade_category($this->grade_categories[0]);
  597. $this->assertTrue(method_exists($category, 'is_hidden'));
  598. $category->load_grade_item();
  599. $this->assertEquals($category->is_hidden(), $category->grade_item->is_hidden());
  600. }
  601. protected function sub_test_grade_category_set_hidden() {
  602. $category = new grade_category($this->grade_categories[0]);
  603. $this->assertTrue(method_exists($category, 'set_hidden'));
  604. $category->set_hidden(1);
  605. $category->load_grade_item();
  606. $this->assertEquals(true, $category->grade_item->is_hidden());
  607. }
  608. protected function sub_test_grade_category_can_control_visibility() {
  609. $category = new grade_category($this->grade_categories[0]);
  610. $this->assertTrue($category->can_control_visibility());
  611. }
  612. protected function sub_test_grade_category_insert_course_category() {
  613. // Beware: adding a duplicate course category messes up the data in a way that's hard to recover from.
  614. $grade_category = new grade_category();
  615. $this->assertTrue(method_exists($grade_category, 'insert_course_category'));
  616. $id = $grade_category->insert_course_category($this->courseid);
  617. $this->assertNotNull($id);
  618. $this->assertEquals('?', $grade_category->fullname);
  619. $this->assertEquals(GRADE_AGGREGATE_WEIGHTED_MEAN2, $grade_category->aggregation);
  620. $this->assertEquals("/$id/", $grade_category->path);
  621. $this->assertEquals(1, $grade_category->depth);
  622. $this->assertNull($grade_category->parent);
  623. }
  624. protected function generate_random_raw_grade($item, $userid) {
  625. $grade = new grade_grade();
  626. $grade->itemid = $item->id;
  627. $grade->userid = $userid;
  628. $grade->grademin = 0;
  629. $grade->grademax = 1;
  630. $valuetype = "grade$item->gradetype";
  631. $grade->rawgrade = rand(0, 1000) / 1000;
  632. $grade->insert();
  633. return $grade->rawgrade;
  634. }
  635. protected function sub_test_grade_category_is_extracredit_used() {
  636. $category = new grade_category();
  637. // Following use aggregationcoef.
  638. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
  639. $this->assertTrue($category->is_extracredit_used());
  640. $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
  641. $this->assertTrue($category->is_extracredit_used());
  642. $category->aggregation = GRADE_AGGREGATE_SUM;
  643. $this->assertTrue($category->is_extracredit_used());
  644. // Following don't use aggregationcoef.
  645. $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
  646. $this->assertFalse($category->is_extracredit_used());
  647. $category->aggregation = GRADE_AGGREGATE_MAX;
  648. $this->assertFalse($category->is_extracredit_used());
  649. $category->aggregation = GRADE_AGGREGATE_MEAN;
  650. $this->assertFalse($category->is_extracredit_used());
  651. $category->aggregation = GRADE_AGGREGATE_MEDIAN;
  652. $this->assertFalse($category->is_extracredit_used());
  653. $category->aggregation = GRADE_AGGREGATE_MIN;
  654. $this->assertFalse($category->is_extracredit_used());
  655. $category->aggregation = GRADE_AGGREGATE_MODE;
  656. $this->assertFalse($category->is_extracredit_used());
  657. }
  658. protected function sub_test_grade_category_aggregation_uses_extracredit() {
  659. $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN2));
  660. $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_EXTRACREDIT_MEAN));
  661. $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_SUM));
  662. $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN));
  663. $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MAX));
  664. $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEAN));
  665. $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEDIAN));
  666. $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MIN));
  667. $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MODE));
  668. }
  669. }