Permalink
Browse files

MDL-36955 Multianswer grading penalties do not address subparts

  • Loading branch information...
1 parent e9af609 commit c2f056a9255fa740c594f45bf65327512e2864e3 Jean-Michel Vedrine committed Dec 12, 2012
@@ -39,7 +39,7 @@
* @copyright 2010 Pierre Pichet
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class qtype_multianswer_question extends question_graded_automatically {
+class qtype_multianswer_question extends question_graded_automatically_with_countback {
/** @var array of question_graded_automatically. */
public $subquestions = array();
@@ -263,6 +263,32 @@ public function get_num_parts_right(array $response) {
return array($numright, count($this->subquestions));
}
+ public function compute_final_grade($responses, $totaltries) {
+ $fractionsum = 0;
+ $fractionmax = 0;
+ foreach ($this->subquestions as $i => $subq) {
+ $fractionmax += $subq->defaultmark;
+
+ $lastresponse = array();
+ $lastchange = 0;
+ $subfraction = 0;
+ foreach ($responses as $responseindex => $response) {
+ $substep = $this->get_substep(null, $i);
+ $subresp = $substep->filter_array($response);
+ if ($subq->is_same_response($lastresponse, $subresp)) {
+ continue;
+ }
+ $lastresponse = $subresp;
+ $lastchange = $responseindex;
+ list($subfraction, $newstate) = $subq->grade_response($subresp);
+ }
+
+ $fractionsum += $subq->defaultmark * max(0, $subfraction - $lastchange * $this->penalty);
+ }
+
+ return $fractionsum / $fractionmax;
+ }
+
public function summarise_response(array $response) {
$summary = array();
foreach ($this->subquestions as $i => $subq) {
@@ -177,4 +177,65 @@ public function test_clear_wrong_from_response() {
$this->assertEquals($question->clear_wrong_from_response($response),
array('sub2_answer' => $right));
}
+
+ public function test_compute_final_grade() {
+ $question = test_question_maker::make_question('multianswer');
+ // Set penalty to 0.2 to ease calculations.
+ $question->penalty = 0.2;
+ // Set subquestion 2 defaultmark to 2, to make it a better test,
+ // even thought (at the moment) that never happens for real.
+ $question->subquestions[2]->defaultmark = 2;
+
+ $question->start_attempt(new question_attempt_step(), 1);
+
+ // Compute right and wrong response for subquestion 2.
+ $rightchoice = $question->subquestions[2]->get_correct_response();
+ $right = reset($rightchoice);
+ $wrong = ($right +1) % 3;
+
+ // Get subquestion 1 right at 2nd try and subquestion 2 right at 3rd try.
+ $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong),
+ 1 => array('sub1_answer' => 'Owl', 'sub2_answer' => $wrong),
+ 2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
+ );
+ $finalgrade = $question->compute_final_grade($responses, 1);
+ $this->assertEquals(1/3*(1 - 0.2) + 2/3*(1 - 2*0.2), $finalgrade);
+
+ // Get subquestion 1 right at 3rd try and subquestion 2 right at 2nd try.
+ $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong),
+ 1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $right),
+ 2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
+ 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
+ );
+ $finalgrade = $question->compute_final_grade($responses, 1);
+ $this->assertEquals(1/3*(1 - 2*0.2) + 2/3*(1 - 0.2), $finalgrade);
+
+ // Get subquestion 1 right at 4th try and subquestion 2 right at 1st try.
+ $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
+ 1 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
+ 2 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
+ 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
+ );
+ $finalgrade = $question->compute_final_grade($responses, 1);
+ $this->assertEquals(1/3*(1 - 3*0.2) + 2/3, $finalgrade);
+
+ // Get subquestion 1 right at 4th try and subquestion 2 right 3rd try.
+ // Subquestion 2 was right at 1st try, but last change is at 3rd try.
+ $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
+ 1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $wrong),
+ 2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right),
+ 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
+ );
+ $finalgrade = $question->compute_final_grade($responses, 1);
+ $this->assertEquals(1/3*(1 - 3*0.2) + 2/3*(1 - 2*0.2), $finalgrade);
+
+ // Incomplete responses. Subquestion 1 is right at 4th try and subquestion 2 at 3rd try.
+ $responses = array(0 => array('sub1_answer' => 'Dog'),
+ 1 => array('sub1_answer' => 'Cat'),
+ 2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right),
+ 3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
+ );
+ $finalgrade = $question->compute_final_grade($responses, 1);
+ $this->assertEquals(1/3*(1 - 3*0.2) + 2/3*(1 - 2*0.2), $finalgrade);
+ }
}
@@ -214,8 +214,7 @@ public function test_interactive_feedback() {
// Check the initial state.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(null);
- // TODO change to interactivecountback after MDL-36955 is integrated.
- $this->assertEquals('interactive',
+ $this->assertEquals('interactivecountback',
$this->quba->get_question_attempt($this->slot)->get_behaviour_name());
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
@@ -322,4 +321,93 @@ public function test_interactive_feedback() {
$this->get_tries_remaining_expectation(1),
$this->get_no_hint_visible_expectation());
}
+
+ public function test_interactivecountback_feedback() {
+
+ // Create a multianswer question.
+ $q = test_question_maker::make_question('multianswer', 'fourmc');
+ $q->hints = array(
+ new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, true, true),
+ new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
+ );
+ $choices = array('' => '', '0' => 'Califormia', '1' => 'Arizona');
+
+ $this->start_attempt_at_question($q, 'interactive', 12);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->assertEquals('interactivecountback',
+ $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
+ $this->check_current_output(
+ $this->get_contains_marked_out_of_summary(),
+ $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
+ $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
+ $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
+ $this->get_contains_select_expectation('sub4_answer', $choices, null, true),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_validation_error_expectation(),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(3),
+ $this->get_no_hint_visible_expectation());
+
+ // Submit an answer with two right, and two wrong.
+ $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
+ 'sub3_answer' => '1', 'sub4_answer' => '1', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_select_expectation('sub1_answer', $choices, 1, false),
+ $this->get_contains_select_expectation('sub2_answer', $choices, 1, false),
+ $this->get_contains_select_expectation('sub3_answer', $choices, 1, false),
+ $this->get_contains_select_expectation('sub4_answer', $choices, 1, false),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_contains_try_again_button_expectation(true),
+ $this->get_does_not_contain_correctness_expectation(),
+ new question_pattern_expectation('/' .
+ preg_quote(get_string('notcomplete', 'qbehaviour_interactive')) . '/'),
+ $this->get_contains_hint_expectation('This is the first hint.'));
+
+ // Check that extract responses will return the reset data.
+ $prefix = $this->quba->get_field_prefix($this->slot);
+ $this->assertEquals(array('sub1_answer' => 1),
+ $this->quba->extract_responses($this->slot, array($prefix . 'sub1_answer' => 1)));
+
+ // Do try again.
+ $this->process_submission(array('sub1_answer' => '',
+ 'sub2_answer' => '1', 'sub3_answer' => '',
+ 'sub4_answer' => '1', '-tryagain' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_select_expectation('sub1_answer', $choices, '', true),
+ $this->get_contains_select_expectation('sub2_answer', $choices, '1', true),
+ $this->get_contains_select_expectation('sub3_answer', $choices, '', true),
+ $this->get_contains_select_expectation('sub4_answer', $choices, '1', true),
+ $this->get_contains_submit_button_expectation(true),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_tries_remaining_expectation(2),
+ $this->get_no_hint_visible_expectation());
+
+ // Submit the right answer.
+ $this->process_submission(array('sub1_answer' => '0', 'sub2_answer' => '1',
+ 'sub3_answer' => '0', 'sub4_answer' => '1', '-submit' => 1));
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(10);
+ $this->check_current_output(
+ $this->get_contains_select_expectation('sub1_answer', $choices, '0', false),
+ $this->get_contains_select_expectation('sub2_answer', $choices, '1', false),
+ $this->get_contains_select_expectation('sub3_answer', $choices, '0', false),
+ $this->get_contains_select_expectation('sub4_answer', $choices, '1', false),
+ $this->get_contains_submit_button_expectation(false),
+ $this->get_does_not_contain_try_again_button_expectation(),
+ $this->get_contains_correct_expectation(),
+ new question_no_pattern_expectation('/class="control\b[^"]*\bpartiallycorrect"/'));
+ }
}

0 comments on commit c2f056a

Please sign in to comment.