Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 402 and 403]

   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 core_question;
  18  
  19  use qubaid_list;
  20  use question_bank;
  21  use question_engine;
  22  
  23  /**
  24   * Tests for the {@see core_question\local\bank\random_question_loader} class.
  25   *
  26   * @package   core_question
  27   * @copyright  2015 The Open University
  28   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   */
  30  class random_question_loader_test extends \advanced_testcase {
  31  
  32      public function test_empty_category_gives_null() {
  33          $this->resetAfterTest();
  34          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
  35  
  36          $cat = $generator->create_question_category();
  37          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
  38  
  39          $this->assertNull($loader->get_next_question_id($cat->id, 0));
  40          $this->assertNull($loader->get_next_question_id($cat->id, 1));
  41      }
  42  
  43      public function test_unknown_category_behaves_like_empty() {
  44          // It is up the caller to make sure the category id is valid.
  45          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
  46          $this->assertNull($loader->get_next_question_id(-1, 1));
  47      }
  48  
  49      public function test_descriptions_not_returned() {
  50          $this->resetAfterTest();
  51          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
  52  
  53          $cat = $generator->create_question_category();
  54          $info = $generator->create_question('description', null, ['category' => $cat->id]);
  55          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
  56  
  57          $this->assertNull($loader->get_next_question_id($cat->id, 0));
  58      }
  59  
  60      public function test_hidden_questions_not_returned() {
  61          global $DB;
  62          $this->resetAfterTest();
  63          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
  64  
  65          $cat = $generator->create_question_category();
  66          $question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
  67          $DB->set_field('question_versions', 'status',
  68              \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN, ['questionid' => $question1->id]);
  69          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
  70  
  71          $this->assertNull($loader->get_next_question_id($cat->id, 0));
  72      }
  73  
  74      public function test_cloze_subquestions_not_returned() {
  75          $this->resetAfterTest();
  76          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
  77  
  78          $cat = $generator->create_question_category();
  79          $question1 = $generator->create_question('multianswer', null, ['category' => $cat->id]);
  80          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
  81  
  82          $this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 0));
  83          $this->assertNull($loader->get_next_question_id($cat->id, 0));
  84      }
  85  
  86      public function test_random_questions_not_returned() {
  87          $this->resetAfterTest();
  88          $this->setAdminUser();
  89          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
  90  
  91          $cat = $generator->create_question_category();
  92          $course = $this->getDataGenerator()->create_course();
  93          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course]);
  94          quiz_add_random_questions($quiz, 1, $cat->id, 1, false);
  95          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
  96  
  97          $this->assertNull($loader->get_next_question_id($cat->id, 0));
  98      }
  99  
 100      public function test_one_question_category_returns_that_q_then_null() {
 101          $this->resetAfterTest();
 102          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 103  
 104          $cat = $generator->create_question_category();
 105          $question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
 106          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 107  
 108          $this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 1));
 109          $this->assertNull($loader->get_next_question_id($cat->id, 0));
 110      }
 111  
 112      public function test_two_question_category_returns_both_then_null() {
 113          $this->resetAfterTest();
 114          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 115  
 116          $cat = $generator->create_question_category();
 117          $question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
 118          $question2 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
 119          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 120  
 121          $questionids = [];
 122          $questionids[] = $loader->get_next_question_id($cat->id, 0);
 123          $questionids[] = $loader->get_next_question_id($cat->id, 0);
 124          sort($questionids);
 125          $this->assertEquals([$question1->id, $question2->id], $questionids);
 126  
 127          $this->assertNull($loader->get_next_question_id($cat->id, 1));
 128      }
 129  
 130      public function test_nested_categories() {
 131          $this->resetAfterTest();
 132          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 133  
 134          $cat1 = $generator->create_question_category();
 135          $cat2 = $generator->create_question_category(['parent' => $cat1->id]);
 136          $question1 = $generator->create_question('shortanswer', null, ['category' => $cat1->id]);
 137          $question2 = $generator->create_question('shortanswer', null, ['category' => $cat2->id]);
 138          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 139  
 140          $this->assertEquals($question2->id, $loader->get_next_question_id($cat2->id, 1));
 141          $this->assertEquals($question1->id, $loader->get_next_question_id($cat1->id, 1));
 142  
 143          $this->assertNull($loader->get_next_question_id($cat1->id, 0));
 144      }
 145  
 146      public function test_used_question_not_returned_until_later() {
 147          $this->resetAfterTest();
 148          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 149  
 150          $cat = $generator->create_question_category();
 151          $question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
 152          $question2 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
 153          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]),
 154                  array($question2->id => 2));
 155  
 156          $this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 0));
 157          $this->assertNull($loader->get_next_question_id($cat->id, 0));
 158      }
 159  
 160      public function test_previously_used_question_not_returned_until_later() {
 161          $this->resetAfterTest();
 162          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 163  
 164          $cat = $generator->create_question_category();
 165          $question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
 166          $question2 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
 167          $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
 168          $quba->set_preferred_behaviour('deferredfeedback');
 169          $question = question_bank::load_question($question2->id);
 170          $quba->add_question($question);
 171          $quba->add_question($question);
 172          $quba->start_all_questions();
 173          question_engine::save_questions_usage_by_activity($quba);
 174  
 175          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array($quba->get_id())));
 176  
 177          $this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 0));
 178          $this->assertEquals($question2->id, $loader->get_next_question_id($cat->id, 0));
 179          $this->assertNull($loader->get_next_question_id($cat->id, 0));
 180      }
 181  
 182      public function test_empty_category_does_not_have_question_available() {
 183          $this->resetAfterTest();
 184          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 185  
 186          $cat = $generator->create_question_category();
 187          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array()));
 188  
 189          $this->assertFalse($loader->is_question_available($cat->id, 0, 1));
 190          $this->assertFalse($loader->is_question_available($cat->id, 1, 1));
 191      }
 192  
 193      public function test_descriptions_not_available() {
 194          $this->resetAfterTest();
 195          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 196  
 197          $cat = $generator->create_question_category();
 198          $info = $generator->create_question('description', null, array('category' => $cat->id));
 199          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array()));
 200  
 201          $this->assertFalse($loader->is_question_available($cat->id, 0, $info->id));
 202          $this->assertFalse($loader->is_question_available($cat->id, 1, $info->id));
 203      }
 204  
 205      public function test_existing_question_is_available_but_then_marked_used() {
 206          $this->resetAfterTest();
 207          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 208  
 209          $cat = $generator->create_question_category();
 210          $question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
 211          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array()));
 212  
 213          $this->assertTrue($loader->is_question_available($cat->id, 0, $question1->id));
 214          $this->assertFalse($loader->is_question_available($cat->id, 0, $question1->id));
 215  
 216          $this->assertFalse($loader->is_question_available($cat->id, 0, -1));
 217      }
 218  
 219      /**
 220       * Data provider for the get_questions test.
 221       */
 222      public function get_questions_test_cases() {
 223          return [
 224                  'empty category' => [
 225                          'categoryindex' => 'emptycat',
 226                          'includesubcategories' => false,
 227                          'usetagnames' => [],
 228                          'expectedquestionindexes' => []
 229                  ],
 230                  'single category' => [
 231                          'categoryindex' => 'cat1',
 232                          'includesubcategories' => false,
 233                          'usetagnames' => [],
 234                          'expectedquestionindexes' => ['cat1q1', 'cat1q2']
 235                  ],
 236                  'include sub category' => [
 237                          'categoryindex' => 'cat1',
 238                          'includesubcategories' => true,
 239                          'usetagnames' => [],
 240                          'expectedquestionindexes' => ['cat1q1', 'cat1q2', 'subcatq1', 'subcatq2']
 241                  ],
 242                  'single category with tags' => [
 243                          'categoryindex' => 'cat1',
 244                          'includesubcategories' => false,
 245                          'usetagnames' => ['cat1'],
 246                          'expectedquestionindexes' => ['cat1q1']
 247                  ],
 248                  'include sub category with tag on parent' => [
 249                          'categoryindex' => 'cat1',
 250                          'includesubcategories' => true,
 251                          'usetagnames' => ['cat1'],
 252                          'expectedquestionindexes' => ['cat1q1']
 253                  ],
 254                  'include sub category with tag on sub' => [
 255                          'categoryindex' => 'cat1',
 256                          'includesubcategories' => true,
 257                          'usetagnames' => ['subcat'],
 258                          'expectedquestionindexes' => ['subcatq1']
 259                  ],
 260                  'include sub category with same tag on parent and sub' => [
 261                          'categoryindex' => 'cat1',
 262                          'includesubcategories' => true,
 263                          'usetagnames' => ['foo'],
 264                          'expectedquestionindexes' => ['cat1q1', 'subcatq1']
 265                  ],
 266                  'include sub category with tag not matching' => [
 267                          'categoryindex' => 'cat1',
 268                          'includesubcategories' => true,
 269                          'usetagnames' => ['cat1', 'cat2'],
 270                          'expectedquestionindexes' => []
 271                  ]
 272          ];
 273      }
 274  
 275      /**
 276       * Test the get_questions function with various parameter combinations.
 277       *
 278       * This function creates a data set as follows:
 279       *      Category: cat1
 280       *          Question: cat1q1
 281       *              Tags: 'cat1', 'foo'
 282       *          Question: cat1q2
 283       *      Category: cat2
 284       *          Question: cat2q1
 285       *              Tags: 'cat2', 'foo'
 286       *          Question: cat2q2
 287       *      Category: subcat
 288       *          Question: subcatq1
 289       *              Tags: 'subcat', 'foo'
 290       *          Question: subcatq2
 291       *          Parent: cat1
 292       *      Category: emptycat
 293       *
 294       * @dataProvider get_questions_test_cases()
 295       * @param string $categoryindex The named index for the category to use
 296       * @param bool $includesubcategories If the search should include subcategories
 297       * @param string[] $usetagnames The tag names to include in the search
 298       * @param string[] $expectedquestionindexes The questions expected in the result
 299       */
 300      public function test_get_questions_variations(
 301              $categoryindex,
 302              $includesubcategories,
 303              $usetagnames,
 304              $expectedquestionindexes
 305      ) {
 306          $this->resetAfterTest();
 307  
 308          $categories = [];
 309          $questions = [];
 310          $tagnames = [
 311                  'cat1',
 312                  'cat2',
 313                  'subcat',
 314                  'foo'
 315          ];
 316          $collid = \core_tag_collection::get_default();
 317          $tags = \core_tag_tag::create_if_missing($collid, $tagnames);
 318          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 319  
 320          // First category and questions.
 321          list($category, $categoryquestions) = $this->create_category_and_questions(2, ['cat1', 'foo']);
 322          $categories['cat1'] = $category;
 323          $questions['cat1q1'] = $categoryquestions[0];
 324          $questions['cat1q2'] = $categoryquestions[1];
 325          // Second category and questions.
 326          list($category, $categoryquestions) = $this->create_category_and_questions(2, ['cat2', 'foo']);
 327          $categories['cat2'] = $category;
 328          $questions['cat2q1'] = $categoryquestions[0];
 329          $questions['cat2q2'] = $categoryquestions[1];
 330          // Sub category and questions.
 331          list($category, $categoryquestions) = $this->create_category_and_questions(2, ['subcat', 'foo'], $categories['cat1']);
 332          $categories['subcat'] = $category;
 333          $questions['subcatq1'] = $categoryquestions[0];
 334          $questions['subcatq2'] = $categoryquestions[1];
 335          // Empty category.
 336          list($category, $categoryquestions) = $this->create_category_and_questions(0);
 337          $categories['emptycat'] = $category;
 338  
 339          // Generate the arguments for the get_questions function.
 340          $category = $categories[$categoryindex];
 341          $tagids = array_map(function($tagname) use ($tags) {
 342              return $tags[$tagname]->id;
 343          }, $usetagnames);
 344  
 345          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 346          $result = $loader->get_questions($category->id, $includesubcategories, $tagids);
 347          // Generate the expected question set.
 348          $expectedquestions = array_map(function($index) use ($questions) {
 349              return $questions[$index];
 350          }, $expectedquestionindexes);
 351  
 352          // Ensure the result matches what was expected.
 353          $this->assertCount(count($expectedquestions), $result);
 354          foreach ($expectedquestions as $question) {
 355              $this->assertEquals($result[$question->id]->id, $question->id);
 356              $this->assertEquals($result[$question->id]->category, $question->category);
 357          }
 358      }
 359  
 360      /**
 361       * get_questions should allow limiting and offsetting of the result set.
 362       */
 363      public function test_get_questions_with_limit_and_offset() {
 364          $this->resetAfterTest();
 365          $numberofquestions = 5;
 366          $includesubcategories = false;
 367          $tagids = [];
 368          $limit = 1;
 369          $offset = 0;
 370          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 371          list($category, $questions) = $this->create_category_and_questions($numberofquestions);
 372  
 373          // Add questionid as key to find them easily later.
 374          $questionsbyid = [];
 375          array_walk($questions, function (&$value) use (&$questionsbyid) {
 376              $questionsbyid[$value->id] = $value;
 377          });
 378  
 379          for ($i = 0; $i < $numberofquestions; $i++) {
 380              $result = $loader->get_questions(
 381                      $category->id,
 382                      $includesubcategories,
 383                      $tagids,
 384                      $limit,
 385                      $offset
 386              );
 387  
 388              $this->assertCount($limit, $result);
 389              $actual = array_shift($result);
 390              $expected = $questionsbyid[$actual->id];
 391              $this->assertEquals($expected->id, $actual->id);
 392              $offset++;
 393          }
 394      }
 395  
 396      /**
 397       * get_questions should allow retrieving questions with only a subset of
 398       * fields populated.
 399       */
 400      public function test_get_questions_with_restricted_fields() {
 401          $this->resetAfterTest();
 402          $includesubcategories = false;
 403          $tagids = [];
 404          $limit = 10;
 405          $offset = 0;
 406          $fields = ['id', 'name'];
 407          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 408          list($category, $questions) = $this->create_category_and_questions(1);
 409  
 410          $result = $loader->get_questions(
 411                  $category->id,
 412                  $includesubcategories,
 413                  $tagids,
 414                  $limit,
 415                  $offset,
 416                  $fields
 417          );
 418  
 419          $expectedquestion = array_shift($questions);
 420          $actualquestion = array_shift($result);
 421          $actualfields = get_object_vars($actualquestion);
 422          $actualfields = array_keys($actualfields);
 423          sort($actualfields);
 424          sort($fields);
 425  
 426          $this->assertEquals($fields, $actualfields);
 427      }
 428  
 429      /**
 430       * Data provider for the count_questions test.
 431       */
 432      public function count_questions_test_cases() {
 433          return [
 434                  'empty category' => [
 435                          'categoryindex' => 'emptycat',
 436                          'includesubcategories' => false,
 437                          'usetagnames' => [],
 438                          'expectedcount' => 0
 439                  ],
 440                  'single category' => [
 441                          'categoryindex' => 'cat1',
 442                          'includesubcategories' => false,
 443                          'usetagnames' => [],
 444                          'expectedcount' => 2
 445                  ],
 446                  'include sub category' => [
 447                          'categoryindex' => 'cat1',
 448                          'includesubcategories' => true,
 449                          'usetagnames' => [],
 450                          'expectedcount' => 4
 451                  ],
 452                  'single category with tags' => [
 453                          'categoryindex' => 'cat1',
 454                          'includesubcategories' => false,
 455                          'usetagnames' => ['cat1'],
 456                          'expectedcount' => 1
 457                  ],
 458                  'include sub category with tag on parent' => [
 459                          'categoryindex' => 'cat1',
 460                          'includesubcategories' => true,
 461                          'usetagnames' => ['cat1'],
 462                          'expectedcount' => 1
 463                  ],
 464                  'include sub category with tag on sub' => [
 465                          'categoryindex' => 'cat1',
 466                          'includesubcategories' => true,
 467                          'usetagnames' => ['subcat'],
 468                          'expectedcount' => 1
 469                  ],
 470                  'include sub category with same tag on parent and sub' => [
 471                          'categoryindex' => 'cat1',
 472                          'includesubcategories' => true,
 473                          'usetagnames' => ['foo'],
 474                          'expectedcount' => 2
 475                  ],
 476                  'include sub category with tag not matching' => [
 477                          'categoryindex' => 'cat1',
 478                          'includesubcategories' => true,
 479                          'usetagnames' => ['cat1', 'cat2'],
 480                          'expectedcount' => 0
 481                  ]
 482          ];
 483      }
 484  
 485      /**
 486       * Test the count_questions function with various parameter combinations.
 487       *
 488       * This function creates a data set as follows:
 489       *      Category: cat1
 490       *          Question: cat1q1
 491       *              Tags: 'cat1', 'foo'
 492       *          Question: cat1q2
 493       *      Category: cat2
 494       *          Question: cat2q1
 495       *              Tags: 'cat2', 'foo'
 496       *          Question: cat2q2
 497       *      Category: subcat
 498       *          Question: subcatq1
 499       *              Tags: 'subcat', 'foo'
 500       *          Question: subcatq2
 501       *          Parent: cat1
 502       *      Category: emptycat
 503       *
 504       * @dataProvider count_questions_test_cases()
 505       * @param string $categoryindex The named index for the category to use
 506       * @param bool $includesubcategories If the search should include subcategories
 507       * @param string[] $usetagnames The tag names to include in the search
 508       * @param int $expectedcount The number of questions expected in the result
 509       */
 510      public function test_count_questions_variations(
 511              $categoryindex,
 512              $includesubcategories,
 513              $usetagnames,
 514              $expectedcount
 515      ) {
 516          $this->resetAfterTest();
 517  
 518          $categories = [];
 519          $questions = [];
 520          $tagnames = [
 521                  'cat1',
 522                  'cat2',
 523                  'subcat',
 524                  'foo'
 525          ];
 526          $collid = \core_tag_collection::get_default();
 527          $tags = \core_tag_tag::create_if_missing($collid, $tagnames);
 528          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 529  
 530          // First category and questions.
 531          list($category, $categoryquestions) = $this->create_category_and_questions(2, ['cat1', 'foo']);
 532          $categories['cat1'] = $category;
 533          $questions['cat1q1'] = $categoryquestions[0];
 534          $questions['cat1q2'] = $categoryquestions[1];
 535          // Second category and questions.
 536          list($category, $categoryquestions) = $this->create_category_and_questions(2, ['cat2', 'foo']);
 537          $categories['cat2'] = $category;
 538          $questions['cat2q1'] = $categoryquestions[0];
 539          $questions['cat2q2'] = $categoryquestions[1];
 540          // Sub category and questions.
 541          list($category, $categoryquestions) = $this->create_category_and_questions(2, ['subcat', 'foo'], $categories['cat1']);
 542          $categories['subcat'] = $category;
 543          $questions['subcatq1'] = $categoryquestions[0];
 544          $questions['subcatq2'] = $categoryquestions[1];
 545          // Empty category.
 546          list($category, $categoryquestions) = $this->create_category_and_questions(0);
 547          $categories['emptycat'] = $category;
 548  
 549          // Generate the arguments for the get_questions function.
 550          $category = $categories[$categoryindex];
 551          $tagids = array_map(function($tagname) use ($tags) {
 552              return $tags[$tagname]->id;
 553          }, $usetagnames);
 554  
 555          $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
 556          $result = $loader->count_questions($category->id, $includesubcategories, $tagids);
 557  
 558          // Ensure the result matches what was expected.
 559          $this->assertEquals($expectedcount, $result);
 560      }
 561  
 562      /**
 563       * Create a question category and create questions in that category. Tag
 564       * the first question in each category with the given tags.
 565       *
 566       * @param int $questioncount How many questions to create.
 567       * @param string[] $tagnames The list of tags to use.
 568       * @param stdClass|null $parentcategory The category to set as the parent of the created category.
 569       * @return array The category and questions.
 570       */
 571      protected function create_category_and_questions($questioncount, $tagnames = [], $parentcategory = null) {
 572          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 573  
 574          if ($parentcategory) {
 575              $catparams = ['parent' => $parentcategory->id];
 576          } else {
 577              $catparams = [];
 578          }
 579  
 580          $category = $generator->create_question_category($catparams);
 581          $questions = [];
 582  
 583          for ($i = 0; $i < $questioncount; $i++) {
 584              $questions[] = $generator->create_question('shortanswer', null, ['category' => $category->id]);
 585          }
 586  
 587          if (!empty($tagnames) && !empty($questions)) {
 588              $context = \context::instance_by_id($category->contextid);
 589              \core_tag_tag::set_item_tags('core_question', 'question', $questions[0]->id, $context, $tagnames);
 590          }
 591  
 592          return [$category, $questions];
 593      }
 594  }