MDL-40077 quiz stats : Test stats as expected for csv file responses
authorJamie Pratt <me@jamiep.org>
Fri, 9 Aug 2013 06:39:44 +0000 (13:39 +0700)
committerJamie Pratt <me@jamiep.org>
Sat, 10 Aug 2013 04:17:47 +0000 (11:17 +0700)
mod/quiz/report/statistics/report.php
mod/quiz/report/statistics/tests/fixtures/qstats00.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/fixtures/questions00.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/fixtures/results00.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/fixtures/steps00.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php [new file with mode: 0644]
mod/quiz/tests/attempt_walkthrough_from_csv_test.php

index d781d85..f30eca2 100644 (file)
@@ -122,21 +122,7 @@ class quiz_statistics_report extends quiz_default_report {
         $filename = quiz_report_download_filename($report, $courseshortname, $quiz->name);
         $this->table->is_downloading($download, $filename,
                 get_string('quizstructureanalysis', 'quiz_statistics'));
-
-        // Load the questions.
-        $questions = quiz_report_get_significant_questions($quiz);
-        $questionids = array();
-        foreach ($questions as $question) {
-            $questionids[] = $question->id;
-        }
-        $fullquestions = question_load_questions($questionids);
-        foreach ($questions as $qno => $question) {
-            $q = $fullquestions[$question->id];
-            $q->maxmark = $question->maxmark;
-            $q->slot = $qno;
-            $q->number = $question->number;
-            $questions[$qno] = $q;
-        }
+        $questions = $this->load_and_initialise_questions_for_calculations($quiz);
 
         // Get the data to be displayed.
         list($quizstats, $questions, $subquestions, $s) =
@@ -607,7 +593,7 @@ class quiz_statistics_report extends quiz_default_report {
     /**
      * Compute the quiz statistics.
      *
-     * @param object $quizid the quiz id.
+     * @param int $quizid the quiz id.
      * @param int $currentgroup the current group. 0 for none.
      * @param bool $nostudentsingroup true if there a no students.
      * @param bool $useallattempts use all attempts, or just first attempts.
@@ -1057,6 +1043,28 @@ class quiz_statistics_report extends quiz_default_report {
             return get_string('firstattempts', 'quiz_statistics');
         }
     }
+
+    /**
+     * @param object $quiz the quiz.
+     * @return array of questions for this quiz.
+     */
+    public function load_and_initialise_questions_for_calculations($quiz) {
+        // Load the questions.
+        $questions = quiz_report_get_significant_questions($quiz);
+        $questionids = array();
+        foreach ($questions as $question) {
+            $questionids[] = $question->id;
+        }
+        $fullquestions = question_load_questions($questionids);
+        foreach ($questions as $qno => $question) {
+            $q = $fullquestions[$question->id];
+            $q->maxmark = $question->maxmark;
+            $q->slot = $qno;
+            $q->number = $question->number;
+            $questions[$qno] = $q;
+        }
+        return $questions;
+    }
 }
 
 function quiz_statistics_attempts_sql($quizid, $currentgroup, $groupstudents,
diff --git a/mod/quiz/report/statistics/tests/fixtures/qstats00.csv b/mod/quiz/report/statistics/tests/fixtures/qstats00.csv
new file mode 100644 (file)
index 0000000..1ba861e
--- /dev/null
@@ -0,0 +1,8 @@
+slot,facility,sd,effectiveweight,covariance,markvariance,othermarkvariance,discriminationindex,covariancemax,discriminativeefficiency
+1,0.704,0.4513682901,21.2922742344,-0.022555556,0.2037333333,0.5002777794,-7.0650767526,0.2385555565,-9.4550536967
+2,0.48,0.5099019514,18.8979800309,-0.1172777785,0.26,0.6334555578,-28.8982125772,0.318833334,-36.7834118938
+3,0.973333332,0.13333334,4.443012573,-0.0098888894,0.0177777796,0.6609,-9.1230674268,0.045666669,-21.6545012165
+4,0.68,0.4760952286,18.9347251357,-0.0833888893,0.2266666667,0.5990111128,-22.6306444113,0.2652222232,-31.4411395613
+5,0.52,0.3055050463,11.1450138688,-0.0436944444,0.0933333333,0.6529555563,-17.6997047674,0.2063055556,-21.1794802584
+6,0.64,0.4898979486,9.8081339177,-0.2015555547,0.24,0.8220111101,-45.3785178421,0.3539999995,-56.9365974439
+7,0.62,0.331662479,15.4788602394,-0.0142499998,0.11,0.5774000005,-5.6543166602,0.2190833335,-6.5043742058
diff --git a/mod/quiz/report/statistics/tests/fixtures/questions00.csv b/mod/quiz/report/statistics/tests/fixtures/questions00.csv
new file mode 100644 (file)
index 0000000..8787dee
--- /dev/null
@@ -0,0 +1,10 @@
+slot,type,which,cat,mark
+1,random,,rand,1
+,shortanswer,,rand,1
+,numerical,,rand,1
+2,calculatedsimple,sumwithvariants,maincat,1
+3,match,,maincat,1
+4,truefalse,,maincat,1
+5,multichoice,two_of_four,maincat,1
+6,multichoice,one_of_four,maincat,1
+7,multianswer,,maincat,1
diff --git a/mod/quiz/report/statistics/tests/fixtures/results00.csv b/mod/quiz/report/statistics/tests/fixtures/results00.csv
new file mode 100644 (file)
index 0000000..c917abb
--- /dev/null
@@ -0,0 +1,26 @@
+quizattempt,slots.1.mark,slots.2.mark,slots.3.mark,slots.4.mark,slots.5.mark,slots.6.mark,slots.7.mark,summarks
+1,1,1,1,1,1,0,1,6
+2,1,1,1,1,0.5,1,0,5.5
+3,1,1,1,1,0.5,0,1,5.5
+4,0,0,1,1,1,0,0,3
+5,1,0,1,0,0,1,0.5,3.5
+6,1,1,1,1,0.5,0,0.5,5
+7,0,1,1,1,0.5,1,0.5,5
+8,1,1,1,0,0.5,1,0.5,5
+9,1,0,1,1,1,1,1,6
+10,0,1,1,0,0.5,1,0.5,4
+11,0.8,0,1,1,0.5,1,0.5,4.8
+12,1,1,1,0,0.5,1,1,5.5
+13,1,0,1,1,0.5,1,0.5,5
+14,1,0,1,1,1,0,0.5,4.5
+15,0,0,1,1,0,1,1,4
+16,1,0,1,0,0.5,1,1,4.5
+17,0.8,0,1,1,0.5,0,1,4.3
+18,1,0,1,1,0,1,0.5,4.5
+19,1,0,1,1,0.5,0,0.5,4
+20,0,1,1,0,0.5,1,0,3.5
+21,1,1,0.3333333,1,0.5,0,0.5,4.33333
+22,1,1,1,0,0.5,0,0.5,4
+23,1,1,1,1,0,1,0.5,5.5
+24,0,0,1,0,0.5,1,1,3.5
+25,0,0,1,1,1,1,1,5
diff --git a/mod/quiz/report/statistics/tests/fixtures/steps00.csv b/mod/quiz/report/statistics/tests/fixtures/steps00.csv
new file mode 100644 (file)
index 0000000..aeb0070
--- /dev/null
@@ -0,0 +1,26 @@
+quizattempt,firstname,lastname,randqs.1,responses.1.answer,variants.2,responses.2.answer,responses.3.0,responses.3.1,responses.3.2,responses.4.answer,responses.5.0,responses.5.1,responses.5.2,responses.5.3,responses.6.answer,responses.7.1.answer,responses.7.2.answer
+1,John,Jones,numerical,3.14,1,9.9,amphibian,mammal,amphibian,1,1,0,1,0,1,Owl,2
+2,John,Smith,shortanswer,frog,1,9.9,amphibian,mammal,amphibian,1,0,1,1,0,0,Dog,0
+3,John,Vicars,numerical,3.14,6,9.4,amphibian,mammal,amphibian,1,0,0,1,1,1,Owl,2
+4,John,Pacino,shortanswer,butterfly,6,-0.1,amphibian,mammal,amphibian,1,1,0,1,0,3,Dog,0
+5,John,Deniro,numerical,3.14,4,0,amphibian,mammal,amphibian,0,0,1,0,1,0,Dog,2
+6,John,Banks,numerical,3.14,1,9.9,amphibian,mammal,amphibian,1,1,0,0,1,1,Owl,1
+7,John,Asimov,numerical,3.142,7,9.1,amphibian,mammal,amphibian,1,0,0,1,1,0,Owl,1
+8,John,Chomsky,numerical,3.14,4,19.4,amphibian,mammal,amphibian,0,0,0,1,1,0,Owl,1
+9,John,Yamaguchi,shortanswer,frog,1,-0.7,amphibian,mammal,amphibian,1,1,0,1,0,0,Owl,2
+10,John,Robbins,numerical,3.1,5,14.2,amphibian,mammal,amphibian,0,0,1,1,0,0,Owl,0
+11,Joe,Jones,shortanswer,toad,6,-0.2,amphibian,mammal,amphibian,1,1,1,0,0,0,Owl,0
+12,Joe,Smith,shortanswer,frog,8,5.7,amphibian,mammal,amphibian,0,0,0,1,1,0,Owl,2
+13,Joe,Vicars,numerical,3.14,8,-0.2,amphibian,mammal,amphibian,1,1,0,0,1,0,wfz9p,2
+14,Joe,Pacino,shortanswer,frog,1,-0.2,amphibian,mammal,amphibian,1,1,0,1,0,1,Owl,0
+15,Joe,Deniro,numerical,3.1,7,-0.9,amphibian,mammal,amphibian,1,0,1,0,1,0,Owl,2
+16,Joe,Banks,shortanswer,frog,10,-0.7,amphibian,mammal,amphibian,0,1,0,0,1,0,Owl,2
+17,Joe,Asimov,shortanswer,toad,4,-0.4,amphibian,mammal,amphibian,1,0,1,1,0,2,Owl,2
+18,Joe,Chomsky,shortanswer,frog,6,-1,amphibian,mammal,amphibian,1,0,1,0,1,0,Pussy-cat,2
+19,Joe,Yamaguchi,numerical,3.14,8,-0.5,amphibian,mammal,amphibian,1,1,1,0,0,1,Owl,0
+20,Joe,Robbins,shortanswer,butterfly,4,19.4,amphibian,mammal,amphibian,0,0,1,1,0,0,"Wiggly worm",0
+21,Roberto,Jones,shortanswer,frog,8,5.7,amphibian,amphibian,mammal,1,0,1,1,0,2,RjUpn,2
+22,Roberto,Smith,shortanswer,frog,5,14.2,amphibian,mammal,amphibian,0,1,0,0,1,1,Dog,2
+23,Roberto,Vicars,shortanswer,frog,5,14.2,amphibian,mammal,amphibian,1,0,1,0,1,0,"Wiggly worm",2
+24,Roberto,Pacino,numerical,3.1,1,-1,amphibian,mammal,amphibian,0,1,1,0,0,0,Owl,2
+25,Roberto,Deniro,numerical,3.1,8,-0.1,amphibian,mammal,amphibian,1,1,0,1,0,0,Owl,2
diff --git a/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php b/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
new file mode 100644 (file)
index 0000000..5fdc453
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Quiz attempt walk through using data from csv file.
+ *
+ * @package    mod_quiz
+ * @category   phpunit
+ * @copyright  2013 The Open University
+ * @author     Jamie Pratt <me@jamiep.org>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/quiz/tests/attempt_walkthrough_from_csv_test.php');
+require_once($CFG->dirroot . '/mod/quiz/report/default.php');
+require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
+require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
+/**
+ * Test helper subclass of quiz_statistics_report
+ *
+ * @copyright  2013 The Open University
+ * @author     Jamie Pratt <me@jamiep.org>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testable_quiz_statistics_report extends quiz_statistics_report {
+
+    public function get_stats($quiz, $useallattempts = true,
+                              $currentgroup = 0, $groupstudents = array(), $nostudentsingroup = false) {
+        $this->clear_cached_data($quiz->id, $currentgroup, $useallattempts);
+        $questions = $this->load_and_initialise_questions_for_calculations($quiz);
+        return $this->get_quiz_and_questions_stats($quiz, $currentgroup, $nostudentsingroup,
+                                                   $useallattempts, $groupstudents, $questions);
+    }
+}
+
+/**
+ * Quiz attempt walk through using data from csv file.
+ *
+ * @package    mod_quiz
+ * @category   phpunit
+ * @copyright  2013 The Open University
+ * @author     Jamie Pratt <me@jamiep.org>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_from_csv_testcase {
+
+    /**
+     * @var quiz_statistics_report object to do stats calculations.
+     */
+    protected $report;
+
+    protected function get_full_path_of_csv_file($setname, $test) {
+        // Overridden here so that __DIR__ points to the path of this file.
+        return  __DIR__."/fixtures/{$setname}{$test}.csv";
+    }
+
+    protected $files = array('questions', 'steps', 'results', 'qstats');
+
+    /**
+     * Create a quiz add questions to it, walk through quiz attempts and then check results.
+     *
+     * @param PHPUnit_Extensions_Database_DataSet_ITable[] of data read from csv file "questionsXX.csv",
+     *                                                                                  "stepsXX.csv" and "resultsXX.csv".
+     * @dataProvider get_data_for_walkthrough
+     */
+    public function test_walkthrough_from_csv($csvdata) {
+
+        // CSV data files for these tests were generated using :
+        // https://github.com/jamiepratt/moodle-quiz-tools/tree/master/responsegenerator
+
+        $this->resetAfterTest(true);
+        question_bank::get_qtype('random')->clear_caches_before_testing();
+
+        $this->create_quiz($csvdata['questions']);
+
+        $attemptids = $this->walkthrough_attempts($csvdata['steps']);
+
+        $this->check_attempts_results($csvdata['results'], $attemptids);
+
+        $this->report = new testable_quiz_statistics_report();
+        list($quizstats, $questions, $subquestions, $s) = $this->report->get_stats($this->quiz);
+
+        // These quiz stats and the question stats found in qstats00.csv were calculated independently in spreadsheet which is
+        // available in open document or excel format here :
+        // https://github.com/jamiepratt/moodle-quiz-tools/tree/master/statsspreadsheet
+        $quizstatsexpected = array(
+            'median' => 4.5,
+            'firstattemptsavg' => 4.617333332,
+            'allattemptsavg' => 4.617333332,
+            'firstattemptscount' => 25,
+            'allattemptscount' => 25,
+            'standarddeviation' => 0.8117265554,
+            'skewness' => -0.092502502,
+            'kurtosis' => -0.7073968557,
+            'cic' => -87.2230935542,
+            'errorratio' => 136.8294900795,
+            'standarderror' => 1.1106813066
+        );
+
+        foreach ($quizstatsexpected as $statname => $statvalue) {
+            $this->assertEquals($statvalue, $quizstats->$statname, $quizstats->$statname, abs($statvalue) * 1e-5);
+        }
+
+        for ($rowno = 0; $rowno < $csvdata['qstats']->getRowCount(); $rowno++) {
+            $slotqstats = $csvdata['qstats']->getRow($rowno);
+            foreach ($slotqstats as $statname => $slotqstat) {
+                if ($statname !== 'slot') {
+                    switch ($statname) {
+                        case 'covariance' :
+                        case 'discriminationindex' :
+                        case 'discriminativeefficiency' :
+                        case 'effectiveweight' :
+                            $precision = 1e-5;
+                            break;
+                        default :
+                            $precision = 1e-6;
+                    }
+                    $slot = $slotqstats['slot'];
+                    $delta = abs($slotqstat) * $precision;
+                    $actual = $questions[$slot]->_stats->{$statname};
+                    $this->assertEquals(floatval($slotqstat), $actual, "$statname for slot $slot", $delta);
+                }
+            }
+        }
+    }
+}
index 553e021..d88a48d 100644 (file)
@@ -46,6 +46,8 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
      */
     protected $tests = array('00');
 
+    protected $files = array('questions', 'steps', 'results');
+
     /**
      * @var stdClass the quiz record we create.
      */
@@ -123,17 +125,6 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
         }
     }
 
-    public function get_data_for_walkthrough() {
-        $dataset = array();
-        foreach ($this->tests as $test) {
-            $qs = $this->load_csv_data_file('questions', $test);
-            $steps = $this->load_csv_data_file('steps', $test);
-            $results = $this->load_csv_data_file('results', $test);
-            $dataset[] = array($qs, $steps, $results);
-        }
-        return $dataset;
-    }
-
     /**
      * Get full path of CSV file.
      *
@@ -183,28 +174,59 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
         return $parts;
     }
 
+    /**
+     * Data provider method for test_walkthrough_from_csv. Called by PHPUnit.
+     *
+     * @return array One array element for each run of the test. Each element contains an array with the one param for
+     *                  test_walkthrough_from_csv.
+     */
+    public function get_data_for_walkthrough() {
+        $datasets = array();
+        foreach ($this->tests as $test) {
+            $dataset = array();
+            foreach ($this->files as $file) {
+                $dataset[$file] = $this->load_csv_data_file($file, $test);
+            }
+            $datasets[] = array($dataset);
+        }
+        return $datasets;
+    }
+
     /**
      * Create a quiz add questions to it, walk through quiz attempts and then check results.
      *
-     * @param PHPUnit_Extensions_Database_DataSet_ITable $qs questions to add to quiz, read from csv file "questionsXX.csv".
-     * @param PHPUnit_Extensions_Database_DataSet_ITable $steps steps to simulate, read from csv file "stepsXX.csv".
-     * @param PHPUnit_Extensions_Database_DataSet_ITable $results results expected, read from csv file "resultsXX.csv".
+     * @param PHPUnit_Extensions_Database_DataSet_ITable[] of data read from csv file "questionsXX.csv",
+     *                                                                                  "stepsXX.csv" and "resultsXX.csv".
      * @dataProvider get_data_for_walkthrough
      */
-    public function test_walkthrough_from_csv($qs, $steps, $results) {
-        global $DB;
+    public function test_walkthrough_from_csv($csvdata) {
+
+        // CSV data files for these tests were generated using :
+        // https://github.com/jamiepratt/moodle-quiz-tools/tree/master/responsegenerator
+
         $this->resetAfterTest(true);
         question_bank::get_qtype('random')->clear_caches_before_testing();
 
-        $this->create_quiz($qs);
+        $this->create_quiz($csvdata['questions']);
+
+        $attemptids = $this->walkthrough_attempts($csvdata['steps']);
 
+        $this->check_attempts_results($csvdata['results'], $attemptids);
+    }
+
+    /**
+     * @param $steps PHPUnit_Extensions_Database_DataSet_ITable the step data from the csv file.
+     * @return array attempt no as in csv file => the id of the quiz_attempt as stored in the db.
+     */
+    protected function walkthrough_attempts($steps) {
+        global $DB;
         $attemptids = array();
         for ($rowno = 0; $rowno < $steps->getRowCount(); $rowno++) {
 
             $step = $this->explode_dot_separated_keys_to_make_subindexs($steps->getRow($rowno));
             // Find existing user or make a new user to do the quiz.
             $username = array('firstname' => $step['firstname'],
-                              'lastname' => $step['lastname']);
+                              'lastname'  => $step['lastname']);
 
             if (!$user = $DB->get_record('user', $username)) {
                 $user = $this->getDataGenerator()->create_user($username);
@@ -242,15 +264,20 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
             $attemptobj = quiz_attempt::create($attempt->id);
             $attemptobj->process_finish($timenow, false);
 
-
         }
+        return $attemptids;
+    }
 
+    /**
+     * @param $results PHPUnit_Extensions_Database_DataSet_ITable the results data from the csv file.
+     * @param $attemptids array attempt no as in csv file => the id of the quiz_attempt as stored in the db.
+     */
+    protected function check_attempts_results($results, $attemptids) {
         for ($rowno = 0; $rowno < $results->getRowCount(); $rowno++) {
             $result = $this->explode_dot_separated_keys_to_make_subindexs($results->getRow($rowno));
             // Re-load quiz attempt data.
             $attemptobj = quiz_attempt::create($attemptids[$result['quizattempt']]);
             $this->check_attempt_results($result, $attemptobj);
-
         }
     }
 
@@ -279,7 +306,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
                                     break;
                                 default :
                                     throw new coding_exception('Unknown slots sub field column in csv file '
-                                                                   .s($slotfieldname));
+                                                               .s($slotfieldname));
                             }
                         }
                     }