Merge branch 'MDL-28219d' of git://github.com/bostelm/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2011-10-16 21:16:38 +02:00
commit e02ddb9be8
6 changed files with 379 additions and 43 deletions

View File

@ -127,16 +127,24 @@ class qbehaviour_adaptive extends question_behaviour_with_save {
return $status;
}
$prevstep = $this->qa->get_last_step_with_behaviour_var('_try');
$prevresponse = $prevstep->get_qt_data();
$prevtries = $this->qa->get_last_behaviour_var('_try', 0);
$prevbest = $pendingstep->get_fraction();
if (is_null($prevbest)) {
$prevbest = 0;
}
if ($this->question->is_same_response($response, $prevresponse)) {
return question_attempt::DISCARD;
}
list($fraction, $state) = $this->question->grade_response($response);
$pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
if ($state == question_state::$gradedright) {
if ($prevstep->get_state() == question_state::$complete) {
$pendingstep->set_state(question_state::$complete);
} else if ($state == question_state::$gradedright) {
$pendingstep->set_state(question_state::$complete);
} else {
$pendingstep->set_state(question_state::$todo);
@ -153,32 +161,59 @@ class qbehaviour_adaptive extends question_behaviour_with_save {
return question_attempt::DISCARD;
}
$laststep = $this->qa->get_last_step();
$response = $laststep->get_qt_data();
if (!$this->question->is_gradable_response($response)) {
$pendingstep->set_state(question_state::$gaveup);
return question_attempt::KEEP;
}
$prevtries = $this->qa->get_last_behaviour_var('_try', 0);
$prevbest = $pendingstep->get_fraction();
$prevbest = $this->qa->get_fraction();
if (is_null($prevbest)) {
$prevbest = 0;
}
if ($laststep->has_behaviour_var('_try')) {
// Last answer was graded, we want to regrade it. Otherwise the answer
// has changed, and we are grading a new try.
$prevtries -= 1;
$laststep = $this->qa->get_last_step();
$response = $laststep->get_qt_data();
if (!$this->question->is_gradable_response($response)) {
$state = question_state::$gaveup;
$fraction = 0;
} else {
if ($laststep->has_behaviour_var('_try')) {
// Last answer was graded, we want to regrade it. Otherwise the answer
// has changed, and we are grading a new try.
$prevtries -= 1;
}
list($fraction, $state) = $this->question->grade_response($response);
$pendingstep->set_behaviour_var('_try', $prevtries + 1);
$pendingstep->set_behaviour_var('_rawfraction', $fraction);
$pendingstep->set_new_response_summary($this->question->summarise_response($response));
}
list($fraction, $state) = $this->question->grade_response($response);
$pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
$pendingstep->set_state($state);
$pendingstep->set_behaviour_var('_try', $prevtries + 1);
$pendingstep->set_behaviour_var('_rawfraction', $fraction);
$pendingstep->set_new_response_summary($this->question->summarise_response($response));
$pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries)));
return question_attempt::KEEP;
}
/**
* Got the most recently graded step. This is mainly intended for use by the
* renderer.
* @return question_attempt_step the most recently graded step.
*/
public function get_graded_step() {
$step = $this->qa->get_last_step_with_behaviour_var('_try');
if ($step->has_behaviour_var('_try')) {
return $step;
} else {
return null;
}
}
/**
* Determine whether a question state represents an "improvable" result,
* that is, whether the user can still improve their score.
*
* @param question_state $state the question state.
* @return bool whether the state is improvable
*/
public function is_state_improvable(question_state $state) {
return $state == question_state::$todo;
}
}

View File

@ -36,13 +36,6 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
protected function get_graded_step(question_attempt $qa) {
foreach ($qa->get_reverse_step_iterator() as $step) {
if ($step->has_behaviour_var('_try')) {
return $step;
}
}
}
public function controls(question_attempt $qa, question_display_options $options) {
return $this->submit_button($qa, $options);
@ -51,7 +44,7 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
public function feedback(question_attempt $qa, question_display_options $options) {
// Try to find the last graded step.
$gradedstep = $this->get_graded_step($qa);
$gradedstep = $qa->get_behaviour()->get_graded_step($qa);
if (is_null($gradedstep) || $qa->get_max_mark() == 0 ||
$options->marks < question_display_options::MARK_AND_MAX) {
return '';
@ -100,14 +93,13 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
}
$output = '';
// print details of grade adjustment due to penalties
// Print details of grade adjustment due to penalties
if ($mark->raw != $mark->cur) {
$output .= ' ' . get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark);
}
// print info about new penalty
// penalty is relevant only if the answer is not correct and further attempts are possible
if (!$qa->get_state()->is_finished()) {
// Print information about any new penalty, only relevant if the answer can be improved.
if ($qa->get_behaviour()->is_state_improvable($qa->get_state())) {
$output .= ' ' . get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
format_float($qa->get_question()->penalty, $options->markdp));
}

View File

@ -39,6 +39,18 @@ require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_base {
protected function get_contains_penalty_info_expectation($penalty) {
$penaltyinfo = get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
format_float($penalty, $this->displayoptions->markdp));
return new PatternExpectation('/'.preg_quote($penaltyinfo).'/');
}
protected function get_does_not_contain_penalty_info_expectation() {
$penaltyinfo = get_string('gradingdetailspenalty', 'qbehaviour_adaptive', 'XXXXX');
$penaltypattern = '/'.str_replace('XXXXX', '\\w*', preg_quote($penaltyinfo)).'/';
return new NoPatternExpectation($penaltypattern);
}
public function test_adaptive_multichoice() {
// Create a multiple choice, single response question.
@ -72,7 +84,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->get_contains_mc_radio_expectation($wrongindex, true, true),
$this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false),
$this->get_contains_mc_radio_expectation(($wrongindex + 2) % 3, true, false),
$this->get_contains_incorrect_expectation());
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33));
$this->assertPattern('/B|C/',
$this->quba->get_response_summary($this->slot));
@ -102,9 +115,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),
$this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, true, false),
$this->get_contains_correct_expectation(),
new PatternExpectation('/' . preg_quote(
get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
format_float($mc->penalty, $this->displayoptions->markdp))) . '/'));
$this->get_does_not_contain_penalty_info_expectation());
$this->assertEqual('A',
$this->quba->get_response_summary($this->slot));
@ -133,7 +144,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
// Now change the correct answer to the question, and regrade.
$mc->answers[13]->fraction = -0.33333333;
$mc->answers[15]->fraction = 1;
$mc->answers[14]->fraction = 1; // We don't know which "wrong" index we chose above!
$mc->answers[15]->fraction = 1; // Therefore, treat answers B and C with the same score.
$this->quba->regrade_all_questions();
// Verify.
@ -144,7 +156,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->get_contains_partcorrect_expectation());
$autogradedstep = $this->get_step($this->get_step_count() - 2);
$this->assertWithinMargin($autogradedstep->get_fraction(), 0, 0.0000001);
$this->assertWithinMargin($autogradedstep->get_fraction(), 1, 0.0000001);
}
public function test_adaptive_multichoice2() {
@ -173,14 +185,16 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->check_current_output(
$this->get_contains_mark_summary(2),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation());
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation());
// Save the same correct answer again. Should no do anything.
// Save the same correct answer again. Should not do anything.
$numsteps = $this->get_step_count();
$this->process_submission(array('choice0' => 1, 'choice2' => 1));
// Verify.
$this->check_step_count($numsteps);
$this->check_current_mark(2);
$this->check_current_state(question_state::$complete);
// Finish the attempt.
@ -196,6 +210,229 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->get_contains_correct_expectation());
}
public function test_adaptive_shortanswer_partially_right() {
// Create a short answer question
$sa = test_question_maker::make_a_shortanswer_question();
$this->start_attempt_at_question($sa, 'adaptive');
// Check the initial state.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation());
// Submit a partially correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'toad'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_partcorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_validation_error_expectation());
// Submit an incorrect answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_validation_error_expectation());
// Submit a correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(0.8);
$this->check_current_output(
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(false),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_shortanswer_wrong_right_wrong() {
// Create a short answer question
$sa = test_question_maker::make_a_shortanswer_question();
$this->start_attempt_at_question($sa, 'adaptive');
// Check the initial state.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation());
// Submit a wrong answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_validation_error_expectation());
// Submit the same wrong answer again. Nothing should change.
$this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_validation_error_expectation());
// Submit a correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit another incorrect answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedwrong);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(false),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_shortanswer_invalid_after_complete() {
// Create a short answer question
$sa = test_question_maker::make_a_shortanswer_question();
$this->start_attempt_at_question($sa, 'adaptive');
// Check the initial state.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation());
// Submit a wrong answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
// Verify.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(0);
$this->check_current_output(
$this->get_contains_mark_summary(0),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_validation_error_expectation());
// Submit a correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit an empty answer.
$this->process_submission(array('-submit' => 1, 'answer' => ''));
// Verify.
$this->check_current_state(question_state::$invalid);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_contains_validation_error_expectation());
// Submit another wrong answer.
$this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedwrong);
$this->check_current_mark(0.66666667);
$this->check_current_output(
$this->get_contains_mark_summary(0.67),
$this->get_contains_submit_button_expectation(false),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
public function test_adaptive_shortanswer_try_to_submit_blank() {
// Create a short answer question with correct answer true.
@ -220,6 +457,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_correctness_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_contains_validation_error_expectation());
$this->assertNull($this->quba->get_response_summary($this->slot));
@ -233,6 +471,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_partcorrect_expectation(),
$this->get_contains_penalty_info_expectation(0.33),
$this->get_does_not_contain_validation_error_expectation());
// Now submit blank again.
@ -245,6 +484,60 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->get_contains_mark_summary(0.8),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_partcorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_contains_validation_error_expectation());
}
public function test_adaptive_numerical() {
// Create a numerical question
$sa = test_question_maker::make_question('numerical', 'pi');
$this->start_attempt_at_question($sa, 'adaptive');
// Check the initial state.
$this->check_current_state(question_state::$todo);
$this->check_current_mark(null);
$this->check_current_output(
$this->get_contains_marked_out_of_summary(),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation());
// Submit the correct answer.
$this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_correct_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Submit an incorrect answer.
$this->process_submission(array('-submit' => 1, 'answer' => '-5'));
// Verify.
$this->check_current_state(question_state::$complete);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
$this->get_contains_submit_button_expectation(true),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_penalty_info_expectation(),
$this->get_does_not_contain_validation_error_expectation());
// Finish the attempt.
$this->quba->finish_all_questions();
// Verify.
$this->check_current_state(question_state::$gradedwrong);
$this->check_current_mark(1);
$this->check_current_output(
$this->get_contains_mark_summary(1),
$this->get_contains_submit_button_expectation(false),
$this->get_contains_incorrect_expectation(),
$this->get_does_not_contain_validation_error_expectation());
}
}

View File

@ -129,7 +129,8 @@ class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrou
// Now change the correct answer to the question, and regrade.
$mc->answers[13]->fraction = -0.33333333;
$mc->answers[15]->fraction = 1;
$mc->answers[14]->fraction = 1; // We don't know which "wrong" index we chose above!
$mc->answers[15]->fraction = 1; // Therefore, treat answers B and C with the same score.
$this->quba->regrade_all_questions();
// Verify.
@ -139,8 +140,8 @@ class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrou
$this->get_contains_mark_summary(1),
$this->get_contains_partcorrect_expectation());
$autogradedstep = $this->get_step($this->get_step_count() - 2);
$this->assertWithinMargin($autogradedstep->get_fraction(), 0, 0.0000001);
$autogradedstep = $this->get_step($this->get_step_count() - 3);
$this->assertWithinMargin($autogradedstep->get_fraction(), 1, 0.0000001);
}
public function test_multichoice2() {

View File

@ -415,6 +415,21 @@ class question_attempt {
return new question_attempt_step_read_only();
}
/**
* Get the last step with a particular behaviour variable set.
* @param string $name the name of the variable to get.
* @return question_attempt_step the last step, or a step with no variables
* if there was not a real step.
*/
public function get_last_step_with_behaviour_var($name) {
foreach ($this->get_reverse_step_iterator() as $step) {
if ($step->has_behaviour_var($name)) {
return $step;
}
}
return new question_attempt_step_read_only();
}
/**
* Get the latest value of a particular question type variable. That is, get
* the value from the latest step that has it set. Return null if it is not

View File

@ -153,7 +153,7 @@ class qtype_numerical_question extends question_graded_automatically {
$prevresponse, $newresponse, 'unit');
}
return false;
return true;
}
public function get_correct_response() {