From 1ff3389a54c3af5eba4d99560010b5df62a30c0c Mon Sep 17 00:00:00 2001 From: Jean-Michel Vedrine Date: Wed, 15 Aug 2012 15:33:22 +0200 Subject: [PATCH] MDL-34808 qformat examview Add phpunit tests to examview import format --- question/format/examview/format.php | 103 ++-- .../examview/lang/en/qformat_examview.php | 3 +- .../examview/tests/examviewformat_test.php | 465 ++++++++++++++++++ question/format/examview/version.php | 3 +- 4 files changed, 531 insertions(+), 43 deletions(-) create mode 100644 question/format/examview/tests/examviewformat_test.php diff --git a/question/format/examview/format.php b/question/format/examview/format.php index f1acd3424c8..7cea482d034 100644 --- a/question/format/examview/format.php +++ b/question/format/examview/format.php @@ -17,8 +17,7 @@ /** * Examview question importer. * - * @package qformat - * @subpackage examview + * @package qformat_examview * @copyright 2005 Howard Miller * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -35,7 +34,7 @@ require_once($CFG->libdir . '/xmlize.php'); * @copyright 2005 Howard Miller * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class qformat_examview extends qformat_default { +class qformat_examview extends qformat_based_on_xml { public $qtypes = array( 'tf' => TRUEFALSE, @@ -49,8 +48,8 @@ class qformat_examview extends qformat_default { 'es' => ESSAY, 'ca' => 99, 'ot' => 99, - 'sa' => SHORTANSWER - ); + 'sa' => SHORTANSWER, + ); public $matching_questions = array(); @@ -62,6 +61,28 @@ class qformat_examview extends qformat_default { return 'application/xml'; } + /** + * Some softwares put entities in exported files. + * This method try to clean up known problems. + * @param string str string to correct + * @return string the corrected string + */ + public function cleaninput($str) { + + $html_code_list = array( + "'" => "'", + "’" => "'", + "“" => "\"", + "”" => "\"", + "–" => "-", + "—" => "-", + ); + $str = strtr($str, $html_code_list); + // Use textlib entities_to_utf8 function to convert only numerical entities. + $str = textlib::entities_to_utf8( $str, false); + return $str; + } + /** * unxmlise reconstructs part of the xml data structure in order * to identify the actual data therein @@ -87,13 +108,6 @@ class qformat_examview extends qformat_default { $text = strip_tags($text); return $text; } - protected function text_field($text) { - return array( - 'text' => htmlspecialchars(trim($text), ENT_NOQUOTES), - 'format' => FORMAT_HTML, - 'files' => array(), - ); - } protected function add_blank_combined_feedback($question) { $question->correctfeedback['text'] = ''; @@ -108,7 +122,7 @@ class qformat_examview extends qformat_default { return $question; } - protected function parse_matching_groups($matching_groups) { + public function parse_matching_groups($matching_groups) { if (empty($matching_groups)) { return; } @@ -136,8 +150,7 @@ class qformat_examview extends qformat_default { $phrase = trim($this->unxmlise($qrec['text']['0']['#'])); $answer = trim($this->unxmlise($qrec['answer']['0']['#'])); $answer = strip_tags( $answer ); - $match_group->subquestions[] = $phrase; - $match_group->subanswers[] = $match_group->subchoices[$answer]; + $match_group->mappings[$phrase] = $match_group->subchoices[$answer]; $this->matching_questions[$groupname] = $match_group; return null; } @@ -146,6 +159,7 @@ class qformat_examview extends qformat_default { if (empty($this->matching_questions)) { return; } + foreach ($this->matching_questions as $match_group) { $question = $this->defaultquestion(); $htmltext = s($match_group->questiontext); @@ -157,12 +171,17 @@ class qformat_examview extends qformat_default { $question = $this->add_blank_combined_feedback($question); $question->subquestions = array(); $question->subanswers = array(); - foreach ($match_group->subquestions as $key => $value) { - $htmltext = s($value); - $question->subquestions[] = $this->text_field($htmltext); - - $htmltext = s($match_group->subanswers[$key]); - $question->subanswers[] = $htmltext; + foreach ($match_group->subchoices as $subchoice) { + $fiber = array_keys ($match_group->mappings, $subchoice); + $subquestion = ''; + foreach ($fiber as $subquestion) { + $question->subquestions[] = $this->text_field($subquestion); + $question->subanswers[] = $subchoice; + } + if ($subquestion == '') { // Then in this case, $subchoice is a distractor. + $question->subquestions[] = $this->text_field(''); + $question->subanswers[] = $subchoice; + } } $questions[] = $question; } @@ -172,7 +191,7 @@ class qformat_examview extends qformat_default { return str_replace('’', "'", $text); } - protected function readquestions($lines) { + public function readquestions($lines) { // Parses an array of lines into an array of questions, // where each item is a question object as defined by // readquestion(). @@ -209,9 +228,11 @@ class qformat_examview extends qformat_default { $question->qtype = null; } $question->single = 1; + // Only one answer is allowed. $htmltext = $this->unxmlise($qrec['#']['text'][0]['#']); - $question->questiontext = $htmltext; + + $question->questiontext = $this->cleaninput($htmltext); $question->questiontextformat = FORMAT_HTML; $question->questiontextfiles = array(); $question->name = shorten_text( $question->questiontext, 250 ); @@ -251,11 +272,11 @@ class qformat_examview extends qformat_default { $question->answer = $choices[$answer]; $question->correctanswer = $question->answer; if ($question->answer == 1) { - $question->feedbacktrue = $this->text_field('Correct'); - $question->feedbackfalse = $this->text_field('Incorrect'); + $question->feedbacktrue = $this->text_field(get_string('correct', 'question')); + $question->feedbackfalse = $this->text_field(get_string('incorrect', 'question')); } else { - $question->feedbacktrue = $this->text_field('Incorrect'); - $question->feedbackfalse = $this->text_field('Correct'); + $question->feedbacktrue = $this->text_field(get_string('incorrect', 'question')); + $question->feedbackfalse = $this->text_field(get_string('correct', 'question')); } return $question; } @@ -268,13 +289,13 @@ class qformat_examview extends qformat_default { foreach ($choices as $key => $value) { if (strpos(trim($key), 'choice-') !== false) { - $question->answer[$key] = $this->text_field(s($this->unxmlise($value[0]['#']))); + $question->answer[] = $this->text_field(s($this->unxmlise($value[0]['#']))); if (strcmp($key, $answer) == 0) { - $question->fraction[$key] = 1; - $question->feedback[$key] = $this->text_field('Correct'); + $question->fraction[] = 1; + $question->feedback[] = $this->text_field(get_string('correct', 'question')); } else { - $question->fraction[$key] = 0; - $question->feedback[$key] = $this->text_field('Incorrect'); + $question->fraction[] = 0; + $question->feedback[] = $this->text_field(get_string('incorrect', 'question')); } } } @@ -290,11 +311,15 @@ class qformat_examview extends qformat_default { foreach ($answers as $key => $value) { $value = trim($value); if (strlen($value) > 0) { - $question->answer[$key] = $value; - $question->fraction[$key] = 1; - $question->feedback[$key] = $this->text_field("Correct"); + $question->answer[] = $value; + $question->fraction[] = 1; + $question->feedback[] = $this->text_field(get_string('correct', 'question')); } } + $question->answer[] = '*'; + $question->fraction[] = 0; + $question->feedback[] = $this->text_field(get_string('incorrect', 'question')); + return $question; } @@ -318,10 +343,10 @@ class qformat_examview extends qformat_default { $value = trim($value); if (is_numeric($value)) { $errormargin = 0; - $question->answer[$key] = $value; - $question->fraction[$key] = 1; - $question->feedback[$key] = $this->text_field("Correct"); - $question->tolerance[$key] = $errormargin; + $question->answer[] = $value; + $question->fraction[] = 1; + $question->feedback[] = $this->text_field(get_string('correct', 'question')); + $question->tolerance[] = $errormargin; } } return $question; diff --git a/question/format/examview/lang/en/qformat_examview.php b/question/format/examview/lang/en/qformat_examview.php index 970778f59ec..ce51ff1b71d 100644 --- a/question/format/examview/lang/en/qformat_examview.php +++ b/question/format/examview/lang/en/qformat_examview.php @@ -17,8 +17,7 @@ /** * Strings for component 'qformat_examview', language 'en', branch 'MOODLE_20_STABLE' * - * @package qformat - * @subpackage examview + * @package qformat_examview * @copyright 2010 Helen Foster * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/question/format/examview/tests/examviewformat_test.php b/question/format/examview/tests/examviewformat_test.php new file mode 100644 index 00000000000..6434ba83b82 --- /dev/null +++ b/question/format/examview/tests/examviewformat_test.php @@ -0,0 +1,465 @@ +. + +/** + * Unit tests for the Moodle Examview format. + * + * @package qformat_examview + * @copyright 2012 jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/question/format.php'); +require_once($CFG->dirroot . '/question/format/examview/format.php'); +require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); + + +/** + * Unit tests for the examview question import format. + * + * @copyright 2012 Jean-Michel Vedrine + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qformat_examview_test extends question_testcase { + + public function make_test_xml() { + $xml = " + +
+ Moodle Example + A +
+ + + ansi + Times New Roman + variable + roman + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 13 + + #000000 + + + 1 + 11 + + #000000 + + + 1 + 11 + #000000 + + + 1 + 11 + #000000 + + + 1 + 11 + #000000 + + + Name: ________________________ Class: ___________________ Date: __________ ID: + Name: ________________________ ID: + + + + + True/False +Indicate whether the sentence or statement is true or false. + Modified True/False +Indicate whether the sentence or statement is true or false. If false, change the identified word or phrase to make the sentence or statement true. + Multiple Choice +Identify the letter of the choice that best completes the statement or answers the question. + Yes/No +Indicate whether you agree with the sentence or statement. + Numeric Response + Completion +Complete each sentence or statement. + Matching + Short Answer + Problem + Essay + Case + Other + + 42 is the Absolute Answer to everything. + 42 is the Ultimate Answer. + F + + + What's between orange and green in the spectrum? + + red + yellow + blue + + B + + + This is a numeric response question. How much is 12 * 2? + 24 + + -1 + + + + Classify the animals. + + insect + amphibian + mammal + + + + cat + C + + + frog + B + + + newt + B + + + Name an amphibian: __________. + frog + + -1 + + + + How are you? + Examview answer for essay questions will be imported as informations for graders. + + -1 + + +
"; + return array(0=>$xml); + } + + public function test_import_truefalse() { + + $xml = $this->make_test_xml(); + $questions = array(); + + $importer = new qformat_examview(); + $questions = $importer->readquestions($xml); + $q = $questions[0]; + + $expectedq = new stdClass(); + $expectedq->qtype = 'truefalse'; + $expectedq->name = '42 is the Absolute Answer to everything.'; + $expectedq->questiontext = "42 is the Absolute Answer to everything."; + $expectedq->questiontextformat = FORMAT_HTML; + $expectedq->generalfeedback = ''; + $expectedq->generalfeedbackformat = FORMAT_MOODLE; + $expectedq->defaultmark = 1; + $expectedq->length = 1; + $expectedq->correctanswer = 0; + $expectedq->feedbacktrue = array( + 'text' => get_string('incorrect', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ); + $expectedq->feedbackfalse = array( + 'text' => get_string('correct', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ); + + $this->assert(new question_check_specified_fields_expectation($expectedq), $q); + } + public function test_import_multichoice_single() { + $xml = $this->make_test_xml(); + $questions = array(); + + $importer = new qformat_examview(); + $questions = $importer->readquestions($xml); + $q = $questions[1]; + + $expectedq = new stdClass(); + $expectedq->qtype = 'multichoice'; + $expectedq->single = 1; + $expectedq->name = "What's between orange and green in the spectrum?"; + $expectedq->questiontext = "What's between orange and green in the spectrum?"; + $expectedq->questiontextformat = FORMAT_HTML; + $expectedq->correctfeedback = array('text' => '', + 'format' => FORMAT_HTML, 'files' => array()); + $expectedq->partiallycorrectfeedback = array('text' => '', + 'format' => FORMAT_HTML, 'files' => array()); + $expectedq->incorrectfeedback = array('text' => '', + 'format' => FORMAT_HTML, 'files' => array()); + $expectedq->generalfeedback = ''; + $expectedq->generalfeedbackformat = FORMAT_MOODLE; + $expectedq->defaultmark = 1; + $expectedq->length = 1; + $expectedq->penalty = 0.3333333; + $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers'); + $expectedq->answer = array( + 0 => array( + 'text' => 'red', + 'format' => FORMAT_HTML, + 'files' => array(), + ), + 1 => array( + 'text' => 'yellow', + 'format' => FORMAT_HTML, + 'files' => array(), + ), + 2 => array( + 'text' => 'blue', + 'format' => FORMAT_HTML, + 'files' => array(), + ) + ); + $expectedq->fraction = array(0, 1, 0); + $expectedq->feedback = array( + 0 => array( + 'text' => get_string('incorrect', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ), + 1 => array( + 'text' => get_string('correct', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ), + 2 => array( + 'text' => get_string('incorrect', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ) + ); + + $this->assert(new question_check_specified_fields_expectation($expectedq), $q); + } + + public function test_import_numerical() { + + $xml = $this->make_test_xml(); + $questions = array(); + + $importer = new qformat_examview(); + $questions = $importer->readquestions($xml); + $q = $questions[2]; + + $expectedq = new stdClass(); + $expectedq->qtype = 'numerical'; + $expectedq->name = 'This is a numeric response question. How much is 12 * 2?'; + $expectedq->questiontext = 'This is a numeric response question. How much is 12 * 2?'; + $expectedq->questiontextformat = FORMAT_HTML; + $expectedq->generalfeedback = ''; + $expectedq->generalfeedbackformat = FORMAT_MOODLE; + $expectedq->defaultmark = 1; + $expectedq->length = 1; + $expectedq->penalty = 0.3333333; + $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers'); + $expectedq->answer = array('24'); + $expectedq->fraction = array(1); + $expectedq->feedback = array( + 0 => array( + 'text' => get_string('correct', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ), + ); + + $this->assert(new question_check_specified_fields_expectation($expectedq), $q); + } + + + + public function test_import_fill_in_the_blank() { + + $xml = $this->make_test_xml(); + $questions = array(); + + $importer = new qformat_examview(); + $questions = $importer->readquestions($xml); + $q = $questions[3]; + + $expectedq = new stdClass(); + $expectedq->qtype = 'shortanswer'; + $expectedq->name = 'Name an amphibian: __________.'; + $expectedq->questiontext = 'Name an amphibian: __________.'; + $expectedq->questiontextformat = FORMAT_HTML; + $expectedq->generalfeedback = ''; + $expectedq->generalfeedbackformat = FORMAT_MOODLE; + $expectedq->defaultmark = 1; + $expectedq->length = 1; + $expectedq->usecase = 0; + $expectedq->answer = array('frog', '*'); + $expectedq->fraction = array(1, 0); + $expectedq->feedback = array( + 0 => array( + 'text' => get_string('correct', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ), + 1 => array( + 'text' => get_string('incorrect', 'question'), + 'format' => FORMAT_HTML, + 'files' => array(), + ) + ); + + $this->assert(new question_check_specified_fields_expectation($expectedq), $q); + } + + public function test_import_essay() { + + $xml = $this->make_test_xml(); + $questions = array(); + + $importer = new qformat_examview(); + $questions = $importer->readquestions($xml); + $q = $questions[4]; + + $expectedq = new stdClass(); + $expectedq->qtype = 'essay'; + $expectedq->name = 'How are you?'; + $expectedq->questiontext = 'How are you?'; + $expectedq->questiontextformat = FORMAT_HTML; + $expectedq->generalfeedback = ''; + $expectedq->generalfeedbackformat = FORMAT_MOODLE; + $expectedq->defaultmark = 1; + $expectedq->length = 1; + $expectedq->responseformat = 'editor'; + $expectedq->responsefieldlines = 15; + $expectedq->attachments = 0; + $expectedq->graderinfo = array( + 'text' => 'Examview answer for essay questions will be imported as informations for graders.', + 'format' => FORMAT_HTML, + 'files' => array()); + + $this->assert(new question_check_specified_fields_expectation($expectedq), $q); + } + + // Due to the way matching questions are parsed, + // the test for matching questions is somewhat different. + // First we test the parse_matching_groups method alone. + // Then we test the whole process wich involve parse_matching_groups, + // parse_ma and process_matches methods. + public function test_parse_matching_groups() { + $lines = $this->make_test_xml(); + + $importer = new qformat_examview(); + $text = implode($lines, ' '); + + $xml = xmlize($text, 0); + $importer->parse_matching_groups($xml['examview']['#']['matching-group']); + $matching = $importer->matching_questions; + $group = new stdClass(); + $group->questiontext = 'Classify the animals.'; + $group->subchoices = array('A' => 'insect', 'B' => 'amphibian', 'C' =>'mammal'); + $group->subquestions = array(); + $group->subanswers = array(); + $expectedmatching = array( 'Matching 1' => $group); + + $this->assertEquals($matching, $expectedmatching); + } + + public function test_import_match() { + + $xml = $this->make_test_xml(); + $questions = array(); + + $importer = new qformat_examview(); + $questions = $importer->readquestions($xml); + $q = $questions[5]; + + $expectedq = new stdClass(); + $expectedq->qtype = 'match'; + $expectedq->name = 'Classify the animals.'; + $expectedq->questiontext = 'Classify the animals.'; + $expectedq->questiontextformat = FORMAT_HTML; + $expectedq->correctfeedback = array('text' => '', + 'format' => FORMAT_HTML, 'files' => array()); + $expectedq->partiallycorrectfeedback = array('text' => '', + 'format' => FORMAT_HTML, 'files' => array()); + $expectedq->incorrectfeedback = array('text' => '', + 'format' => FORMAT_HTML, 'files' => array()); + $expectedq->generalfeedback = ''; + $expectedq->generalfeedbackformat = FORMAT_MOODLE; + $expectedq->defaultmark = 1; + $expectedq->length = 1; + $expectedq->penalty = 0.3333333; + $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers'); + $expectedq->subquestions = array( + array('text' => '', 'format' => FORMAT_HTML, 'files' => array()), + array('text' => 'frog', 'format' => FORMAT_HTML, 'files' => array()), + array('text' => 'newt', 'format' => FORMAT_HTML, 'files' => array()), + array('text' => 'cat', 'format' => FORMAT_HTML, 'files' => array())); + $expectedq->subanswers = array('insect', 'amphibian', 'amphibian', 'mammal'); + + $this->assert(new question_check_specified_fields_expectation($expectedq), $q); + } +} diff --git a/question/format/examview/version.php b/question/format/examview/version.php index e32474d020d..d6686e1e214 100644 --- a/question/format/examview/version.php +++ b/question/format/examview/version.php @@ -17,8 +17,7 @@ /** * Version information for the calculated question type. * - * @package qformat - * @subpackage examview + * @package qformat_examview * @copyright 2011 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -- 2.43.0