weekly release 2.6dev
[moodle.git] / mod / quiz / tests / attempt_walkthrough_from_csv_test.php
CommitLineData
da6bb0c5
JP
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 * Quiz attempt walk through using data from csv file.
19 *
20 * @package mod_quiz
21 * @category phpunit
22 * @copyright 2013 The Open University
23 * @author Jamie Pratt <me@jamiep.org>
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
27defined('MOODLE_INTERNAL') || die();
28
29global $CFG;
30require_once($CFG->dirroot . '/mod/quiz/editlib.php');
31require_once($CFG->dirroot . '/mod/quiz/locallib.php');
32
33/**
34 * Quiz attempt walk through using data from csv file.
35 *
36 * @package mod_quiz
37 * @category phpunit
38 * @copyright 2013 The Open University
39 * @author Jamie Pratt <me@jamiep.org>
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 */
42class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
43
44 /**
45 * @var array postfix number for sets of csv files to load data from.
46 */
47 protected $tests = array('00');
48
49 /**
50 * @var stdClass the quiz record we create.
51 */
52 protected $quiz;
53
54 /**
55 * @var array with slot no => question name => questionid. Question ids of questions created in the same category as random q.
56 */
57 protected $randqids;
58
59 public function create_quiz($qs) {
60 global $SITE, $DB;
61 $this->setAdminUser();
62
63 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
64 $slots = array();
65 $qidsbycat = array();
66 $sumofgrades = 0;
67 for ($rowno = 0; $rowno < $qs->getRowCount(); $rowno++) {
68 $q = $this->explode_dot_separated_keys_to_make_subindexs($qs->getRow($rowno));
69
70 $catname = array('name' => $q['cat']);
71 if (!$cat = $DB->get_record('question_categories', array('name' => $q['cat']))) {
72 $cat = $questiongenerator->create_question_category($catname);
73 }
74 $q['catid'] = $cat->id;
75 foreach (array('which' => null, 'overrides' => array()) as $key => $default) {
76 if (empty($q[$key])) {
77 $q[$key] = $default;
78 }
79 }
80
81 if ($q['type'] !== 'random') {
82 // Don't actually create random questions here.
83 $overrides = array('category' => $cat->id, 'defaultmark' => $q['mark']) + $q['overrides'];
84 $question = $questiongenerator->create_question($q['type'], $q['which'], $overrides);
85 $q['id'] = $question->id;
86
87 if (!isset($qidsbycat[$q['cat']])) {
88 $qidsbycat[$q['cat']] = array();
89 }
90 if (!empty($q['which'])) {
91 $name = $q['type'].'_'.$q['which'];
92 } else {
93 $name = $q['type'];
94 }
95 $qidsbycat[$q['catid']][$name] = $q['id'];
96 }
97 if (!empty($q['slot'])) {
98 $slots[$q['slot']] = $q;
99 $sumofgrades += $q['mark'];
100 }
101 }
102
103 ksort($slots);
104
105 // Make a quiz.
106 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
107 $this->quiz = $quizgenerator->create_instance(array('course'=>$SITE->id,
108 'questionsperpage' => 0,
109 'grade' => 100.0,
110 'sumgrades' => $sumofgrades));
111
112 $this->randqids = array();
113 foreach ($slots as $slotno => $slotquestion) {
114 if ($slotquestion['type'] !== 'random') {
115 quiz_add_quiz_question($slotquestion['id'], $this->quiz, 0);
116 // Setting default mark above does not affect the grade for multi-answer question type (and maybe others??).
117 // Set the mark again just to be sure.
118 quiz_update_question_instance($slotquestion['mark'], $slotquestion['id'], $this->quiz);
119 } else {
120 quiz_add_random_questions($this->quiz, 0, $slotquestion['catid'], 1, 0);
121 $this->randqids[$slotno] = $qidsbycat[$slotquestion['catid']];
122 }
123 }
124 }
125
126 public function get_data_for_walkthrough() {
127 $dataset = array();
128 foreach ($this->tests as $test) {
129 $qs = $this->load_csv_data_file('questions', $test);
130 $steps = $this->load_csv_data_file('steps', $test);
e39a2faa
JP
131 $results = $this->load_csv_data_file('results', $test);
132 $dataset[] = array($qs, $steps, $results);
da6bb0c5
JP
133 }
134 return $dataset;
135 }
136
137 /**
138 * Get full path of CSV file.
139 *
140 * @param string $setname
141 * @param string $test
142 * @return string full path of file.
143 */
144 protected function get_full_path_of_csv_file($setname, $test) {
145 return __DIR__."/fixtures/{$setname}{$test}.csv";
146 }
147
148 /**
149 * Load dataset from CSV file "{$setname}{$test}.csv".
150 *
151 * @param string $setname
152 * @param string $test
153 * @return \PHPUnit_Extensions_Database_DataSet_ITable
154 */
155 protected function load_csv_data_file($setname, $test) {
156 $files = array($setname => $this->get_full_path_of_csv_file($setname, $test));
157 return $this->createCsvDataSet($files)->getTable($setname);
158 }
159
e39a2faa
JP
160 /**
161 * Break down row of csv data into sub arrays, according to column names.
162 *
163 * @param array $row from csv file with field names with parts separate by '.'.
164 * @return array the row with each part of the field name following a '.' being a separate sub array's index.
165 */
166 protected function explode_dot_separated_keys_to_make_subindexs(array $row) {
167 $parts = array();
168 foreach ($row as $columnkey => $value) {
169 $newkeys = explode('.', trim($columnkey));
170 $placetoputvalue =& $parts;
171 foreach ($newkeys as $newkeydepth => $newkey) {
172 if ($newkeydepth + 1 === count($newkeys)) {
173 $placetoputvalue[$newkey] = $value;
174 } else {
175 // Going deeper down.
176 if (!isset($placetoputvalue[$newkey])) {
177 $placetoputvalue[$newkey] = array();
178 }
179 $placetoputvalue =& $placetoputvalue[$newkey];
180 }
181 }
182 }
183 return $parts;
184 }
185
da6bb0c5
JP
186 /**
187 * Create a quiz add questions to it, walk through quiz attempts and then check results.
188 *
189 * @param PHPUnit_Extensions_Database_DataSet_ITable $qs questions to add to quiz, read from csv file "questionsXX.csv".
190 * @param PHPUnit_Extensions_Database_DataSet_ITable $steps steps to simulate, read from csv file "stepsXX.csv".
e39a2faa 191 * @param PHPUnit_Extensions_Database_DataSet_ITable $results results expected, read from csv file "resultsXX.csv".
da6bb0c5
JP
192 * @dataProvider get_data_for_walkthrough
193 */
e39a2faa 194 public function test_walkthrough_from_csv($qs, $steps, $results) {
da6bb0c5
JP
195 global $DB;
196 $this->resetAfterTest(true);
197 question_bank::get_qtype('random')->clear_caches_before_testing();
198
199 $this->create_quiz($qs);
200
e39a2faa 201 $attemptids = array();
da6bb0c5
JP
202 for ($rowno = 0; $rowno < $steps->getRowCount(); $rowno++) {
203
204 $step = $this->explode_dot_separated_keys_to_make_subindexs($steps->getRow($rowno));
205 // Find existing user or make a new user to do the quiz.
206 $username = array('firstname' => $step['firstname'],
207 'lastname' => $step['lastname']);
208
209 if (!$user = $DB->get_record('user', $username)) {
210 $user = $this->getDataGenerator()->create_user($username);
211 }
212 $this->setUser($user);
213 // Start the attempt.
214 $quizobj = quiz::create($this->quiz->id, $user->id);
215 $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
216 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
217
218 $timenow = time();
219 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow);
220 // Select variant and / or random sub question.
221 if (!isset($step['variants'])) {
222 $step['variants'] = array();
223 }
224 if (isset($step['randqs'])) {
225 // Replace 'names' with ids.
226 foreach ($step['randqs'] as $slotno => $randqname) {
227 $step['randqs'][$slotno] = $this->randqids[$slotno][$randqname];
228 }
229 } else {
230 $step['randqs'] = array();
231 }
232 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow, $step['randqs'], $step['variants']);
233 quiz_attempt_save_started($quizobj, $quba, $attempt);
234
e39a2faa
JP
235 $attemptids[$step['quizattempt']] = $attempt->id;
236
da6bb0c5
JP
237 // Process some responses from the student.
238 $attemptobj = quiz_attempt::create($attempt->id);
239 $attemptobj->process_submitted_actions($timenow, false, $step['responses']);
240
241 // Finish the attempt.
242 $attemptobj = quiz_attempt::create($attempt->id);
243 $attemptobj->process_finish($timenow, false);
244
e39a2faa
JP
245
246 }
247
248 for ($rowno = 0; $rowno < $results->getRowCount(); $rowno++) {
249 $result = $this->explode_dot_separated_keys_to_make_subindexs($results->getRow($rowno));
da6bb0c5 250 // Re-load quiz attempt data.
e39a2faa
JP
251 $attemptobj = quiz_attempt::create($attemptids[$result['quizattempt']]);
252 $this->check_attempt_results($result, $attemptobj);
da6bb0c5 253
da6bb0c5
JP
254 }
255 }
256
257 /**
e39a2faa 258 * Check that attempt results are as specified in $result.
da6bb0c5 259 *
e39a2faa
JP
260 * @param array $result row of data read from csv file.
261 * @param quiz_attempt $attemptobj the attempt object loaded from db.
262 * @throws coding_exception
da6bb0c5 263 */
e39a2faa
JP
264 protected function check_attempt_results($result, $attemptobj) {
265 foreach ($result as $fieldname => $value) {
266 switch ($fieldname) {
267 case 'quizattempt' :
268 break;
269 case 'attemptnumber' :
270 $this->assertEquals($value, $attemptobj->get_attempt_number());
271 break;
272 case 'slots' :
273 foreach ($value as $slotno => $slottests) {
274 foreach ($slottests as $slotfieldname => $slotvalue) {
275 switch ($slotfieldname) {
276 case 'mark' :
277 $this->assertEquals(round($slotvalue, 2), $attemptobj->get_question_mark($slotno),
278 "Mark for slot $slotno of attempt {$result['quizattempt']}.");
279 break;
280 default :
281 throw new coding_exception('Unknown slots sub field column in csv file '
282 .s($slotfieldname));
283 }
284 }
da6bb0c5 285 }
e39a2faa
JP
286 break;
287 case 'finished' :
288 $this->assertEquals($value, $attemptobj->is_finished());
289 break;
290 case 'summarks' :
291 $this->assertEquals($value, $attemptobj->get_sum_marks(), "Sum of marks of attempt {$result['quizattempt']}.");
292 break;
293 case 'quizgrade' :
294 // Check quiz grades.
295 $grades = quiz_get_user_grades($attemptobj->get_quiz(), $attemptobj->get_userid());
296 $grade = array_shift($grades);
297 $this->assertEquals($value, $grade->rawgrade, "Quiz grade for attempt {$result['quizattempt']}.");
298 break;
299 case 'gradebookgrade' :
300 // Check grade book.
301 $gradebookgrades = grade_get_grades($attemptobj->get_courseid(),
302 'mod', 'quiz',
303 $attemptobj->get_quizid(),
304 $attemptobj->get_userid());
305 $gradebookitem = array_shift($gradebookgrades->items);
306 $gradebookgrade = array_shift($gradebookitem->grades);
307 $this->assertEquals($value, $gradebookgrade->grade, "Gradebook grade for attempt {$result['quizattempt']}.");
308 break;
309 default :
310 throw new coding_exception('Unknown column in csv file '.s($fieldname));
da6bb0c5
JP
311 }
312 }
da6bb0c5
JP
313 }
314}