AutorÃa | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Quiz activity-related unit tests for Completion Progress block.
*
* @package block_completion_progress
* @copyright 2020 Jonathon Fowler <fowlerj@usq.edu.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_completion_progress;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/quiz/lib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
use block_completion_progress\completion_progress;
use block_completion_progress\defaults;
if (!class_exists('mod_quiz\quiz_settings')) {
// Moodle 4.1 or earlier.
class_alias('quiz', 'mod_quiz\quiz_settings', false);
class_alias('quiz_attempt', 'mod_quiz\quiz_attempt', false);
}
/**
* Quiz activity-related unit tests for Completion Progress block.
*
* @package block_completion_progress
* @copyright 2020 Jonathon Fowler <fowlerj@usq.edu.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_completion_test extends \block_completion_progress\tests\completion_testcase {
/**
* A data provider supplying each of the possible quiz grade methods.
* @return array
*/
public function grademethod_provider(): array {
return [
'QUIZ_GRADEHIGHEST' => [ QUIZ_GRADEHIGHEST, ],
'QUIZ_GRADEAVERAGE' => [ QUIZ_GRADEAVERAGE, ],
'QUIZ_ATTEMPTFIRST' => [ QUIZ_ATTEMPTFIRST, ],
'QUIZ_ATTEMPTLAST' => [ QUIZ_ATTEMPTLAST, ],
];
}
/**
* Test completion determination in a Quiz activity with
* pass/fail enabled.
*
* @param integer $grademethod
*
* @covers \block_completion_progress\completion_progress
* @dataProvider grademethod_provider
*/
public function test_quiz_passfail($grademethod) {
$generator = $this->getDataGenerator();
$instance = $generator->create_module('quiz', [
'course' => $this->course->id,
'grade' => 100,
'sumgrades' => 100,
'layout' => '1,0', // One question.
'attempts' => -1,
'grademethod' => $grademethod,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionusegrade' => 1, // Student must receive a grade to complete.
'completionexpected' => time() - DAYSECS,
]);
$cm = get_coursemodule_from_id('quiz', $instance->cmid);
// Set the passing grade.
$item = \grade_item::fetch(['courseid' => $this->course->id, 'itemtype' => 'mod',
'itemmodule' => 'quiz', 'iteminstance' => $instance->id, 'outcomeid' => null]);
$item->gradepass = 50;
$item->update();
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$question = $questiongenerator->create_question('essay', null, [
'category' => $cat->id,
'name' => 'Pass-fail essay question',
'defaultmark' => 100,
'responserequired' => 1,
'attachmentsrequired' => 0,
'responseformat' => 'editor',
]);
quiz_add_quiz_question($question->id, $instance, 1);
$teacher = $generator->create_and_enrol($this->course, 'editingteacher');
// Student 1 submits to the activity and gets graded correctly.
$student1 = $generator->create_and_enrol($this->course, 'student');
$this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
$attempt = $this->submit_for_student($student1, $instance, 1);
$this->assert_progress_completion($student1, $cm, 'submitted');
$this->mark_student($attempt, $teacher, 75); // Pass.
$this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE_PASS);
// Student 2 submits to the activity and gets graded incorrectly.
$student2 = $generator->create_and_enrol($this->course, 'student');
$this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
$attempt = $this->submit_for_student($student2, $instance, 1);
$this->assert_progress_completion($student2, $cm, 'submitted');
$this->mark_student($attempt, $teacher, 25); // Fail.
$this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE_FAIL);
// Student 2 then submits again.
$attempt = $this->submit_for_student($student2, $instance, 2);
switch ($grademethod) {
case QUIZ_GRADEHIGHEST:
$this->assert_progress_completion($student2, $cm, 'submitted');
break;
case QUIZ_GRADEAVERAGE:
$this->assert_progress_completion($student2, $cm, 'submitted');
break;
case QUIZ_ATTEMPTFIRST:
$this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE_FAIL);
break;
case QUIZ_ATTEMPTLAST:
$this->assert_progress_completion($student2, $cm, 'submitted');
break;
}
}
/**
* Test completion determination in an Assignment activity with basic completion.
*
* @param integer $grademethod
*
* @covers \block_completion_progress\completion_progress
* @dataProvider grademethod_provider
*/
public function test_quiz_basic($grademethod) {
$generator = $this->getDataGenerator();
$instance = $generator->create_module('quiz', [
'course' => $this->course->id,
'grade' => 100,
'sumgrades' => 100,
'layout' => '1,0', // One question.
'attempts' => -1,
'grademethod' => $grademethod,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionusegrade' => 1, // Student must receive a grade to complete.
'completionexpected' => time() - DAYSECS,
]);
$cm = get_coursemodule_from_id('quiz', $instance->cmid);
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$question = $questiongenerator->create_question('essay', null, [
'category' => $cat->id,
'name' => 'Basic essay question',
'defaultmark' => 100,
'responserequired' => 1,
'attachmentsrequired' => 0,
'responseformat' => 'editor',
]);
quiz_add_quiz_question($question->id, $instance, 1);
$teacher = $generator->create_and_enrol($this->course, 'editingteacher');
// Student 1 submits to the activity and gets graded correct.
$student1 = $generator->create_and_enrol($this->course, 'student');
$this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
$attempt = $this->submit_for_student($student1, $instance, 1);
$this->assert_progress_completion($student1, $cm, 'submitted');
$this->mark_student($attempt, $teacher, 75); // Pass.
$this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE);
// Student 2 submits to the activity and gets graded incorrect.
$student2 = $generator->create_and_enrol($this->course, 'student');
$this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
$attempt = $this->submit_for_student($student2, $instance, 1);
$this->assert_progress_completion($student2, $cm, 'submitted');
$this->mark_student($attempt, $teacher, 25); // Fail.
$this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
// Student 2 then submits again.
$attempt = $this->submit_for_student($student2, $instance, 2);
switch ($grademethod) {
case QUIZ_GRADEHIGHEST:
$this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
break;
case QUIZ_GRADEAVERAGE:
$this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
break;
case QUIZ_ATTEMPTFIRST:
$this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
break;
case QUIZ_ATTEMPTLAST:
$this->assert_progress_completion($student2, $cm, 'submitted');
break;
}
}
/**
* Submit a quiz attempt.
* @param object $student
* @param object $quiz
* @param integer $attemptnumber
* @return quiz_attempt
*/
private function submit_for_student($student, $quiz, $attemptnumber) {
$quizobj = \mod_quiz\quiz_settings::create($quiz->id, $student->id);
$attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, null, false, [], [], $student->id);
$attemptobj = \mod_quiz\quiz_attempt::create($attempt->id);
// Save a response for the essay in the first slot.
$qa = $attemptobj->get_question_attempt(1);
$qa->process_action([
'answer' => 'Response',
'answerformat' => FORMAT_HTML,
], null, $student->id);
// Finish the attempt.
$attemptobj->process_attempt(time(), true, false, 1);
return $attemptobj;
}
/**
* Mark the first question of an attempt.
* @param quiz_attempt $attemptobj
* @param object $teacher
* @param integer $mark
*/
private function mark_student($attemptobj, $teacher, $mark) {
global $DB;
$this->setUser($teacher);
$quba = $attemptobj->get_question_usage();
$quba->get_question_attempt(1)->manual_grade(
'Comment', $mark, FORMAT_HTML);
\question_engine::save_questions_usage_by_activity($quba);
$update = new \stdClass();
$update->id = $attemptobj->get_attemptid();
$update->timemodified = time();
$update->sumgrades = $quba->get_total_mark();
$DB->update_record('quiz_attempts', $update);
if (class_exists('mod_quiz\grade_calculator')) {
$attemptobj->get_quizobj()->get_grade_calculator()->recompute_final_grade($attemptobj->get_userid());
} else {
quiz_save_best_grade($attemptobj->get_quiz(), $attemptobj->get_userid());
}
$this->setUser(null);
}
}