|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 require_once(dirname(__FILE__) . '/../lib.php'); 00030 require_once(dirname(__FILE__) . '/helpers.php'); 00031 00032 00040 class testable_question_engine_unit_of_work extends question_engine_unit_of_work { 00041 public function get_modified() { 00042 return $this->modified; 00043 } 00044 00045 public function get_attempts_added() { 00046 return $this->attemptsadded; 00047 } 00048 00049 public function get_attempts_modified() { 00050 return $this->attemptsmodified; 00051 } 00052 00053 public function get_steps_added() { 00054 return $this->stepsadded; 00055 } 00056 00057 public function get_steps_modified() { 00058 return $this->stepsmodified; 00059 } 00060 00061 public function get_steps_deleted() { 00062 return $this->stepsdeleted; 00063 } 00064 } 00065 00066 00073 class question_engine_unit_of_work_test extends data_loading_method_test_base { 00075 protected $quba; 00076 00078 protected $slot; 00079 00081 protected $observer; 00082 00083 public function setUp() { 00084 // Create a usage in an initial state, with one shortanswer question added, 00085 // and attempted in interactive mode submitted responses 'toad' then 'frog'. 00086 // Then set it to use a new unit of work for any subsequent changes. 00087 // Create a short answer question. 00088 $question = test_question_maker::make_question('shortanswer'); 00089 $question->hints = array( 00090 new question_hint(0, 'This is the first hint.', FORMAT_HTML), 00091 new question_hint(0, 'This is the second hint.', FORMAT_HTML), 00092 ); 00093 $question->id = -1; 00094 question_bank::start_unit_test(); 00095 question_bank::load_test_question_data($question); 00096 00097 $this->setup_initial_test_state($this->get_test_data()); 00098 } 00099 00100 public function testDown() { 00101 question_bank::end_unit_test(); 00102 } 00103 00104 protected function setup_initial_test_state($testdata) { 00105 $records = new test_recordset($testdata); 00106 00107 $this->quba = question_usage_by_activity::load_from_records($records, 1); 00108 00109 $this->slot = 1; 00110 $this->observer = new testable_question_engine_unit_of_work($this->quba); 00111 $this->quba->set_observer($this->observer); 00112 } 00113 00114 protected function get_test_data() { 00115 return array( 00116 array('qubaid', 'contextid', 'component', 'preferredbehaviour', 00117 'questionattemptid', 'contextid', 'questionusageid', 'slot', 00118 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged', 00119 'questionsummary', 'rightanswer', 'responsesummary', 'timemodified', 00120 'attemptstepid', 'sequencenumber', 'state', 'fraction', 00121 'timecreated', 'userid', 'name', 'value'), 00122 array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, '-_triesleft', 3), 00123 array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, 'answer', 'toad'), 00124 array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-submit', 1), 00125 array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-_triesleft', 1), 00126 array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 3, 2, 'todo', null, 1256233740, 1, '-tryagain', 1), 00127 array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', null, 1256233790, 1, 'answer', 'frog'), 00128 array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 1.0000000, 1256233790, 1, '-finish', 1), 00129 ); 00130 } 00131 00132 public function test_initial_state() { 00133 $this->assertFalse($this->observer->get_modified()); 00134 $this->assertEqual(0, count($this->observer->get_attempts_added())); 00135 $this->assertEqual(0, count($this->observer->get_attempts_modified())); 00136 $this->assertEqual(0, count($this->observer->get_steps_added())); 00137 $this->assertEqual(0, count($this->observer->get_steps_modified())); 00138 $this->assertEqual(0, count($this->observer->get_steps_deleted())); 00139 } 00140 00141 public function test_update_usage() { 00142 00143 $this->quba->set_preferred_behaviour('deferredfeedback'); 00144 00145 $this->assertTrue($this->observer->get_modified()); 00146 } 00147 00148 public function test_add_question() { 00149 00150 $slot = $this->quba->add_question(test_question_maker::make_question('truefalse')); 00151 00152 $newattempts = $this->observer->get_attempts_added(); 00153 $this->assertEqual(1, count($newattempts)); 00154 $this->assertIdentical($this->quba->get_question_attempt($slot), reset($newattempts)); 00155 $this->assertIdentical($slot, key($newattempts)); 00156 } 00157 00158 public function test_add_and_start_question() { 00159 00160 $slot = $this->quba->add_question(test_question_maker::make_question('truefalse')); 00161 $this->quba->start_question($slot); 00162 00163 // The point here is that, although we have added a step, it is not listed 00164 // separately becuase it is part of a newly added attempt, and all steps 00165 // for a newly added attempt are automatically added to the DB, so it does 00166 // not need to be tracked separately. 00167 $newattempts = $this->observer->get_attempts_added(); 00168 $this->assertEqual(1, count($newattempts)); 00169 $this->assertIdentical($this->quba->get_question_attempt($slot), 00170 reset($newattempts)); 00171 $this->assertIdentical($slot, key($newattempts)); 00172 $this->assertEqual(0, count($this->observer->get_steps_added())); 00173 } 00174 00175 public function test_process_action() { 00176 00177 $this->quba->manual_grade($this->slot, 'Acutally, that is not quite right', 0.5); 00178 00179 // Here, however, were we are adding a step to an existing qa, we do need to track that. 00180 $this->assertEqual(0, count($this->observer->get_attempts_added())); 00181 00182 $updatedattempts = $this->observer->get_attempts_modified(); 00183 $this->assertEqual(1, count($updatedattempts)); 00184 00185 $updatedattempt = reset($updatedattempts); 00186 $this->assertIdentical($this->quba->get_question_attempt($this->slot), $updatedattempt); 00187 $this->assertIdentical($this->slot, key($updatedattempts)); 00188 00189 $newsteps = $this->observer->get_steps_added(); 00190 $this->assertEqual(1, count($newsteps)); 00191 00192 list($newstep, $qaid, $seq) = reset($newsteps); 00193 $this->assertIdentical($this->quba->get_question_attempt($this->slot)->get_last_step(), $newstep); 00194 } 00195 00196 public function test_regrade_same_steps() { 00197 00198 // Change the question in a minor way and regrade. 00199 $this->quba->get_question($this->slot)->answer[14]->fraction = 0.5; 00200 $this->quba->regrade_all_questions(); 00201 00202 // Here, the qa, and all the steps, should be marked as updated. 00203 // Here, however, were we are adding a step to an existing qa, we do need to track that. 00204 $this->assertEqual(0, count($this->observer->get_attempts_added())); 00205 $this->assertEqual(0, count($this->observer->get_steps_added())); 00206 $this->assertEqual(0, count($this->observer->get_steps_deleted())); 00207 00208 $updatedattempts = $this->observer->get_attempts_modified(); 00209 $this->assertEqual(1, count($updatedattempts)); 00210 00211 $updatedattempt = reset($updatedattempts); 00212 $this->assertIdentical($this->quba->get_question_attempt($this->slot), $updatedattempt); 00213 00214 $updatedsteps = $this->observer->get_steps_modified(); 00215 $this->assertEqual($updatedattempt->get_num_steps(), count($updatedsteps)); 00216 00217 foreach ($updatedattempt->get_step_iterator() as $seq => $step) { 00218 $this->assertIdentical(array($step, $updatedattempt->get_database_id(), $seq), 00219 $updatedsteps[$seq]); 00220 } 00221 } 00222 00223 public function test_regrade_losing_steps() { 00224 00225 // Change the question so that 'toad' is also right, and regrade. This 00226 // will mean that the try again, and second try states are no longer 00227 // needed, so they should be dropped. 00228 $this->quba->get_question($this->slot)->answers[14]->fraction = 1; 00229 $this->quba->regrade_all_questions(); 00230 00231 $this->assertEqual(0, count($this->observer->get_attempts_added())); 00232 $this->assertEqual(0, count($this->observer->get_steps_added())); 00233 00234 $updatedattempts = $this->observer->get_attempts_modified(); 00235 $this->assertEqual(1, count($updatedattempts)); 00236 00237 $updatedattempt = reset($updatedattempts); 00238 $this->assertIdentical($this->quba->get_question_attempt($this->slot), $updatedattempt); 00239 00240 $updatedsteps = $this->observer->get_steps_modified(); 00241 $this->assertEqual($updatedattempt->get_num_steps(), count($updatedsteps)); 00242 00243 foreach ($updatedattempt->get_step_iterator() as $seq => $step) { 00244 $this->assertIdentical(array($step, $updatedattempt->get_database_id(), $seq), 00245 $updatedsteps[$seq]); 00246 } 00247 00248 $deletedsteps = $this->observer->get_steps_deleted(); 00249 $this->assertEqual(2, count($deletedsteps)); 00250 00251 $firstdeletedstep = reset($deletedsteps); 00252 $this->assertEqual(array('-tryagain' => 1), $firstdeletedstep->get_all_data()); 00253 00254 $seconddeletedstep = end($deletedsteps); 00255 $this->assertEqual(array('answer' => 'frog', '-finish' => 1), 00256 $seconddeletedstep->get_all_data()); 00257 } 00258 00259 public function test_tricky_regrade() { 00260 00261 // The tricky thing here is that we take a half-complete question-attempt, 00262 // and then as one transaction, we submit some more responses, and then 00263 // change the question attempt as in test_regrade_losing_steps, and regrade 00264 // before the steps are even written to the database the first time. 00265 $somedata = $this->get_test_data(); 00266 $somedata = array_slice($somedata, 0, 5); 00267 $this->setup_initial_test_state($somedata); 00268 00269 $this->quba->process_action($this->slot, array('-tryagain' => 1)); 00270 $this->quba->process_action($this->slot, array('answer' => 'frog', '-submit' => 1)); 00271 $this->quba->finish_all_questions(); 00272 00273 $this->quba->get_question($this->slot)->answers[14]->fraction = 1; 00274 $this->quba->regrade_all_questions(); 00275 00276 $this->assertEqual(0, count($this->observer->get_attempts_added())); 00277 00278 $updatedattempts = $this->observer->get_attempts_modified(); 00279 $this->assertEqual(1, count($updatedattempts)); 00280 00281 $updatedattempt = reset($updatedattempts); 00282 $this->assertIdentical($this->quba->get_question_attempt($this->slot), $updatedattempt); 00283 00284 $this->assertEqual(0, count($this->observer->get_steps_added())); 00285 00286 $updatedsteps = $this->observer->get_steps_modified(); 00287 $this->assertEqual($updatedattempt->get_num_steps(), count($updatedsteps)); 00288 00289 foreach ($updatedattempt->get_step_iterator() as $seq => $step) { 00290 $this->assertIdentical(array($step, $updatedattempt->get_database_id(), $seq), 00291 $updatedsteps[$seq]); 00292 } 00293 00294 $this->assertEqual(0, count($this->observer->get_steps_deleted())); 00295 } 00296 }