MDL-63185 mod_quiz: more fixes to new behat code

Part of MDL-62610
This commit is contained in:
Tim Hunt 2018-09-26 18:01:56 +01:00 committed by Simey Lameze
parent 9b1fc262d8
commit c5499edaef
5 changed files with 185 additions and 71 deletions

View File

@ -44,19 +44,17 @@ Feature: Basic use of the Responses report
@javascript
Scenario: Report works when there are attempts
Given I log in as "student1"
And user "student1" has started an attempt at quiz "Quiz 1"
And user "student1" has submitted answers in their attempt at quiz "Quiz 1":
Given user "student1" has started an attempt at quiz "Quiz 1"
And user "student1" has checked answers in their attempt at quiz "Quiz 1":
| slot | response |
| 1 | 1.0 |
And user "student1" has submitted answers in their attempt at quiz "Quiz 1":
And user "student1" has checked answers in their attempt at quiz "Quiz 1":
| slot | response |
| 1 | 3.0 |
And user "student1" has submitted answers in their attempt at quiz "Quiz 1":
And user "student1" has checked answers in their attempt at quiz "Quiz 1":
| slot | response |
| 1 | 3.14 |
And user "student1" has finished an attempt at quiz "Quiz 1"
And I log out
When I log in as "teacher"
And I am on "Course 1" course homepage
And I follow "Quiz 1"

View File

@ -562,6 +562,60 @@ class behat_mod_quiz extends behat_question_base {
$this->find('xpath', $xpath);
}
/**
* Helper used by user_has_attempted_with_responses,
* user_has_started_an_attempt_at_quiz_with_details, etc.
*
* @param TableNode $attemptinfo data table from the Behat step
* @return array with two elements, $forcedrandomquestions, $forcedvariants,
* that can be passed to $quizgenerator->create_attempt.
*/
protected function extract_forced_randomisation_from_attempt_info(TableNode $attemptinfo) {
global $DB;
$forcedrandomquestions = [];
$forcedvariants = [];
foreach ($attemptinfo->getHash() as $slotinfo) {
if (empty($slotinfo['slot'])) {
throw new ExpectationException('When simulating a quiz attempt, ' .
'the slot column is required.', $this->getSession());
}
if (!empty($slotinfo['actualquestion'])) {
$forcedrandomquestions[$slotinfo['slot']] = $DB->get_field('question', 'id',
['name' => $slotinfo['actualquestion']], MUST_EXIST);
}
if (!empty($slotinfo['variant'])) {
$forcedvariants[$slotinfo['slot']] = (int) $slotinfo['variant'];
}
}
return [$forcedrandomquestions, $forcedvariants];
}
/**
* Helper used by user_has_attempted_with_responses, user_has_checked_answers_in_their_attempt_at_quiz,
* user_has_input_answers_in_their_attempt_at_quiz, etc.
*
* @param TableNode $attemptinfo data table from the Behat step
* @return array of responses that can be passed to $quizgenerator->submit_responses.
*/
protected function extract_responses_from_attempt_info(TableNode $attemptinfo) {
$responses = [];
foreach ($attemptinfo->getHash() as $slotinfo) {
if (empty($slotinfo['slot'])) {
throw new ExpectationException('When simulating a quiz attempt, ' .
'the slot column is required.', $this->getSession());
}
if (!array_key_exists('response', $slotinfo)) {
throw new ExpectationException('When simulating a quiz attempt, ' .
'the response column is required.', $this->getSession());
}
$responses[$slotinfo['slot']] = $slotinfo['response'];
}
return $responses;
}
/**
* Attempt a quiz.
*
@ -603,36 +657,16 @@ class behat_mod_quiz extends behat_question_base {
$quizid = $DB->get_field('quiz', 'id', ['name' => $quizname], MUST_EXIST);
$user = $DB->get_record('user', ['username' => $username], '*', MUST_EXIST);
$forcedrandomquestions = [];
$forcedvariants = [];
$responses = [];
foreach ($attemptinfo->getHash() as $slotinfo) {
if (empty($slotinfo['slot'])) {
throw new ExpectationException('When simulating a quiz attempt, ' .
'the slot column is required.', $this->getSession());
}
if (!array_key_exists('response', $slotinfo)) {
throw new ExpectationException('When simulating a quiz attempt, ' .
'the response column is required.', $this->getSession());
}
$responses[$slotinfo['slot']] = $slotinfo['response'];
if (!empty($slotinfo['actualquestion'])) {
$forcedrandomquestions[$slotinfo['slot']] = $DB->get_field('question', 'id',
['name' => $slotinfo['actualquestion']], MUST_EXIST);
}
if (!empty($slotinfo['variant'])) {
$forcedvariants[$slotinfo['slot']] = (int) $slotinfo['variant'];
}
}
list($forcedrandomquestions, $forcedvariants) =
$this->extract_forced_randomisation_from_attempt_info($attemptinfo);
$responses = $this->extract_responses_from_attempt_info($attemptinfo);
$this->set_user($user);
$attempt = $quizgenerator->create_attempt($quizid, $user->id,
$forcedrandomquestions, $forcedvariants);
$quizgenerator->submit_responses($attempt->id, $responses, true);
$quizgenerator->submit_responses($attempt->id, $responses, false, true);
$this->set_user();
}
@ -662,15 +696,18 @@ class behat_mod_quiz extends behat_question_base {
}
/**
* Submit answers to an existing quiz attempt.
* Start a quiz attempt without answers.
*
* The supplied data table for have a row for each slot where you want
* to force either which random question was chose, or which random variant
* was used, as for {@link user_has_attempted_with_responses()} above.
*
* @param string $username the username of the user that will attempt.
* @param string $quizname the name of the quiz the user will attempt.
* @param string $username the username of the user that will attempt.
* @param TableNode $attemptinfo information about the questions to add, as above.
* @throws \Behat\Mink\Exception\ExpectationException
* @Given /^user "([^"]*)" has submitted answers in their attempt at quiz "([^"]*)":$/
* @Given /^user "([^"]*)" has started an attempt at quiz "([^"]*) randomised as follows:"$/
*/
public function user_has_submitted_answers_in_their_attempt_at_quiz($username, $quizname, TableNode $attemptinfo) {
public function user_has_started_an_attempt_at_quiz_with_details($username, $quizname, TableNode $attemptinfo) {
global $DB;
/** @var mod_quiz_generator $quizgenerator */
@ -679,30 +716,84 @@ class behat_mod_quiz extends behat_question_base {
$quizid = $DB->get_field('quiz', 'id', ['name' => $quizname], MUST_EXIST);
$user = $DB->get_record('user', ['username' => $username], '*', MUST_EXIST);
$forcedvariants = [];
$responses = [];
foreach ($attemptinfo->getHash() as $slotinfo) {
if (empty($slotinfo['slot'])) {
throw new ExpectationException('When simulating a quiz attempt, ' .
'the slot column is required.', $this->getSession());
}
if (!array_key_exists('response', $slotinfo)) {
throw new ExpectationException('When simulating a quiz attempt, ' .
'the response column is required.', $this->getSession());
}
$responses[$slotinfo['slot']] = $slotinfo['response'];
if (!empty($slotinfo['variant'])) {
$forcedvariants[$slotinfo['slot']] = (int) $slotinfo['variant'];
}
}
list($forcedrandomquestions, $forcedvariants) =
$this->extract_forced_randomisation_from_attempt_info($attemptinfo);
$this->set_user($user);
foreach (quiz_get_user_attempts($quizid, $user->id, 'unfinished', true) as $attemptid => $attemptobj) {
$quizgenerator->submit_responses($attemptid, $responses, false);
break;
}
$quizgenerator->create_attempt($quizid, $user->id,
$forcedrandomquestions, $forcedvariants);
$this->set_user();
}
/**
* Input answers to particular questions an existing quiz attempt, without
* simulating a click of the 'Check' button, if any.
*
* Then there should be a number of rows of data, with two columns slot and response,
* as for {@link user_has_attempted_with_responses()} above.
* There is no need to supply answers to all questions. If so, other questions will be
* left unanswered.
*
* @param string $username the username of the user that will attempt.
* @param string $quizname the name of the quiz the user will attempt.
* @param TableNode $attemptinfo information about the questions to add, as above.
* @throws \Behat\Mink\Exception\ExpectationException
* @Given /^user "([^"]*)" has input answers in their attempt at quiz "([^"]*)":$/
*/
public function user_has_input_answers_in_their_attempt_at_quiz($username, $quizname, TableNode $attemptinfo) {
global $DB;
/** @var mod_quiz_generator $quizgenerator */
$quizgenerator = behat_util::get_data_generator()->get_plugin_generator('mod_quiz');
$quizid = $DB->get_field('quiz', 'id', ['name' => $quizname], MUST_EXIST);
$user = $DB->get_record('user', ['username' => $username], '*', MUST_EXIST);
$responses = $this->extract_responses_from_attempt_info($attemptinfo);
$this->set_user($user);
$attempts = quiz_get_user_attempts($quizid, $user->id, 'unfinished', true);
$quizgenerator->submit_responses(key($attempts), $responses, false, false);
$this->set_user();
}
/**
* Submit answers to questions an existing quiz attempt, with a simulated click on the 'Check' button.
*
* This step should only be used with question behaviours that have have
* a 'Check' button. Those include Interactive with multiple tires, Immediate feedback
* and Immediate feedback with CBM.
*
* Then there should be a number of rows of data, with two columns slot and response,
* as for {@link user_has_attempted_with_responses()} above.
* There is no need to supply answers to all questions. If so, other questions will be
* left unanswered.
*
* @param string $quizname the name of the quiz the user will attempt.
* @param string $username the username of the user that will attempt.
* @param TableNode $attemptinfo information about the questions to add, as above.
* @throws \Behat\Mink\Exception\ExpectationException
* @Given /^user "([^"]*)" has checked answers in their attempt at quiz "([^"]*)":$/
*/
public function user_has_checked_answers_in_their_attempt_at_quiz($username, $quizname, TableNode $attemptinfo) {
global $DB;
/** @var mod_quiz_generator $quizgenerator */
$quizgenerator = behat_util::get_data_generator()->get_plugin_generator('mod_quiz');
$quizid = $DB->get_field('quiz', 'id', ['name' => $quizname], MUST_EXIST);
$user = $DB->get_record('user', ['username' => $username], '*', MUST_EXIST);
$responses = $this->extract_responses_from_attempt_info($attemptinfo);
$this->set_user($user);
$attempts = quiz_get_user_attempts($quizid, $user->id, 'unfinished', true);
$quizgenerator->submit_responses(key($attempts), $responses, true, false);
$this->set_user();
}
@ -722,11 +813,9 @@ class behat_mod_quiz extends behat_question_base {
$this->set_user($user);
foreach (quiz_get_user_attempts($quizid, $user->id, 'unfinished', true) as $attemptid => $attemptobj) {
$attemptobj = quiz_attempt::create($attemptid);
$attemptobj->process_finish(time(), true);
break;
}
$attempts = quiz_get_user_attempts($quizid, $user->id, 'unfinished', true);
$attemptobj = quiz_attempt::create(key($attempts));
$attemptobj->process_finish(time(), true);
$this->set_user();
}

View File

@ -121,8 +121,8 @@ class mod_quiz_generator extends testing_module_generator {
'be able to create one attempt for each user. (This should be fixed.)');
}
return quiz_prepare_and_start_new_attempt($quizobj, 1, null,
$offlineattempt = false, $forcedrandomquestions = [], $forcedvariants);
return quiz_prepare_and_start_new_attempt($quizobj, 1, null, false,
$forcedrandomquestions, $forcedvariants);
}
/**
@ -134,6 +134,8 @@ class mod_quiz_generator extends testing_module_generator {
* @param int $attemptid the id of the attempt which is being
* @param array $responses array responses to submit. See description on
* {@link core_question_generator::get_simulated_post_data_for_questions_in_usage()}.
* @param bool $checkbutton if simulate a click on the check button for each question, else simulate save.
* This should only be used with behaviours that have a check button.
* @param bool $finishattempt if true, the attempt will be submitted.
*/
public function submit_responses($attemptid, array $responses, $checkbutton, $finishattempt) {
@ -142,10 +144,28 @@ class mod_quiz_generator extends testing_module_generator {
$attemptobj = quiz_attempt::create($attemptid);
$postdata = $questiongenerator->get_simulated_post_data_for_questions_in_usage(
$attemptobj->get_question_usage(), $responses);
$attemptobj->get_question_usage(), $responses, $checkbutton);
$attemptobj->process_submitted_actions(time(), false, $postdata);
// Bit if a hack for interactive behaviour.
// TODO handle this in a more plugin-friendly way.
if ($checkbutton) {
$postdata = [];
foreach ($responses as $slot => $notused) {
$qa = $attemptobj->get_question_attempt($slot);
if ($qa->get_behaviour() instanceof qbehaviour_interactive && $qa->get_behaviour()->is_try_again_state()) {
$postdata[$qa->get_control_field_name('sequencecheck')] = (string)$qa->get_sequence_check_count();
$postdata[$qa->get_flag_field_name()] = (string)(int)$qa->is_flagged();
$postdata[$qa->get_behaviour_field_name('tryagain')] = 1;
}
}
if ($postdata) {
$attemptobj->process_submitted_actions(time(), false, $postdata);
}
}
if ($finishattempt) {
$attemptobj->process_finish(time(), false);
}

View File

@ -65,7 +65,7 @@ class qbehaviour_interactive extends question_behaviour_with_multiple_tries {
/**
* @return bool are we are currently in the try_again state.
*/
protected function is_try_again_state() {
public function is_try_again_state() {
$laststep = $this->qa->get_last_step();
return $this->qa->get_state()->is_active() && $laststep->has_behaviour_var('submit') &&
$laststep->has_behaviour_var('_triesleft');

View File

@ -167,15 +167,17 @@ class core_question_generator extends component_generator_base {
*
* @param question_usage_by_activity $quba the question usage.
* @param array $responses the resonses to submit, in the format described above.
* @param bool $checkbutton if simulate a click on the check button for each question, else simulate save.
* This should only be used with behaviours that have a check button.
* @return array that can be passed to methods like $quba->process_all_actions as simulated POST data.
*/
public function get_simulated_post_data_for_questions_in_usage(
question_usage_by_activity $quba, array $responses) {
question_usage_by_activity $quba, array $responses, $checkbutton) {
$postdata = [];
foreach ($responses as $slot => $responsesummary) {
$postdata += $this->get_simulated_post_data_for_question_attempt(
$quba->get_question_attempt($slot), $responsesummary);
$quba->get_question_attempt($slot), $responsesummary, $checkbutton);
}
return $postdata;
@ -190,11 +192,13 @@ class core_question_generator extends component_generator_base {
* are passed to the un_summarise_response method of the question to decode.
*
* @param question_attempt $qa the question attempt for which we are generating POST data.
* @param string $responsesummary a textual summary of the resonse, as described above.
* @return array the sumulated post data that can be passed to $quba->process_all_actions.
* @param string $responsesummary a textual summary of the response, as described above.
* @param bool $checkbutton if simulate a click on the check button, else simulate save.
* This should only be used with behaviours that have a check button.
* @return array the simulated post data that can be passed to $quba->process_all_actions.
*/
public function get_simulated_post_data_for_question_attempt(
question_attempt $qa, $responsesummary) {
question_attempt $qa, $responsesummary, $checkbutton) {
$question = $qa->get_question();
if (!$question instanceof question_with_responses) {
@ -210,7 +214,10 @@ class core_question_generator extends component_generator_base {
$postdata[$qa->get_qt_field_name($name)] = (string)$value;
}
// TODO handle behaviour variables.
// TODO handle behaviour variables better than this.
if ($checkbutton) {
$postdata[$qa->get_behaviour_field_name('submit')] = 1;
}
return $postdata;
}