MDL-40542 need to be able to select variant
authorJamie Pratt <me@jamiep.org>
Sun, 14 Jul 2013 12:26:35 +0000 (19:26 +0700)
committerDan Poltawski <dan@moodle.com>
Tue, 23 Jul 2013 07:11:56 +0000 (15:11 +0800)
in unit tests

mod/quiz/locallib.php
mod/quiz/tests/attempt_walkthrough_test.php
question/engine/tests/helpers.php

index 4a4d796..51b34d7 100644 (file)
@@ -140,17 +140,21 @@ function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timen
 /**
  * Start a normal, new, quiz attempt.
  *
- * @param quiz                          $quizobj            the quiz object to start an attempt for.
- * @param question_usage_by_activity    $quba
- * @param object                        $attempt
- * @param integer                       $attemptnumber      starting from 1
- * @param integer                       $timenow            the attempt start time
- * @param array   $questionids slot number => question id. Used for random questions, to force the choice of a particular actual
- *                              question. Intended for testing purposes only.
+ * @param quiz      $quizobj            the quiz object to start an attempt for.
+ * @param question_usage_by_activity $quba
+ * @param object    $attempt
+ * @param integer   $attemptnumber      starting from 1
+ * @param integer   $timenow            the attempt start time
+ * @param array     $questionids        slot number => question id. Used for random questions, to force the choice
+ *                                        of a particular actual question. Intended for testing purposes only.
+ * @param array     $forcedvariantsbyslot slot number => variant. Used for questions with variants,
+ *                                          to force the choice of a particular variant. Intended for testing
+ *                                          purposes only.
  * @throws moodle_exception
- * @return object                       modified attempt object
+ * @return object   modified attempt object
  */
-function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $timenow, $questionids = array()) {
+function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $timenow,
+                                $questionids = array(), $forcedvariantsbyslot = array()) {
     // Fully load all the questions in this quiz.
     $quizobj->preload_questions();
     $quizobj->load_questions();
@@ -190,8 +194,16 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
     } else {
         $variantoffset = $attemptnumber;
     }
-    $quba->start_all_questions(
-        new question_variant_pseudorandom_no_repeats_strategy($variantoffset), $timenow);
+    $variantstrategy = new question_variant_pseudorandom_no_repeats_strategy($variantoffset);
+
+    if (!empty($forcedvariantsbyslot)) {
+        $forcedvariantsbyseed = question_variant_forced_choices_selection_strategy::prepare_forced_choices_array(
+            $forcedvariantsbyslot, $quba);
+        $variantstrategy = new question_variant_forced_choices_selection_strategy(
+            $forcedvariantsbyseed, $variantstrategy);
+    }
+
+    $quba->start_all_questions($variantstrategy, $timenow);
 
     // Update attempt layout.
     $newlayout = array();
index 1d42151..d67f3b8 100644 (file)
@@ -219,4 +219,87 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
             $this->assertEquals(100, $gradebookgrade->grade);
         }
     }
+
+
+    public function get_correct_response_for_variants() {
+        return array(array(1, 9.9), array(2, 8.5), array(5, 14.2), array(10, 6.8, true));
+    }
+
+    protected $quizwithvariants = null;
+
+    /**
+     * Create a quiz with a single question with variants and walk through quiz attempts.
+     *
+     * @dataProvider get_correct_response_for_variants
+     */
+    public function test_quiz_with_question_with_variants_attempt_walkthrough($variantno, $correctresponse, $done = false) {
+        global $SITE;
+
+        $this->resetAfterTest($done);
+
+        $this->setAdminUser();
+
+        if ($this->quizwithvariants === null) {
+            // Make a quiz.
+            $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+
+            $this->quizwithvariants = $quizgenerator->create_instance(array('course'=>$SITE->id,
+                                                                            'questionsperpage' => 0,
+                                                                            'grade' => 100.0,
+                                                                            'sumgrades' => 1));
+
+            $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+
+            $cat = $questiongenerator->create_question_category();
+            $calc = $questiongenerator->create_question('calculatedsimple', 'sumwithvariants', array('category' => $cat->id));
+            quiz_add_quiz_question($calc->id, $this->quizwithvariants, 0);
+        }
+
+
+        // Make a new user to do the quiz.
+        $user1 = $this->getDataGenerator()->create_user();
+        $this->setUser($user1);
+        $quizobj = quiz::create($this->quizwithvariants->id, $user1->id);
+
+        // Start the attempt.
+        $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
+        $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
+
+        $timenow = time();
+        $attempt = quiz_create_attempt($quizobj, 1, false, $timenow);
+        // Select variant.
+        quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow, array(), array(1 => $variantno));
+        quiz_attempt_save_started($quizobj, $quba, $attempt);
+
+        // Process some responses from the student.
+        $attemptobj = quiz_attempt::create($attempt->id);
+        $tosubmit = array(1 => array('answer' => $correctresponse));
+        $attemptobj->process_submitted_actions($timenow, false, $tosubmit);
+
+        // Finish the attempt.
+        $attemptobj = quiz_attempt::create($attempt->id);
+        $attemptobj->process_finish($timenow, false);
+
+        // Re-load quiz attempt data.
+        $attemptobj = quiz_attempt::create($attempt->id);
+
+        // Check that results are stored as expected.
+        $this->assertEquals(1, $attemptobj->get_attempt_number());
+        $this->assertEquals(1, $attemptobj->get_sum_marks());
+        $this->assertEquals(true, $attemptobj->is_finished());
+        $this->assertEquals($timenow, $attemptobj->get_submitted_date());
+        $this->assertEquals($user1->id, $attemptobj->get_userid());
+
+        // Check quiz grades.
+        $grades = quiz_get_user_grades($this->quizwithvariants, $user1->id);
+        $grade = array_shift($grades);
+        $this->assertEquals(100.0, $grade->rawgrade);
+
+        // Check grade book.
+        $gradebookgrades = grade_get_grades($SITE->id, 'mod', 'quiz', $this->quizwithvariants->id, $user1->id);
+        $gradebookitem = array_shift($gradebookgrades->items);
+        $gradebookgrade = array_shift($gradebookitem->grades);
+        $this->assertEquals(100, $gradebookgrade->grade);
+    }
+
 }
index 4ca15ca..7d4ce01 100644 (file)
@@ -1195,3 +1195,68 @@ class question_test_recordset extends moodle_recordset {
         $this->records = null;
     }
 }
+
+/**
+ * A {@link question_variant_selection_strategy} designed for testing.
+ * For selected. questions it wil return a specific variants. In for the other
+ * slots it will use a fallback strategy.
+ *
+ * @copyright  2013 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_variant_forced_choices_selection_strategy
+    implements question_variant_selection_strategy {
+
+    /** @var array seed => variant to select. */
+    protected $forcedchoices;
+
+    /** @var question_variant_selection_strategy strategy used to make the non-forced choices. */
+    protected $basestrategy;
+
+    /**
+     * Constructor.
+     * @param array $forcedchoice array seed => variant to select.
+     * @param question_variant_selection_strategy $basestrategy strategy used
+     *      to make the non-forced choices.
+     */
+    public function __construct(array $forcedchoices, question_variant_selection_strategy $basestrategy) {
+        $this->forcedchoices = $forcedchoices;
+        $this->basestrategy  = $basestrategy;
+    }
+
+    public function choose_variant($maxvariants, $seed) {
+        if (array_key_exists($seed, $this->forcedchoices)) {
+            if ($this->forcedchoices[$seed] > $maxvariants) {
+                throw new coding_exception('Forced variant out of range.');
+            }
+            return $this->forcedchoices[$seed];
+        } else {
+            return $this->basestrategy->choose_variant($maxvariants, $seed);
+        }
+    }
+
+    /**
+     * Helper method for preparing the $forcedchoices array.
+     * @param array $variantsbyslot slot number => variant to select.
+     * @param question_usage_by_activity $quba the question usage we need a strategy for.
+     * @return array that can be passed to the constructor as $forcedchoices.
+     */
+    public static function prepare_forced_choices_array(array $variantsbyslot,
+                                                        question_usage_by_activity $quba) {
+
+        $forcedchoices = array();
+
+        foreach ($variantsbyslot as $slot => $varianttochoose) {
+            $question = $quba->get_question($slot);
+            $seed = $question->get_variants_selection_seed();
+            if (array_key_exists($seed, $forcedchoices) && $forcedchoices[$seed] != $varianttochoose) {
+                throw new coding_exception('Inconsistent forced variant detected at slot ' . $slot);
+            }
+            if ($varianttochoose > $question->get_num_variants()) {
+                throw new coding_exception('Forced variant out of range at slot ' . $slot);
+            }
+            $forcedchoices[$seed] = $varianttochoose;
+        }
+        return $forcedchoices;
+    }
+}