Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

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