Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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