Merge branch 'MDL-36955' of git://github.com/jmvedrine/moodle
This commit is contained in:
@@ -39,7 +39,7 @@ require_once($CFG->dirroot . '/question/type/multichoice/question.php');
|
||||
* @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 @@ class qtype_multianswer_question extends question_graded_automatically {
|
||||
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 @@ class qtype_multianswer_question_test extends advanced_testcase {
|
||||
$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 @@ class qtype_multianswer_walkthrough_test extends qbehaviour_walkthrough_test_bas
|
||||
// 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 @@ class qtype_multianswer_walkthrough_test extends qbehaviour_walkthrough_test_bas
|
||||
$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"/'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user