Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace qtype_multianswer;
18
 
19
use question_attempt_step;
20
use question_display_options;
21
use question_state;
22
use question_testcase;
23
 
24
defined('MOODLE_INTERNAL') || die();
25
 
26
global $CFG;
27
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
28
 
29
 
30
/**
31
 * Unit tests for qtype_multianswer_question.
32
 *
33
 * @package    qtype_multianswer
34
 * @copyright  2011 The Open University
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 * @covers \qtype_multianswer_question
37
 */
1441 ariadna 38
final class question_test extends \advanced_testcase {
11 efrain 39
    public function test_get_expected_data(): void {
1 efrain 40
        $question = \test_question_maker::make_question('multianswer');
41
        $this->assertEquals(array('sub1_answer' => PARAM_RAW_TRIMMED,
42
                'sub2_answer' => PARAM_RAW), $question->get_expected_data());
43
    }
44
 
11 efrain 45
    public function test_is_complete_response(): void {
1 efrain 46
        $question = \test_question_maker::make_question('multianswer');
47
 
48
        $this->assertFalse($question->is_complete_response(array()));
49
        $this->assertTrue($question->is_complete_response(array('sub1_answer' => 'Owl',
50
                'sub2_answer' => '2')));
51
        $this->assertTrue($question->is_complete_response(array('sub1_answer' => '0',
52
                'sub2_answer' => 0)));
53
        $this->assertFalse($question->is_complete_response(array('sub1_answer' => 'Owl')));
54
    }
55
 
11 efrain 56
    public function test_is_gradable_response(): void {
1 efrain 57
        $question = \test_question_maker::make_question('multianswer');
58
 
59
        $this->assertFalse($question->is_gradable_response(array()));
60
        $this->assertTrue($question->is_gradable_response(array('sub1_answer' => 'Owl',
61
                'sub2_answer' => '2')));
62
        $this->assertTrue($question->is_gradable_response(array('sub1_answer' => '0',
63
                'sub2_answer' => 0)));
64
        $this->assertTrue($question->is_gradable_response(array('sub1_answer' => 'Owl')));
65
    }
66
 
11 efrain 67
    public function test_grading(): void {
1 efrain 68
        $question = \test_question_maker::make_question('multianswer');
69
        $question->start_attempt(new question_attempt_step(), 1);
70
 
71
        $rightchoice = $question->subquestions[2]->get_correct_response();
72
 
73
        $this->assertEquals(array(1, question_state::$gradedright), $question->grade_response(
74
                array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice))));
75
        $this->assertEquals(array(0.5, question_state::$gradedpartial), $question->grade_response(
76
                array('sub1_answer' => 'Owl')));
77
        $this->assertEquals(array(0.5, question_state::$gradedpartial), $question->grade_response(
78
                array('sub1_answer' => 'Goat', 'sub2_answer' => reset($rightchoice))));
79
        $this->assertEquals(array(0, question_state::$gradedwrong), $question->grade_response(
80
                array('sub1_answer' => 'Dog')));
81
    }
82
 
11 efrain 83
    public function test_get_correct_response(): void {
1 efrain 84
        $question = \test_question_maker::make_question('multianswer');
85
        $question->start_attempt(new question_attempt_step(), 1);
86
 
87
        $rightchoice = $question->subquestions[2]->get_correct_response();
88
 
89
        $this->assertEquals(array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice)),
90
                $question->get_correct_response());
91
    }
92
 
11 efrain 93
    public function test_get_question_summary(): void {
1 efrain 94
        $question = \test_question_maker::make_question('multianswer');
95
 
96
        // Bit of a hack to make testing easier.
97
        $question->subquestions[2]->shuffleanswers = false;
98
 
99
        $question->start_attempt(new question_attempt_step(), 1);
100
 
101
        $qsummary = $question->get_question_summary();
102
        $this->assertEquals('Complete this opening line of verse: "The _____ and the ' .
103
                '{Bow-wow; Wiggly worm; Pussy-cat} went to sea".', $qsummary);
104
    }
105
 
11 efrain 106
    public function test_summarise_response(): void {
1 efrain 107
        $question = \test_question_maker::make_question('multianswer');
108
        $question->start_attempt(new question_attempt_step(), 1);
109
 
110
        $rightchoice = $question->subquestions[2]->get_correct_response();
111
 
112
        $this->assertEquals(get_string('subqresponse', 'qtype_multianswer',
113
                array('i' => 1, 'response' => 'Owl')) . '; ' .
114
                get_string('subqresponse', 'qtype_multianswer',
115
                array('i' => 2, 'response' => 'Pussy-cat')), $question->summarise_response(
116
                array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice))));
117
    }
118
 
11 efrain 119
    public function test_get_num_parts_right(): void {
1 efrain 120
        $question = \test_question_maker::make_question('multianswer');
121
        $question->start_attempt(new question_attempt_step(), 1);
122
 
123
        $rightchoice = $question->subquestions[2]->get_correct_response();
124
        $right = reset($rightchoice);
125
 
126
        $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right);
127
        list($numpartsright, $numparts) = $question->get_num_parts_right($response);
128
        $this->assertEquals(1, $numpartsright);
129
        $this->assertEquals(2, $numparts);
130
        $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right);
131
        list($numpartsright, $numparts) = $question->get_num_parts_right($response);
132
        $this->assertEquals(2, $numpartsright);
133
        $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3);
134
        list($numpartsright, $numparts) = $question->get_num_parts_right($response);
135
        $this->assertEquals(0, $numpartsright);
136
        $response = array('sub1_answer' => 'Owl');
137
        list($numpartsright, $numparts) = $question->get_num_parts_right($response);
138
        $this->assertEquals(1, $numpartsright);
139
        $response = array('sub1_answer' => 'Dog');
140
        list($numpartsright, $numparts) = $question->get_num_parts_right($response);
141
        $this->assertEquals(0, $numpartsright);
142
        $response = array('sub2_answer' => $right);
143
        list($numpartsright, $numparts) = $question->get_num_parts_right($response);
144
        $this->assertEquals(1, $numpartsright);
145
    }
146
 
11 efrain 147
    public function test_get_num_parts_right_fourmc(): void {
1 efrain 148
        // Create a multianswer question with four mcq.
149
        $question = \test_question_maker::make_question('multianswer', 'fourmc');
150
        $question->start_attempt(new question_attempt_step(), 1);
151
 
152
        $response = array('sub1_answer' => '1', 'sub2_answer' => '1',
153
                'sub3_answer' => '1', 'sub4_answer' => '1');
154
        list($numpartsright, $numparts) = $question->get_num_parts_right($response);
155
        $this->assertEquals(2, $numpartsright);
156
    }
157
 
11 efrain 158
    public function test_clear_wrong_from_response(): void {
1 efrain 159
        $question = \test_question_maker::make_question('multianswer');
160
        $question->start_attempt(new question_attempt_step(), 1);
161
 
162
        $rightchoice = $question->subquestions[2]->get_correct_response();
163
        $right = reset($rightchoice);
164
 
165
        $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right);
166
        $this->assertEquals($question->clear_wrong_from_response($response),
167
                array('sub1_answer' => '', 'sub2_answer' => $right));
168
        $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right);
169
        $this->assertEquals($question->clear_wrong_from_response($response),
170
                array('sub1_answer' => 'Owl', 'sub2_answer' => $right));
171
        $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3);
172
        $this->assertEquals($question->clear_wrong_from_response($response),
173
                array('sub1_answer' => '', 'sub2_answer' => ''));
174
        $response = array('sub1_answer' => 'Owl');
175
        $this->assertEquals($question->clear_wrong_from_response($response),
176
                array('sub1_answer' => 'Owl'));
177
        $response = array('sub2_answer' => $right);
178
        $this->assertEquals($question->clear_wrong_from_response($response),
179
                array('sub2_answer' => $right));
180
    }
181
 
11 efrain 182
    public function test_compute_final_grade(): void {
1 efrain 183
        $question = \test_question_maker::make_question('multianswer');
184
        // Set penalty to 0.2 to ease calculations.
185
        $question->penalty = 0.2;
186
        // Set subquestion 2 defaultmark to 2, to make it a better test,
187
        // even thought (at the moment) that never happens for real.
188
        $question->subquestions[2]->defaultmark = 2;
189
 
190
        $question->start_attempt(new question_attempt_step(), 1);
191
 
192
        // Compute right and wrong response for subquestion 2.
193
        $rightchoice = $question->subquestions[2]->get_correct_response();
194
        $right = reset($rightchoice);
195
        $wrong = ($right + 1) % 3;
196
 
197
        // Get subquestion 1 right at 2nd try and subquestion 2 right at 3rd try.
198
        $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong),
199
                           1 => array('sub1_answer' => 'Owl', 'sub2_answer' => $wrong),
200
                           2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
201
                          );
202
        $finalgrade = $question->compute_final_grade($responses, 1);
203
        $this->assertEqualsWithDelta(1 / 3 * (1 - 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA);
204
 
205
        // Get subquestion 1 right at 3rd try and subquestion 2 right at 2nd try.
206
        $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $wrong),
207
                           1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $right),
208
                           2 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
209
                           3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
210
                          );
211
        $finalgrade = $question->compute_final_grade($responses, 1);
212
        $this->assertEqualsWithDelta(1 / 3 * (1 - 2 * 0.2) + 2 / 3 * (1 - 0.2), $finalgrade, question_testcase::GRADE_DELTA);
213
 
214
        // Get subquestion 1 right at 4th try and subquestion 2 right at 1st try.
215
        $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
216
                           1 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
217
                           2 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
218
                           3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
219
                          );
220
        $finalgrade = $question->compute_final_grade($responses, 1);
221
        $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3, $finalgrade, question_testcase::GRADE_DELTA);
222
 
223
        // Get subquestion 1 right at 4th try and subquestion 2 right 3rd try.
224
        // Subquestion 2 was right at 1st try, but last change is at 3rd try.
225
        $responses = array(0 => array('sub1_answer' => 'Dog', 'sub2_answer' => $right),
226
                           1 => array('sub1_answer' => 'Cat', 'sub2_answer' => $wrong),
227
                           2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right),
228
                           3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
229
                          );
230
        $finalgrade = $question->compute_final_grade($responses, 1);
231
        $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA);
232
 
233
        // Incomplete responses. Subquestion 1 is right at 4th try and subquestion 2 at 3rd try.
234
        $responses = array(0 => array('sub1_answer' => 'Dog'),
235
                           1 => array('sub1_answer' => 'Cat'),
236
                           2 => array('sub1_answer' => 'Frog', 'sub2_answer' => $right),
237
                           3 => array('sub1_answer' => 'Owl', 'sub2_answer' => $right),
238
                          );
239
        $finalgrade = $question->compute_final_grade($responses, 1);
240
        $this->assertEqualsWithDelta(1 / 3 * (1 - 3 * 0.2) + 2 / 3 * (1 - 2 * 0.2), $finalgrade, question_testcase::GRADE_DELTA);
241
    }
242
 
243
    /**
244
     * test_get_question_definition_for_external_rendering
245
     */
11 efrain 246
    public function test_get_question_definition_for_external_rendering(): void {
1 efrain 247
        $this->resetAfterTest();
248
 
249
        $question = \test_question_maker::make_question('multianswer');
250
        $question->start_attempt(new question_attempt_step(), 1);
251
        $qa = \test_question_maker::get_a_qa($question);
252
        $displayoptions = new question_display_options();
253
 
254
        $options = $question->get_question_definition_for_external_rendering($qa, $displayoptions);
255
        $this->assertNull($options);
256
    }
257
 
258
    /**
259
     * Helper method to make a simulated second version of the standard multianswer test question.
260
     *
261
     * The key think is that all the answer ids are changed (increased by 20).
262
     *
263
     * @param \qtype_multianswer_question $question
264
     * @return \qtype_multianswer_question
265
     */
266
    protected function make_second_version(
267
            \qtype_multianswer_question $question): \qtype_multianswer_question {
268
        $newquestion = fullclone($question);
269
 
270
        $newquestion->subquestions[1]->answers = [
271
            36 => new \question_answer(16, 'Apple', 0.3333333,
272
                                      'Good', FORMAT_HTML),
273
            37 => new \question_answer(17, 'Burger', -0.5,
274
                                      '', FORMAT_HTML),
275
            38 => new \question_answer(18, 'Hot dog', -0.5,
276
                                      'Not a fruit', FORMAT_HTML),
277
            39 => new \question_answer(19, 'Pizza', -0.5,
278
                                      '', FORMAT_HTML),
279
            40 => new \question_answer(20, 'Orange', 0.3333333,
280
                                      'Correct', FORMAT_HTML),
281
            41 => new \question_answer(21, 'Banana', 0.3333333,
282
                                      '', FORMAT_HTML),
283
        ];
284
 
285
        $newquestion->subquestions[2]->answers = [
286
            42 => new \question_answer(22, 'Raddish', 0.5,
287
                                      'Good', FORMAT_HTML),
288
            43 => new \question_answer(23, 'Chocolate', -0.5,
289
                                      '', FORMAT_HTML),
290
            44 => new \question_answer(24, 'Biscuit', -0.5,
291
                                      'Not a vegetable', FORMAT_HTML),
292
            45 => new \question_answer(25, 'Cheese', -0.5,
293
                                      '', FORMAT_HTML),
294
            46 => new \question_answer(26, 'Carrot', 0.5,
295
                                      'Correct', FORMAT_HTML),
296
        ];
297
 
298
        return $newquestion;
299
    }
300
 
11 efrain 301
    public function test_validate_can_regrade_with_other_version_ok(): void {
1 efrain 302
        /** @var \qtype_multianswer_question $question */
303
        $question = \test_question_maker::make_question('multianswer', 'multiple');
304
 
305
        $newquestion = $this->make_second_version($question);
306
 
307
        $this->assertNull($newquestion->validate_can_regrade_with_other_version($question));
308
    }
309
 
11 efrain 310
    public function test_validate_can_regrade_with_other_version_wrong_subquestions(): void {
1 efrain 311
        /** @var \qtype_multianswer_question $question */
312
        $question = \test_question_maker::make_question('multianswer', 'multiple');
313
 
314
        $newquestion = $this->make_second_version($question);
315
        unset($newquestion->subquestions[2]);
316
 
317
        $this->assertEquals(
318
                get_string('regradeissuenumsubquestionschanged', 'qtype_multianswer'),
319
                $newquestion->validate_can_regrade_with_other_version($question));
320
    }
321
 
11 efrain 322
    public function test_validate_can_regrade_with_other_version_one_wrong_subquestion(): void {
1 efrain 323
        /** @var \qtype_multianswer_question $question */
324
        $question = \test_question_maker::make_question('multianswer', 'multiple');
325
 
326
        $newquestion = $this->make_second_version($question);
327
        unset($newquestion->subquestions[1]->answers[41]);
328
 
329
        $this->assertEquals(
330
                get_string('regradeissuenumchoiceschanged', 'qtype_multichoice'),
331
                $newquestion->validate_can_regrade_with_other_version($question));
332
    }
333
 
11 efrain 334
    public function test_update_attempt_state_date_from_old_version_ok(): void {
1 efrain 335
        /** @var \qtype_multianswer_question $question */
336
        $question = \test_question_maker::make_question('multianswer', 'multiple');
337
 
338
        $newquestion = $this->make_second_version($question);
339
 
340
        $oldstep = new question_attempt_step();
341
        $oldstep->set_qt_var('_sub1_order', '16,17,18,19,20,21');
342
        $oldstep->set_qt_var('_sub2_order', '22,23,24,25,26');
343
 
344
        $expected = [
345
            '_sub1_order' => '36,37,38,39,40,41',
346
            '_sub2_order' => '42,43,44,45,46',
347
        ];
348
 
349
        $this->assertEquals($expected,
350
                $newquestion->update_attempt_state_data_for_new_version($oldstep, $question));
351
    }
352
 
353
    /**
354
     * Test functions work with zero weight.
355
     * This is used for testing the MDL-77378 bug.
356
     */
11 efrain 357
    public function test_zeroweight(): void {
1 efrain 358
        $this->resetAfterTest();
359
        /** @var \qtype_multianswer_question $question */
360
        $question = \test_question_maker::make_question('multianswer', 'zeroweight');
361
        $question->start_attempt(new question_attempt_step(), 1);
362
 
363
        $this->assertEquals([null, question_state::$gradedright], $question->grade_response(
364
            ['sub1_answer' => 'Something']));
365
        $this->assertEquals([null, question_state::$gradedwrong], $question->grade_response(
366
            ['sub1_answer' => 'Input box']));
367
 
368
        $this->assertEquals(1, $question->get_max_fraction());
369
        $this->assertEquals(0, $question->get_min_fraction());
1441 ariadna 370
        $this->assertNull($question->compute_final_grade([['sub1_answer' => 'Something']], 1));
371
        $this->assertNull($question->grade_response([['sub1_answer' => 'Something']])[0]);
1 efrain 372
    }
373
 
374
}