MDL-40076 Build entire quiz attempts by supplying attempt
[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);
131 $dataset[] = array($qs, $steps);
132 }
133 return $dataset;
134 }
135
136 /**
137 * Get full path of CSV file.
138 *
139 * @param string $setname
140 * @param string $test
141 * @return string full path of file.
142 */
143 protected function get_full_path_of_csv_file($setname, $test) {
144 return __DIR__."/fixtures/{$setname}{$test}.csv";
145 }
146
147 /**
148 * Load dataset from CSV file "{$setname}{$test}.csv".
149 *
150 * @param string $setname
151 * @param string $test
152 * @return \PHPUnit_Extensions_Database_DataSet_ITable
153 */
154 protected function load_csv_data_file($setname, $test) {
155 $files = array($setname => $this->get_full_path_of_csv_file($setname, $test));
156 return $this->createCsvDataSet($files)->getTable($setname);
157 }
158
159 /**
160 * Create a quiz add questions to it, walk through quiz attempts and then check results.
161 *
162 * @param PHPUnit_Extensions_Database_DataSet_ITable $qs questions to add to quiz, read from csv file "questionsXX.csv".
163 * @param PHPUnit_Extensions_Database_DataSet_ITable $steps steps to simulate, read from csv file "stepsXX.csv".
164 * @dataProvider get_data_for_walkthrough
165 */
166 public function test_walkthrough_from_csv($qs, $steps) {
167 global $DB;
168 $this->resetAfterTest(true);
169 question_bank::get_qtype('random')->clear_caches_before_testing();
170
171 $this->create_quiz($qs);
172
173 for ($rowno = 0; $rowno < $steps->getRowCount(); $rowno++) {
174
175 $step = $this->explode_dot_separated_keys_to_make_subindexs($steps->getRow($rowno));
176 // Find existing user or make a new user to do the quiz.
177 $username = array('firstname' => $step['firstname'],
178 'lastname' => $step['lastname']);
179
180 if (!$user = $DB->get_record('user', $username)) {
181 $user = $this->getDataGenerator()->create_user($username);
182 }
183 $this->setUser($user);
184 // Start the attempt.
185 $quizobj = quiz::create($this->quiz->id, $user->id);
186 $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
187 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
188
189 $timenow = time();
190 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow);
191 // Select variant and / or random sub question.
192 if (!isset($step['variants'])) {
193 $step['variants'] = array();
194 }
195 if (isset($step['randqs'])) {
196 // Replace 'names' with ids.
197 foreach ($step['randqs'] as $slotno => $randqname) {
198 $step['randqs'][$slotno] = $this->randqids[$slotno][$randqname];
199 }
200 } else {
201 $step['randqs'] = array();
202 }
203 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow, $step['randqs'], $step['variants']);
204 quiz_attempt_save_started($quizobj, $quba, $attempt);
205
206 // Process some responses from the student.
207 $attemptobj = quiz_attempt::create($attempt->id);
208 $attemptobj->process_submitted_actions($timenow, false, $step['responses']);
209
210 // Finish the attempt.
211 $attemptobj = quiz_attempt::create($attempt->id);
212 $attemptobj->process_finish($timenow, false);
213
214 // Re-load quiz attempt data.
215 $attemptobj = quiz_attempt::create($attempt->id);
216
217 // Check that results are stored as expected.
218 $this->assertEquals(1, $attemptobj->get_attempt_number());
219 $this->assertEquals(true, $attemptobj->is_finished());
220 $this->assertEquals($timenow, $attemptobj->get_submitted_date());
221 $this->assertEquals($user->id, $attemptobj->get_userid());
222
223 // Check quiz grades.
224 $grades = quiz_get_user_grades($this->quiz, $user->id);
225 $grade = array_shift($grades);
226 $this->assertEquals(100.0, $grade->rawgrade);
227
228 // Check grade book.
229 $gradebookgrades = grade_get_grades($attemptobj->get_courseid(), 'mod', 'quiz', $this->quiz->id, $user->id);
230 $gradebookitem = array_shift($gradebookgrades->items);
231 $gradebookgrade = array_shift($gradebookitem->grades);
232 $this->assertEquals(100, $gradebookgrade->grade);
233 }
234 }
235
236 /**
237 * Break down row of csv data into sub arrays, according to column names.
238 *
239 * @param array $row from csv file with field names with parts separate by '.'.
240 * @return array the row with each part of the field name following a '.' being a separate sub array's index.
241 */
242 protected function explode_dot_separated_keys_to_make_subindexs(array $row) {
243 $parts = array();
244 foreach ($row as $columnkey => $value) {
245 $newkeys = explode('.', trim($columnkey));
246 $placetoputvalue =& $parts;
247 foreach ($newkeys as $newkeydepth => $newkey) {
248 if ($newkeydepth + 1 === count($newkeys)) {
249 $placetoputvalue[$newkey] = $value;
250 } else {
251 // Going deeper down.
252 if (!isset($placetoputvalue[$newkey])) {
253 $placetoputvalue[$newkey] = array();
254 }
255 $placetoputvalue =& $placetoputvalue[$newkey];
256 }
257 }
258 }
259 return $parts;
260 }
261}