MDL-41954 Fix the grade for assessment calculation in the Workshop module
[moodle.git] / mod / workshop / eval / best / tests / lib_test.php
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/>.
17 /**
18  * Unit tests for grading evaluation method "best"
19  *
20  * @package    workshopeval_best
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  */
26 defined('MOODLE_INTERNAL') || die();
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/eval/best/lib.php');
32 require_once($CFG->libdir . '/gradelib.php');
35 class workshopeval_best_evaluation_testcase extends basic_testcase {
37     /** workshop instance emulation */
38     protected $workshop;
40     /** instance of the grading evaluator being tested */
41     protected $evaluator;
43     /**
44      * Setup testing environment
45      */
46     protected function setUp() {
47         parent::setUp();
49         $cm             = new stdclass();
50         $course         = new stdclass();
51         $context        = new stdclass();
52         $workshop       = (object)array('id' => 42, 'evaluation' => 'best');
53         $this->workshop = new workshop($workshop, $cm, $course, $context);
54         $this->evaluator = new testable_workshop_best_evaluation($this->workshop);
55     }
57     protected function tearDown() {
58         $this->workshop = null;
59         $this->evaluator = null;
60         parent::tearDown();
61     }
63     public function test_normalize_grades() {
64         // fixture set-up
65         $assessments = array();
66         $assessments[1] = (object)array(
67             'dimgrades' => array(3 => 1.0000, 4 => 13.42300),
68         );
69         $assessments[3] = (object)array(
70             'dimgrades' => array(3 => 2.0000, 4 => 19.1000),
71         );
72         $assessments[7] = (object)array(
73             'dimgrades' => array(3 => 3.0000, 4 => 0.00000),
74         );
75         $diminfo = array(
76             3 => (object)array('min' => 1, 'max' => 3),
77             4 => (object)array('min' => 0, 'max' => 20),
78         );
79         // exercise SUT
80         $norm = $this->evaluator->normalize_grades($assessments, $diminfo);
81         // validate
82         $this->assertEquals(gettype($norm), 'array');
83         // the following grades from a scale
84         $this->assertEquals($norm[1]->dimgrades[3], 0);
85         $this->assertEquals($norm[3]->dimgrades[3], 50);
86         $this->assertEquals($norm[7]->dimgrades[3], 100);
87         // the following grades from an interval 0 - 20
88         $this->assertEquals($norm[1]->dimgrades[4], grade_floatval(13.423 / 20 * 100));
89         $this->assertEquals($norm[3]->dimgrades[4], grade_floatval(19.1 / 20 * 100));
90         $this->assertEquals($norm[7]->dimgrades[4], 0);
91     }
93     public function test_normalize_grades_max_equals_min() {
94         // fixture set-up
95         $assessments = array();
96         $assessments[1] = (object)array(
97             'dimgrades' => array(3 => 100.0000),
98         );
99         $diminfo = array(
100             3 => (object)array('min' => 100, 'max' => 100),
101         );
102         // exercise SUT
103         $norm = $this->evaluator->normalize_grades($assessments, $diminfo);
104         // validate
105         $this->assertEquals(gettype($norm), 'array');
106         $this->assertEquals($norm[1]->dimgrades[3], 100);
107     }
109     public function test_average_assessment_same_weights() {
110         // fixture set-up
111         $assessments = array();
112         $assessments[18] = (object)array(
113             'weight'        => 1,
114             'dimgrades'     => array(1 => 50, 2 => 33.33333),
115         );
116         $assessments[16] = (object)array(
117             'weight'        => 1,
118             'dimgrades'     => array(1 => 0, 2 => 66.66667),
119         );
120         // exercise SUT
121         $average = $this->evaluator->average_assessment($assessments);
122         // validate
123         $this->assertEquals(gettype($average->dimgrades), 'array');
124         $this->assertEquals(grade_floatval($average->dimgrades[1]), grade_floatval(25));
125         $this->assertEquals(grade_floatval($average->dimgrades[2]), grade_floatval(50));
126     }
128     public function test_average_assessment_different_weights() {
129         // fixture set-up
130         $assessments = array();
131         $assessments[11] = (object)array(
132             'weight'        => 1,
133             'dimgrades'     => array(3 => 10.0, 4 => 13.4, 5 => 95.0),
134         );
135         $assessments[13] = (object)array(
136             'weight'        => 3,
137             'dimgrades'     => array(3 => 11.0, 4 => 10.1, 5 => 92.0),
138         );
139         $assessments[17] = (object)array(
140             'weight'        => 1,
141             'dimgrades'     => array(3 => 11.0, 4 => 8.1, 5 => 88.0),
142         );
143         // exercise SUT
144         $average = $this->evaluator->average_assessment($assessments);
145         // validate
146         $this->assertEquals(gettype($average->dimgrades), 'array');
147         $this->assertEquals(grade_floatval($average->dimgrades[3]), grade_floatval((10.0 + 11.0*3 + 11.0)/5));
148         $this->assertEquals(grade_floatval($average->dimgrades[4]), grade_floatval((13.4 + 10.1*3 + 8.1)/5));
149         $this->assertEquals(grade_floatval($average->dimgrades[5]), grade_floatval((95.0 + 92.0*3 + 88.0)/5));
150     }
152     public function test_average_assessment_noweight() {
153         // fixture set-up
154         $assessments = array();
155         $assessments[11] = (object)array(
156             'weight'        => 0,
157             'dimgrades'     => array(3 => 10.0, 4 => 13.4, 5 => 95.0),
158         );
159         $assessments[17] = (object)array(
160             'weight'        => 0,
161             'dimgrades'     => array(3 => 11.0, 4 => 8.1, 5 => 88.0),
162         );
163         // exercise SUT
164         $average = $this->evaluator->average_assessment($assessments);
165         // validate
166         $this->assertNull($average);
167     }
169     public function test_weighted_variance() {
170         // fixture set-up
171         $assessments[11] = (object)array(
172             'weight'        => 1,
173             'dimgrades'     => array(3 => 11, 4 => 2),
174         );
175         $assessments[13] = (object)array(
176             'weight'        => 3,
177             'dimgrades'     => array(3 => 11, 4 => 4),
178         );
179         $assessments[17] = (object)array(
180             'weight'        => 2,
181             'dimgrades'     => array(3 => 11, 4 => 5),
182         );
183         $assessments[20] = (object)array(
184             'weight'        => 1,
185             'dimgrades'     => array(3 => 11, 4 => 7),
186         );
187         $assessments[25] = (object)array(
188             'weight'        => 1,
189             'dimgrades'     => array(3 => 11, 4 => 9),
190         );
191         // exercise SUT
192         $variance = $this->evaluator->weighted_variance($assessments);
193         // validate
194         // dimension [3] have all the grades equal to 11
195         $this->assertEquals($variance[3], 0);
196         // dimension [4] represents data 2, 4, 4, 4, 5, 5, 7, 9 having stdev=2 (stdev is sqrt of variance)
197         $this->assertEquals($variance[4], 4);
198     }
200     public function test_assessments_distance_zero() {
201         // fixture set-up
202         $diminfo = array(
203             3 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567),
204             4 => (object)array('weight' => 1, 'min' => 1, 'max' => 5,   'variance' => 98.76543),
205         );
206         $assessment1 = (object)array('dimgrades' => array(3 => 15, 4 => 2));
207         $assessment2 = (object)array('dimgrades' => array(3 => 15, 4 => 2));
208         $settings = (object)array('comparison' => 5);
209         // exercise SUT and validate
210         $this->assertEquals($this->evaluator->assessments_distance($assessment1, $assessment2, $diminfo, $settings), 0);
211     }
213     public function test_assessments_distance_equals() {
214         /*
215         // fixture set-up
216         $diminfo = array(
217             3 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567),
218             4 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567),
219         );
220         $assessment1 = (object)array('dimgrades' => array(3 => 25, 4 => 4));
221         $assessment2 = (object)array('dimgrades' => array(3 => 75, 4 => 2));
222         $referential = (object)array('dimgrades' => array(3 => 50, 4 => 3));
223         $settings = (object)array('comparison' => 5);
224         // exercise SUT and validate
225         $this->assertEquals($this->evaluator->assessments_distance($assessment1, $referential, $diminfo, $settings),
226                            $this->evaluator->assessments_distance($assessment2, $referential, $diminfo, $settings));
227         */
228         // fixture set-up
229         $diminfo = array(
230             1 => (object)array('min' => 0, 'max' => 2, 'weight' => 1, 'variance' => 625),
231             2 => (object)array('min' => 0, 'max' => 3, 'weight' => 1, 'variance' => 277.7778888889),
232         );
233         $assessment1 = (object)array('dimgrades' => array(1 => 0,  2 => 66.66667));
234         $assessment2 = (object)array('dimgrades' => array(1 => 50, 2 => 33.33333));
235         $referential = (object)array('dimgrades' => array(1 => 25, 2 => 50));
236         $settings = (object)array('comparison' => 9);
237         // exercise SUT and validate
238         $this->assertEquals($this->evaluator->assessments_distance($assessment1, $referential, $diminfo, $settings),
239             $this->evaluator->assessments_distance($assessment2, $referential, $diminfo, $settings));
241     }
243     public function test_assessments_distance_zero_variance() {
244         // Fixture set-up: an assessment form of the strategy "Number of errors",
245         // three assertions, same weight.
246         $diminfo = array(
247             1 => (object)array('min' => 0, 'max' => 1, 'weight' => 1),
248             2 => (object)array('min' => 0, 'max' => 1, 'weight' => 1),
249             3 => (object)array('min' => 0, 'max' => 1, 'weight' => 1),
250         );
252         // Simulate structure returned by {@link workshop_best_evaluation::prepare_data_from_recordset()}
253         $assessments = array(
254             // The first assessment has weight 0 and the assessment was No, No, No.
255             10 => (object)array(
256                 'assessmentid' => 10,
257                 'weight' => 0,
258                 'reviewerid' => 56,
259                 'gradinggrade' => null,
260                 'submissionid' => 99,
261                 'dimgrades' => array(
262                     1 => 0,
263                     2 => 0,
264                     3 => 0,
265                 ),
266             ),
267             // The second assessment has weight 1 and assessments was Yes, Yes, Yes.
268             20 => (object)array(
269                 'assessmentid' => 20,
270                 'weight' => 1,
271                 'reviewerid' => 76,
272                 'gradinggrade' => null,
273                 'submissionid' => 99,
274                 'dimgrades' => array(
275                     1 => 1,
276                     2 => 1,
277                     3 => 1,
278                 ),
279             ),
280             // The third assessment has weight 1 and assessments was Yes, Yes, Yes too.
281             30 => (object)array(
282                 'assessmentid' => 30,
283                 'weight' => 1,
284                 'reviewerid' => 97,
285                 'gradinggrade' => null,
286                 'submissionid' => 99,
287                 'dimgrades' => array(
288                     1 => 1,
289                     2 => 1,
290                     3 => 1,
291                 ),
292             ),
293         );
295         // Process assessments in the same way as in the {@link workshop_best_evaluation::process_assessments()}
296         $assessments = $this->evaluator->normalize_grades($assessments, $diminfo);
297         $average = $this->evaluator->average_assessment($assessments);
298         $variances = $this->evaluator->weighted_variance($assessments);
299         foreach ($variances as $dimid => $variance) {
300             $diminfo[$dimid]->variance = $variance;
301         }
303         // Simulate the chosen comparison of assessments "fair" (does not really matter here but we need something).
304         $settings = (object)array('comparison' => 5);
306         // Exercise SUT: for every assessment, calculate its distance from the average one.
307         $distances = array();
308         foreach ($assessments as $asid => $assessment) {
309             $distances[$asid] = $this->evaluator->assessments_distance($assessment, $average, $diminfo, $settings);
310         }
312         // Validate: the first assessment is far far away from the average one ...
313         $this->assertTrue($distances[10] > 0);
314         // ... while the two others were both picked as the referential ones.
315         $this->assertTrue($distances[20] == 0);
316         $this->assertTrue($distances[30] == 0);
317     }
321 /**
322  * Test subclass that makes all the protected methods we want to test public.
323  */
324 class testable_workshop_best_evaluation extends workshop_best_evaluation {
326     public function normalize_grades(array $assessments, array $diminfo) {
327         return parent::normalize_grades($assessments, $diminfo);
328     }
329     public function average_assessment(array $assessments) {
330         return parent::average_assessment($assessments);
331     }
332     public function weighted_variance(array $assessments) {
333         return parent::weighted_variance($assessments);
334     }
335     public function assessments_distance(stdclass $assessment, stdclass $referential, array $diminfo, stdclass $settings) {
336         return parent::assessments_distance($assessment, $referential, $diminfo, $settings);
337     }