Skip to content
Snippets Groups Projects
Commit df1b068c authored by Tim Hunt's avatar Tim Hunt
Browse files

The test question now more-or-less work

This still needs more tests, and there are still missing bits like the
validation, but we are getting there.
parent ae36141b
Branches
Tags
No related merge requests found
...@@ -64,16 +64,49 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -64,16 +64,49 @@ class qtype_stack_question extends question_graded_automatically {
*/ */
protected $seed; protected $seed;
/**
* @var array stack_cas_session STACK specific: session of variables.
*/
protected $session;
/**
* The next three fields cache the results of some expensive computations.
* The chache is only vaid for a particular response, so we store the current
* response, so that we can clearn the cached information in the result changes.
* See {@link validate_cache()}.
* @var array = null;
*/
protected $lastresponse = null;
/** /**
* @var array input name => result of validate_student_response, if known. * @var array input name => result of validate_student_response, if known.
*/ */
protected $inputstates = array(); protected $inputstates = array();
/** /**
* @var array stack_cas_session STACK specific: session of variables. * @var array prt name => result of evaluate_response, if known.
*/ */
protected $session; protected $prtresults = array();
/**
* Make sure the cache is valid for the current response. If not, clear it.
*/
protected function validate_cache($response) {
if (is_null($this->lastresponse)) {
// Nothing cached yet. No worries.
$this->lastresponse = $response;
return;
}
if ($this->lastresponse == $response) {
return; // Cache is good.
}
// Clear the cache.
$this->lastresponse = $response;
$this->inputstates = array();
$this->prtresults = array();
}
public function start_attempt(question_attempt_step $step, $variant) { public function start_attempt(question_attempt_step $step, $variant) {
...@@ -95,7 +128,7 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -95,7 +128,7 @@ class qtype_stack_question extends question_graded_automatically {
} }
public function apply_attempt_state(question_attempt_step $step) { public function apply_attempt_state(question_attempt_step $step) {
$this->seed = $step->get_qt_var('_seed'); $this->seed = (int) $step->get_qt_var('_seed');
$questionvars = new stack_cas_keyval($this->questionvariables, $this->options, $this->seed, 't'); $questionvars = new stack_cas_keyval($this->questionvariables, $this->options, $this->seed, 't');
$qtext = new stack_cas_text($this->questiontext, $questionvars->get_session(), $this->seed, 't', false, true); $qtext = new stack_cas_text($this->questiontext, $questionvars->get_session(), $this->seed, 't', false, true);
$this->session = $qtext->get_session(); $this->session = $qtext->get_session();
...@@ -123,8 +156,10 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -123,8 +156,10 @@ class qtype_stack_question extends question_graded_automatically {
public function summarise_response(array $response) { public function summarise_response(array $response) {
$bits = array(); $bits = array();
foreach ($response as $name => $value) { foreach ($this->inputs as $name => $notused) {
$bits[] = $name . ': ' . $value; if (array_key_exists($name, $response)) {
$bits[] = $name . ': ' . $response[$name];
}
} }
return implode('; ', $bits); return implode('; ', $bits);
} }
...@@ -139,7 +174,7 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -139,7 +174,7 @@ class qtype_stack_question extends question_graded_automatically {
public function is_same_response(array $prevresponse, array $newresponse) { public function is_same_response(array $prevresponse, array $newresponse) {
foreach ($this->inputs as $name => $input) { foreach ($this->inputs as $name => $input) {
if (!question_utils::arrays_same_at_key_integer( if (!question_utils::arrays_same_at_key_missing_is_blank(
$prevresponse, $newresponse, $name)) { $prevresponse, $newresponse, $name)) {
return false; return false;
} }
...@@ -147,7 +182,15 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -147,7 +182,15 @@ class qtype_stack_question extends question_graded_automatically {
return true; return true;
} }
/**
* Get the results of validating one of the input elements.
* @param string $name the name of one of the input elements.
* @param array $response the response.
* @return array the result of calling validate_student_response() on the input.
*/
protected function get_input_state($name, $response) { protected function get_input_state($name, $response) {
$this->validate_cache($response);
if (array_key_exists($name, $this->inputstates)) { if (array_key_exists($name, $this->inputstates)) {
return $this->inputstates[$name]; return $this->inputstates[$name];
} }
...@@ -164,13 +207,25 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -164,13 +207,25 @@ class qtype_stack_question extends question_graded_automatically {
return $this->inputstates[$name]; return $this->inputstates[$name];
} }
/**
* Get the status of the input element.
* @param string $name the name of one of the input elements.
* @param array $response the response.
* @return string 'score', 'invalid' etc.
*/
public function get_input_status($name, $response) { public function get_input_status($name, $response) {
$state = get_input_state($name, $response); $state = $this->get_input_state($name, $response);
return $state[0]; return $state[0];
} }
/**
* Get the feedback from one of the input elements.
* @param string $name the name of one of the input elements.
* @param array $response the response.
* @return string the feedback from this input element for this response.
*/
public function get_input_feedback($name, $response) { public function get_input_feedback($name, $response) {
$state = get_input_state($name, $response); $state = $this->get_input_state($name, $response);
return $state[1]; return $state[1];
} }
...@@ -184,12 +239,15 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -184,12 +239,15 @@ class qtype_stack_question extends question_graded_automatically {
} }
public function is_gradable_response(array $response) { public function is_gradable_response(array $response) {
$allblank = true;
foreach ($this->inputs as $name => $input) { foreach ($this->inputs as $name => $input) {
if ('invalid' == $this->get_input_status($name, $response)) { $status = $this->get_input_status($name, $response);
if ('invalid' == $status) {
return false; return false;
} }
$allblank = $allblank && ($status == '');
} }
return false; return !$allblank;
} }
public function get_validation_error(array $response) { public function get_validation_error(array $response) {
...@@ -201,35 +259,54 @@ class qtype_stack_question extends question_graded_automatically { ...@@ -201,35 +259,54 @@ class qtype_stack_question extends question_graded_automatically {
$fraction = 0; $fraction = 0;
foreach ($this->prts as $index => $prt) { foreach ($this->prts as $index => $prt) {
$requirednames = $prt->get_required_variables(array_keys($question->inputs)); $results = $this->get_prt_result($index, $response);
if ($this->can_execute_prt($requirednames, $attemptstatus)) {
$results = $prt->evaluate_response($session, $question->options, $response, $seed);
$fraction += $results['fraction']; $fraction += $results['fraction'];
}
return array($fraction, question_state::graded_state_for_fraction($fraction));
}
} else { /**
// TODO better error handling. * Do we have all the necssary inputs to execute one of the potential response trees?
$results['feedback'] = ''; * @param stack_potentialresponse_tree $prt the tree in question.
* @param array $response the response.
* @return bool can this PRT be executed for that response.
*/
protected function can_execute_prt(stack_potentialresponse_tree $prt, $response) {
foreach ($prt->get_required_variables(array_keys($this->inputs)) as $name) {
if ($this->get_input_status($name, $response) != 'score') {
return false;
} }
} }
return array($fraction, question_state::graded_state_for_fraction($fraction)); return true;
} }
/** /**
* Decides if the potential response tree should be executed. * Evaluate a PRT for a particular response.
* @param string $index the index of the PRT to evaluate.
* @param array $response the response to process.
* @return array the result from $prt->evaluate_response(), or a fake array
* if the tree cannot be executed.
*/ */
protected function can_execute_prt($requirednames, $attemptstatus) { public function get_prt_result($index, $response) {
$execute = true; $this->validate_cache($response);
foreach ($requirednames as $name) {
if (array_key_exists ($name, $attemptstatus)) { if (array_key_exists($index, $this->prtresults)) {
if ('score' != $attemptstatus[$name]) { return $this->prtresults[$index];
$execute = false;
} }
$prt = $this->prts[$index];
if ($this->can_execute_prt($prt, $response)) {
$this->prtresults[$index] = $prt->evaluate_response(
$this->session, $this->options, $response, $this->seed);
} else { } else {
$execute = false; $this->prtresults[$index] = array(
'fraction' => null,
'feedback' => '',
);
} }
}
return $execute; return $this->prtresults[$index];
} }
public function get_num_variants() { public function get_num_variants() {
......
...@@ -54,18 +54,14 @@ class qtype_stack_renderer extends qtype_renderer { ...@@ -54,18 +54,14 @@ class qtype_stack_renderer extends qtype_renderer {
$input->get_xhtml($currentvalue, $qa->get_qt_field_name($name), $options->readonly), $input->get_xhtml($currentvalue, $qa->get_qt_field_name($name), $options->readonly),
$questiontext); $questiontext);
if ($options->feedback) {
$feedback = $this->input_feedback($question->get_input_feedback($name, $response)); $feedback = $this->input_feedback($question->get_input_feedback($name, $response));
} else {
$feedback = '';
}
$questiontext = str_replace("<IEfeedback>{$name}</IEfeedback>", $feedback, $questiontext); $questiontext = str_replace("<IEfeedback>{$name}</IEfeedback>", $feedback, $questiontext);
} }
foreach ($question->prts as $index => $prt) { foreach ($question->prts as $index => $prt) {
if ($options->feedback) { if ($options->feedback) {
$result = $question->get_prt_result($response); $result = $question->get_prt_result($index, $response);
$feedback = $this->input_feedback($question->get_input_feedback($name, $response)); $feedback = $this->prt_feedback($result['feedback']);
} else { } else {
$feedback = ''; $feedback = '';
} }
......
...@@ -37,9 +37,52 @@ require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php'); ...@@ -37,9 +37,52 @@ require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
*/ */
class qtype_stack_question_test extends UnitTestCase { class qtype_stack_question_test extends UnitTestCase {
/**
* @return qtype_stack_question the requested question object.
*/
protected function get_test_stack_question($which = null) {
return test_question_maker::make_question('stack', $which);
}
public function test_get_expected_data() { public function test_get_expected_data() {
$q = test_question_maker::make_question('stack'); $q = $this->get_test_stack_question();
$this->assertEqual(array('ans1' => PARAM_RAW), $q->get_expected_data()); $this->assertEqual(array('ans1' => PARAM_RAW), $q->get_expected_data());
} }
public function test_get_correct_response_test0() {
$q = $this->get_test_stack_question('test0');
$this->assertEqual(array('ans1' => '2'), $q->get_correct_response());
}
public function test_get_correct_response_test3() {
$q = $this->get_test_stack_question('test3');
$this->assertEqual(array('ans1' => 'x^3', 'ans2' => 'x^4', 'ans3' => '0', 'ans4' => true),
$q->get_correct_response());
}
public function test_get_is_same_response_test0() {
$q = $this->get_test_stack_question('test0');
$this->assertFalse($q->is_same_response(array(), array('ans1' => '2')));
$this->assertTrue($q->is_same_response(array('ans1' => '2'), array('ans1' => '2')));
$this->assertFalse($q->is_same_response(array('_seed' => '123'), array('ans1' => '2')));
$this->assertFalse($q->is_same_response(array('ans1' => '2'), array('ans1' => '3')));
}
public function test_is_complete_response_test0() {
$q = $this->get_test_stack_question('test0');
$this->assertFalse($q->is_complete_response(array()));
$this->assertTrue($q->is_complete_response(array('ans1' => '2')));
}
public function test_is_gradable_response_test0() {
$q = $this->get_test_stack_question('test0');
$this->assertFalse($q->is_gradable_response(array()));
$this->assertTrue($q->is_gradable_response(array('ans1' => '2')));
}
} }
...@@ -57,6 +57,7 @@ class qtype_stack_walkthrough_test extends qbehaviour_walkthrough_test_base { ...@@ -57,6 +57,7 @@ class qtype_stack_walkthrough_test extends qbehaviour_walkthrough_test_base {
$this->get_no_hint_visible_expectation() $this->get_no_hint_visible_expectation()
); );
// Submit the correct response:
// TODO. // TODO.
} }
} }
...@@ -298,7 +298,6 @@ class stack_interaction_element { ...@@ -298,7 +298,6 @@ class stack_interaction_element {
} else { } else {
$status = 'invalid'; $status = 'invalid';
} }
$status = $valid;
return array($status, $feedback); return array($status, $feedback);
} }
......
...@@ -175,7 +175,7 @@ class stack_potentialresponse_tree { ...@@ -175,7 +175,7 @@ class stack_potentialresponse_tree {
// Tidy up the results. // Tidy up the results.
$feedbackct = new stack_cas_text(implode(' ', $results['feedback']), $cascontext, $seed, 't', false, false); $feedbackct = new stack_cas_text(implode(' ', $results['feedback']), $cascontext, $seed, 't', false, false);
$results['feedback'] = html_writer::tag('div', $feedbackct->get_display_castext(), array('class' => 'PRTFeedback')); $results['feedback'] = $feedbackct->get_display_castext();
$results['errors'] .= $feedbackct->get_errors(); $results['errors'] .= $feedbackct->get_errors();
$results['answernote'] = implode(' | ', $results['answernote']); $results['answernote'] = implode(' | ', $results['answernote']);
$results['fraction'] = $results['score'] * $this->value; $results['fraction'] = $results['score'] * $this->value;
......
...@@ -58,7 +58,7 @@ class stack_potentialresponsetree_test extends UnitTestCase { ...@@ -58,7 +58,7 @@ class stack_potentialresponsetree_test extends UnitTestCase {
$this->assertEqual('', $result['errors']); $this->assertEqual('', $result['errors']);
$this->assertEqual(2, $result['score']); $this->assertEqual(2, $result['score']);
$this->assertEqual(0, $result['penalty']); $this->assertEqual(0, $result['penalty']);
$this->assertEqual('<div class="PRTFeedback">Yeah!</div>', $result['feedback']); $this->assertEqual('Yeah!', $result['feedback']);
$this->assertEqual('ATInt_true | 1-0-1', $result['answernote']); $this->assertEqual('ATInt_true | 1-0-1', $result['answernote']);
} }
...@@ -151,7 +151,7 @@ class stack_potentialresponsetree_test extends UnitTestCase { ...@@ -151,7 +151,7 @@ class stack_potentialresponsetree_test extends UnitTestCase {
$this->assertEqual('', $result['errors']); $this->assertEqual('', $result['errors']);
$this->assertEqual(1, $result['score']); $this->assertEqual(1, $result['score']);
$this->assertEqual(0, $result['penalty']); $this->assertEqual(0, $result['penalty']);
$this->assertEqual('<div class="PRTFeedback">Test 1 true. Test 2 false.</div>', $result['feedback']); $this->assertEqual('Test 1 true. Test 2 false.', $result['feedback']);
$this->assertEqual('1-0-1 | ATFacForm_notfactored. | 1-1-0 | [PRT-CIRCULARITY]=0', $result['answernote']); $this->assertEqual('1-0-1 | ATFacForm_notfactored. | 1-1-0 | [PRT-CIRCULARITY]=0', $result['answernote']);
$this->assertEqual(array('sa1', 'ta'), $tree->get_required_variables(array('sa1', 'sa3', 'ta', 'ssa1', 'a1', 't'))); $this->assertEqual(array('sa1', 'ta'), $tree->get_required_variables(array('sa1', 'sa3', 'ta', 'ssa1', 'a1', 't')));
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment