MDL-80096 tool_generator: execute behat generators in current instance

This is a new feature in tool_generator to execute a feature file
containing only generator steps. This new tool can be used to prepared
testing scenarios for complex issues that requires manual testing.
This commit is contained in:
Ferran Recio
2024-01-08 16:47:09 +01:00
parent 9587029a46
commit b9f78ef45c
20 changed files with 1785 additions and 1 deletions
@@ -0,0 +1,60 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_generator\form;
defined('MOODLE_INTERNAL') || die();
use moodleform;
global $CFG;
require_once($CFG->dirroot . '/lib/formslib.php');
/**
* Form for importting a testing scenario feature file.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class featureimport extends moodleform {
public function definition(): void {
$mform = &$this->_form;
// File upload.
$mform->addElement(
'filepicker',
'featurefile',
get_string('testscenario_file', 'tool_generator'),
null,
['accepted_types' => ['.feature']]
);
$mform->addRule('featurefile', null, 'required');
$this->add_action_buttons(false, get_string('import'));
}
/**
* Get the feature file contents.
* @return string|null the feature file contents or null if not found.
*/
public function get_feature_contents(): ?string {
$result = $this->get_file_content('featurefile');
if (!$result) {
return null;
}
return $result;
}
}
@@ -0,0 +1,123 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
use stdClass;
/**
* Class with a scenario feature parsed.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class parsedfeature {
/** @var int the number of steps. */
private int $stepcount = 0;
/** @var bool if the parser is ok or fail. */
private bool $isvalid = true;
/** @var stdClass[] the list of scenarios with all the steps.
*
* scenarionum => {type: string, title: string, steps: steprunner[]}.
*/
private array $scenarios = [];
/**
* Get the general error, if any.
* @return string
*/
public function get_general_error(): string {
if (!$this->isvalid) {
return get_string('testscenario_invalidfile', 'tool_generator');
}
if ($this->stepcount == 0) {
return get_string('testscenario_nosteps', 'tool_generator');
}
return '';
}
/**
* Check if the parsed feature is valid.
* @return bool
*/
public function is_valid(): bool {
return $this->isvalid && $this->stepcount > 0;
}
/**
* Add a line to the current scenario.
* @param steprunner $step the step to add.
*/
public function add_step(steprunner $step) {
if (empty($this->scenarios)) {
$this->add_scenario('scenario', null);
}
$currentscenario = count($this->scenarios) - 1;
$this->scenarios[$currentscenario]->steps[] = $step;
$this->stepcount++;
if (!$step->is_valid()) {
$this->isvalid = false;
}
}
/**
* Insert a new scenario.
* @param string $type the type of the scenario.
* @param string|null $name the name of the scenario.
*/
public function add_scenario(string $type, ?string $name) {
$this->scenarios[] = (object) [
'type' => $type,
'name' => $name ?? '',
'steps' => [],
'error' => '',
];
}
/**
* Add an error to the current scenario.
* @param string $error
*/
public function add_error(string $error) {
$currentscenario = count($this->scenarios) - 1;
$this->scenarios[$currentscenario]->error = $error;
}
/**
* Get the list of scenarios.
* @return stdClass[] array of scenarionum => {type: string, title: string, steps: steprunner[]}
*/
public function get_scenarios(): array {
return $this->scenarios;
}
/**
* Get all the steps form all scenarios.
* @return steprunner[]
*/
public function get_all_steps(): array {
$result = [];
foreach ($this->scenarios as $scenario) {
foreach ($scenario->steps as $step) {
$result[] = $step;
}
}
return $result;
}
}
@@ -0,0 +1,197 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
use behat_data_generators;
use Behat\Gherkin\Parser;
use Behat\Gherkin\Lexer;
use Behat\Gherkin\Keywords\ArrayKeywords;
use ReflectionClass;
use ReflectionMethod;
/**
* Class to process a scenario generator file.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class runner {
/** @var behat_data_generators the behat data generator instance. */
private behat_data_generators $generator;
/** @var array of valid steps indexed by given expression tag. */
private array $validsteps;
/**
* Initi all composer, behat libraries and load the valid steps.
*/
public function init() {
$this->include_composer_libraries();
$this->include_behat_libraries();
$this->load_generator();
}
/**
* Include composer autload.
*/
public function include_composer_libraries() {
global $CFG;
if (!file_exists($CFG->dirroot . '/vendor/autoload.php')) {
throw new \moodle_exception('Missing composer.');
}
require_once($CFG->dirroot . '/vendor/autoload.php');
return true;
}
/**
* Include all necessary behat libraries.
*/
public function include_behat_libraries() {
global $CFG;
if (!class_exists('Behat\Gherkin\Lexer')) {
throw new \moodle_exception('Missing behat classes.');
}
// Behat utilities.
require_once($CFG->libdir . '/behat/classes/util.php');
require_once($CFG->libdir . '/behat/classes/behat_command.php');
require_once($CFG->libdir . '/behat/behat_base.php');
require_once("{$CFG->libdir}/tests/behat/behat_data_generators.php");
return true;
}
/**
* Load all generators.
*/
private function load_generator() {
$this->generator = new behat_data_generators();
$this->validsteps = $this->scan_generator($this->generator);
}
/**
* Scan a generator to get all valid steps.
* @param behat_data_generators $generator the generator to scan.
* @return array the valid steps.
*/
private function scan_generator(behat_data_generators $generator): array {
$result = [];
$class = new ReflectionClass($generator);
$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
$given = $this->get_method_given($method);
if ($given) {
$result[$given] = $method->getName();
}
}
return $result;
}
/**
* Get the given expression tag of a method.
*
* @param ReflectionMethod $method the method to get the given expression tag.
* @return string|null the given expression tag or null if not found.
*/
private function get_method_given(ReflectionMethod $method): ?string {
$doccomment = $method->getDocComment();
$doccomment = str_replace("\r\n", "\n", $doccomment);
$doccomment = str_replace("\r", "\n", $doccomment);
$doccomment = explode("\n", $doccomment);
foreach ($doccomment as $line) {
$matches = [];
if (preg_match('/.*\@(given|when|then)\s+(.+)$/i', $line, $matches)) {
return $matches[2];
}
}
return null;
}
/**
* Parse a feature file.
* @param string $content the feature file content.
* @return parsedfeature
*/
public function parse_feature(string $content): parsedfeature {
$result = new parsedfeature();
$parser = $this->get_parser();
$feature = $parser->parse($content);
// No need for background in testing scenarios because scenarios can only contain generators.
// In the future the background can be used to define clean up steps (when clean up methods
// are implemented).
if ($feature->hasScenarios()) {
$scenarios = $feature->getScenarios();
foreach ($scenarios as $scenario) {
if ($scenario->getNodeType() == 'Outline') {
$result->add_scenario($scenario->getNodeType(), $scenario->getTitle());
$result->add_error(get_string('testscenario_outline', 'tool_generator'));
continue;
}
$result->add_scenario($scenario->getNodeType(), $scenario->getTitle());
$steps = $scenario->getSteps();
foreach ($steps as $step) {
$result->add_step(new steprunner($this->generator, $this->validsteps, $step));
}
}
}
return $result;
}
/**
* Get the parser.
* @return Parser
*/
private function get_parser(): Parser {
$keywords = new ArrayKeywords([
'en' => [
'feature' => 'Feature',
// If in the future we have clean up steps, background will be renamed to "Clean up".
'background' => 'Background',
'scenario' => 'Scenario',
'scenario_outline' => 'Scenario Outline|Scenario Template',
'examples' => 'Examples|Scenarios',
'given' => 'Given',
'when' => 'When',
'then' => 'Then',
'and' => 'And',
'but' => 'But',
],
]);
$lexer = new Lexer($keywords);
$parser = new Parser($lexer);
return $parser;
}
/**
* Execute a parsed feature.
* @param parsedfeature $parsedfeature the parsed feature to execute.
* @return bool true if all steps were executed successfully.
*/
public function execute(parsedfeature $parsedfeature): bool {
if (!$parsedfeature->is_valid()) {
return false;
}
$result = true;
$steps = $parsedfeature->get_all_steps();
foreach ($steps as $step) {
$result = $step->execute() && $result;
}
return $result;
}
}
@@ -0,0 +1,217 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
use behat_data_generators;
use Behat\Gherkin\Node\StepNode;
/**
* Class to validate and process a scenario step.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class steprunner {
/** @var behat_data_generators the behat data generator instance. */
private behat_data_generators $generator;
/** @var array the valid steps indexed by given expression tag. */
private array $validsteps;
/** @var StepNode the step node to process. */
private StepNode $stepnode;
/** @var string|null the generator method to call. */
private ?string $method = null;
/** @var array the parameters to pass to the generator method. */
private array $params = [];
/** @var bool if the step is valid. */
private bool $isvalid = false;
/** @var bool if the step has been executed. */
private bool $executed = false;
/** @var string the error message if any. */
private string $error = '';
/**
* Constructor.
* @param behat_data_generators $generator the behat data generator instance.
* @param array $validsteps the valid steps indexed by given expression tag.
* @param StepNode $stepnode the step node to process.
*/
public function __construct(behat_data_generators $generator, array $validsteps, StepNode $stepnode) {
$this->generator = $generator;
$this->validsteps = $validsteps;
$this->stepnode = $stepnode;
$this->init();
}
/**
* Init the step runner.
*
* This method will check if the step is valid and all the needed information
* in case it is executed.
*/
private function init() {
$matches = [];
$linetext = $this->stepnode->getText();
foreach ($this->validsteps as $pattern => $method) {
if (!$this->match_given($pattern, $linetext, $matches)) {
continue;
}
$this->method = $method;
$this->params = $this->build_method_params($method, $matches);
$this->isvalid = true;
return;
}
$this->error = get_string('testscenario_invalidstep', 'tool_generator');
}
/**
* Build the method parameters.
* @param string $methodname the method name.
* @param array $matches the matches.
* @return array the method parameters.
*/
private function build_method_params($methodname, $matches) {
$method = new \ReflectionMethod($this->generator, $methodname);
$params = [];
foreach ($method->getParameters() as $param) {
$paramname = $param->getName();
if (isset($matches[$paramname])) {
$params[] = $matches[$paramname];
unset($matches[$paramname]);
} else if (count($matches) > 0) {
// If the param is not present means the regular expressions does not use
// proper names. So we will try to find the param by position.
$params[] = array_pop($matches);
} else {
// No more params to match.
break;
}
}
return array_merge($params, $this->stepnode->getArguments());
}
/**
* Return if the step is valid.
* @return bool
*/
public function is_valid(): bool {
return $this->isvalid;
}
/**
* Return if the step has been executed.
* @return bool
*/
public function is_executed(): bool {
return $this->executed;
}
/**
* Return the step text.
* @return string
*/
public function get_text(): string {
return $this->stepnode->getText();
}
/**
* Return the step error message.
* @return string
*/
public function get_error(): string {
return $this->error;
}
/**
* Return the step arguments as string.
* @return string
*/
public function get_arguments_string(): string {
$result = '';
foreach ($this->stepnode->getArguments() as $argument) {
$result .= $argument->getTableAsString();
}
return $result;
}
/**
* Match a given expression with a text.
* @param string $pattern the given expression.
* @param string $text the text to match.
* @param array $matches the matches.
* @return bool if the step matched the generator given expression.
*/
private function match_given(string $pattern, $text, array &$matches) {
$internalmatcher = [];
if (substr($pattern, 0, 1) === '/') {
// Pattern is a regular expression.
$result = preg_match($pattern, $text, $matches);
foreach ($matches as $key => $value) {
if (is_int($key)) {
unset($matches[$key]);
}
}
return $result;
}
// Patter is a string with parameters.
$elementmatches = [];
preg_match_all('/:([^ ]+)/', $pattern, $elementmatches, PREG_SET_ORDER, 0);
$pattern = preg_replace('/:([^ ]+)/', '(?P<$1>"[^"]+"|[^" ]+)', $pattern);
$pattern = '/^' . $pattern . '$/';
$result = preg_match($pattern, $text, $internalmatcher);
if (!$result) {
return false;
}
foreach ($elementmatches as $elementmatch) {
// Remove any possible " at the beggining and end of $internalmatcher[$elementmatch[1]].
$paramvalue = preg_replace('/^"(.*)"$/', '$1', $internalmatcher[$elementmatch[1]]);
$matches[$elementmatch[1]] = $paramvalue;
}
return true;
}
/**
* Execute the step.
* @return bool if the step is executed or not.
*/
public function execute(): bool {
if (!$this->isvalid) {
return false;
}
$this->executed = true;
try {
call_user_func_array(
[$this->generator, $this->method],
$this->params
);
} catch (\moodle_exception $exception) {
$this->error = $exception->getMessage();
$this->isvalid = false;
return false;
}
return true;
}
}
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_generator\output;
use renderable;
use renderer_base;
use templatable;
use tool_generator\local\testscenario\parsedfeature;
/**
* A report to show the feature file parsing process.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class parsingresult implements renderable, templatable {
/** @var parsedfeature the processed feature object. */
protected $parsedfeature;
/**
* Constructor.
*
* @param parsedfeature $parsedfeature
*/
public function __construct(parsedfeature $parsedfeature) {
$this->parsedfeature = $parsedfeature;
}
/**
* Export for template.
*
* @param renderer_base $output The renderer.
* @return array
*/
public function export_for_template(renderer_base $output): array {
$data = [
'scenarios' => [],
'isvalid' => $this->parsedfeature->is_valid(),
'generalerror' => $this->parsedfeature->get_general_error(),
];
$haslines = false;
foreach ($this->parsedfeature->get_scenarios() as $scenario) {
$scenariodata = [
'type' => $scenario->type,
'name' => $scenario->name,
'steps' => [],
];
if (!empty($scenario->error)) {
$scenariodata['scenarioerror'] = $scenario->error;
}
foreach ($scenario->steps as $step) {
$scenariodata['steps'][] = [
'text' => $step->get_text(),
'arguments' => $step->get_arguments_string(),
'hasarguments' => !empty($step->get_arguments_string()),
'isvalid' => $step->is_valid(),
'error' => $step->get_error(),
'isexecuted' => $step->is_executed(),
];
$haslines = true;
}
if (!empty($scenariodata['steps'])) {
$scenariodata['hassteps'] = true;
}
$data['scenarios'][] = $scenariodata;
}
if ($haslines) {
$data['haslines'] = $haslines;
}
return $data;
}
}
@@ -0,0 +1,178 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Run a test scenario generator feature file.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (isset($_SERVER['REMOTE_ADDR'])) {
die(); // No access from web!
}
define('CLI_SCRIPT', true);
require_once(__DIR__ . '/../../../../config.php');
require_once(__DIR__ . '/../../../../lib/clilib.php');
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_config_manager.php');
require_once(__DIR__ . '/../../../../lib/testing/lib.php');
ini_set('display_errors', '1');
ini_set('log_errors', '1');
list($options, $unrecognised) = cli_get_params(
[
'help' => false,
'feature' => '',
'disable-composer' => false,
'composer-upgrade' => true,
'composer-self-update' => true,
],
[
'h' => 'help',
'f' => 'feature',
]
);
// Checking run.php CLI script usage.
$help = "
Run a feature file into the current Moodle instance. The feature file can only
contains scenarios with core_data_generator steps. It is not yet compatible
with scenario outlines. All scenarios will be executed at once, event background
steps.
Usage:
php runtestscenario.php [--feature=\"value\"] [--help]
[--no-composer-self-update] [--no-composer-upgrade]
[--disable-composer]
Options:
-f, --feature Execute specified feature file (Absolute path of feature file).
--no-composer-self-update
Prevent upgrade of the composer utility using its self-update command
--no-composer-upgrade
Prevent update development dependencies using composer
--disable-composer
A shortcut to disable composer self-update and dependency update
Note: Installation of composer and/or dependencies will still happen as required
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/generator/cli/runtestscenario.php --feature=/path/to/some/testing/scenario.feature
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
// The command will install composer if not present. Usually composer libraries are
// installed when behat or phpunit are installed, but we do not want to force users
// to create all phpunit or behat databases tables just to run a test scenario locally.
if (!file_exists($CFG->dirroot . '/vendor/autoload.php')) {
// Force OPcache reset if used, we do not want any stale caches
// when preparing test environment.
if (function_exists('opcache_reset')) {
opcache_reset();
}
if ($options['disable-composer']) {
// Disable self-update and upgrade easily.
// Note: Installation will still occur regardless of this setting.
$options['composer-self-update'] = false;
$options['composer-upgrade'] = false;
}
// Install and update composer and dependencies as required.
testing_update_composer_dependencies($options['composer-self-update'], $options['composer-upgrade']);
}
if (empty($options['feature'])) {
echo "Missing feature file path.\n";
exit(0);
}
$featurefile = $options['feature'];
if (!file_exists($featurefile)) {
echo "Feature file not found.\n";
exit(0);
}
$runner = new tool_generator\local\testscenario\runner();
try {
$runner->init();
} catch (Exception $e) {
echo "Something is wrong with the behat setup.\n";
echo " Please,try running \"php admin/tool/behat/cli/init.php\" from your Moodle root directory.\n";
exit(0);
}
$content = file_get_contents($featurefile);
if (empty($content)) {
echo "The feature file is empty.\n";
exit(0);
}
try {
$parsedfeature = $runner->parse_feature($content);
} catch (\Exception $error) {
echo "Error parsing feature file: {$error->getMessage()}\n";
echo "Use the web version of the tool to see the parsing details:\n";
echo " Site administration -> development -> Create testing scenarios\n";
exit(0);
}
if (!$parsedfeature->is_valid()) {
echo "The file is not valid: {$parsedfeature->get_general_error()}\n";
echo "Use the web version of the tool to see the details:\n";
echo " Site administration -> development -> Create testing scenarios\n";
exit(0);
}
$total = 0;
$success = 0;
foreach ($parsedfeature->get_all_steps() as $step) {
if ($step->execute()) {
echo "\nOK: {$step->get_text()}\n";
echo "{$step->get_arguments_string()}\n";
$success++;
} else {
echo "\nFAIL: {$step->get_text()}\n";
echo "{$step->get_arguments_string()}\n";
echo "{$step->get_error()}\n";
}
$total++;
}
echo "\n{$success}/{$total} steps executed successfully.\n";
if ($success < $total) {
echo "\nSome steps failed.\n";
exit(1);
} else {
echo "\nAll steps executed successfully.\n";
exit(0);
}
@@ -95,6 +95,18 @@ $string['sitesize_5'] = 'XXL (~20GB; 4177 courses, created in ~10 hours)';
$string['size'] = 'Size of course';
$string['smallfiles'] = 'Small files';
$string['targetcourse'] = 'Test target course';
$string['testscenario'] = 'Create testing scenarios';
$string['testscenario_description'] = 'Creating testing scenarios uses a limited feature files syntax to create all necessary elements to run a manual test.';
$string['testscenario_filedesc'] = 'The upload feature files can only contain scenarios with core_data_generator steps. It is not yet compatible with scenario outlines. All scenarios will be executed at once but background will be ignored.';
$string['testscenario_errorparsing'] = 'Error parsing feature file: {$a}';
$string['testscenario_file'] = 'Feature file';
$string['testscenario_invalidfile'] = 'The file format is not valid or contains invalid steps.';
$string['testscenario_invalidstep'] = 'Unkown step. Create testing scenarios only accepts generator steps.';
$string['testscenario_outline'] = 'Scenario outlines are not supported.';
$string['testscenario_nosteps'] = 'There are no steps to execute in the file.';
$string['testscenario_notready'] = 'Composer and behat libraries are not installed.<br><br>Please, execute this command to enable this tool: <strong>php admin/tool/generator/cli/runtestscenario.php</strong>';
$string['testscenario_scenarionosteps'] = 'This scenario does not have any steps.';
$string['testscenario_steps'] = 'Testing scenario steps:';
$string['testplanexplanation'] = 'This tool creates a JMeter test plan file along with the user credentials file.
This test plan is designed to work along with {$a}, which makes easier to run the test plan in a specific Moodle environment, gathers information about the runs and compares the results, so you will need to download it and use it\'s test_runner.sh script or follow the installation and usage instructions.
+95
View File
@@ -0,0 +1,95 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Web interface to list and filter steps
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_generator\local\testscenario\runner;
use tool_generator\form\featureimport;
use tool_generator\output\parsingresult;
require(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
// Executing behat generator can take some time.
core_php_time_limit::raise(300);
admin_externalpage_setup('toolgenerator_runtestscenario');
$currenturl = new moodle_url('/admin/tool/generator/runtestscenario.php');
$runner = new runner();
/** @var core_renderer $output*/
$output = $PAGE->get_renderer('core');
echo $output->header();
echo $output->heading(get_string('testscenario', 'tool_generator'));
echo $output->paragraph(get_string('testscenario_description', 'tool_generator'));
try {
$runner->init();
} catch (Exception $e) {
echo $output->notification(get_string('testscenario_notready', 'tool_generator'), null, false);
echo $output->footer();
die;
}
echo $output->paragraph(get_string('testscenario_filedesc', 'tool_generator'));
$mform = new featureimport();
$data = null;
if (!$mform->is_cancelled()) {
$data = $mform->get_data();
}
if (empty($data)) {
$mform->display();
echo $OUTPUT->footer();
die;
}
$content = $mform->get_feature_contents();
if (empty($content)) {
echo $output->notification(get_string('testscenario_invalidfile', 'tool_generator'));
echo $output->continue_button($currenturl);
echo $output->footer();
die;
}
try {
$parsedfeature = $runner->parse_feature($content);
} catch (\Throwable $th) {
echo $output->notification(get_string('testscenario_errorparsing', 'tool_generator', $th->getMessage()));
echo $output->continue_button($currenturl);
echo $output->footer();
die;
}
$runner->execute($parsedfeature);
echo $output->render(new parsingresult($parsedfeature));
echo $output->continue_button($currenturl);
echo $output->footer();
+9 -1
View File
@@ -32,5 +32,13 @@ if ($hassiteconfig) {
$ADMIN->add('development', new admin_externalpage('toolgeneratortestplan',
get_string('maketestplan', 'tool_generator'),
$CFG->wwwroot . '/' . $CFG->admin . '/tool/generator/maketestplan.php'));
}
$ADMIN->add(
'development',
new admin_externalpage(
'toolgenerator_runtestscenario',
get_string('testscenario', 'tool_generator'),
$CFG->wwwroot . '/' . $CFG->admin . '/tool/generator/runtestscenario.php'
)
);
}
@@ -0,0 +1,106 @@
{{!
This file is part of Moodle - https://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_generator/parsingresult
Display the result of parsing or executing a behat testing scenario.
Example context (json):
{
"isvalid": true,
"generalerror": "Error message",
"haslines": true,
"scenarios": [
{
"type": "Scenario",
"title": "Scenario title",
"hassteps": true,
"steps": [
{
"text": "Step text",
"isvalid": true,
"isexecuted": true,
"hasarguments": true,
"arguments": "Argument text",
"error": "Error message"
}
]
}
]
}
}}
{{^isvalid}}
<div class="alert alert-danger mb-3" role="alert">
<span class="fa fa-times"></span>
{{generalerror}}
</div>
{{/isvalid}}
{{#haslines}}
<p>{{#str}} testscenario_steps, tool_generator {{/str}}</p>
{{/haslines}}
{{#scenarios}}
<h3>{{type}}: {{name}}</h3>
<div>
{{#scenarioerror}}
<div class="alert alert-danger mb-3" role="alert">
<span class="fa fa-times"></span>
{{scenarioerror}}
</div>
{{/scenarioerror}}
{{^hassteps}}
<div class="alert alert-info mb-3" role="alert">
<span class="fa fa-info-circle"></span>
{{#str}} testscenario_scenarionosteps, tool_generator {{/str}}
</div>
{{/hassteps}}
{{#hassteps}}
<ul class="list-group mb-3">
{{#steps}}
<li class="list-group-item">
<div>
{{text}}
{{^isvalid}}
<span class="badge badge-pill badge-danger">
<span class="fa fa-times"></span>
{{#str}} error {{/str}}
</span>
{{/isvalid}}
{{#isvalid}}
{{#isexecuted}}
<span class="badge badge-pill badge-success">
<span class="fa fa-check "></span>
{{#str}} success {{/str}}
</span>
{{/isexecuted}}
{{/isvalid}}
</div>
{{#hasarguments}}
<div class="alert alert-info mb-0 mt-2" role="alert" style="overflow: auto;">
<pre class="mb-0">{{arguments}}</pre>
</div>
{{/hasarguments}}
{{^isvalid}}
<div class="alert alert-danger mb-0 mt-2" role="alert" style="overflow: auto;">
{{error}}
</div>
{{/isvalid}}
</li>
{{/steps}}
</ul>
{{/hassteps}}
</div>
{{/scenarios}}
@@ -0,0 +1,64 @@
@tool @tool_generator @_file_upload
Feature: Create testing scenarios using generators
In order to execute manual tests
As a developer
I need to use a feature file to execute generators into the current instance
@javascript
Scenario: Create a testing scenario with a course enrolled users and activities
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario.feature" file to "Feature file" filemanager
And I press "Import"
And I should see "Scenario: Create course content"
Then I am on the "C1" "Course" page
And I should see "Activity sample 1" in the "Topic 1" "section"
And I should see "Activity sample 2" in the "Topic 1" "section"
And I navigate to course participants
And I should see "Teacher Test1"
And I should see "Student Test1"
And I should see "Student Test2"
And I should see "Student Test3"
And I should see "Student Test4"
And I should see "Student Test5"
@javascript
Scenario: Prevent creating a testing scenario with no steps to execute
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongempty.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "There are no steps to execute in the file."
@javascript
Scenario: Prevent creating a testing scenario with only background steps to execute
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongonlybackground.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "There are no steps to execute in the file."
@javascript
Scenario: Prevent creating a testing scenario with a wrong file format
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongformat.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "Error parsing feature file"
@javascript
Scenario: Prevent creating a testing scenario with non generator steps
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongstep.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "The file format is not valid or contains invalid steps"
@javascript
Scenario: Prevent creating a testing scenario from a scenario outline
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongoutline.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "Scenario outlines are not supported"
Then I should see "There are no steps to execute in the file"
@@ -0,0 +1,27 @@
Feature: Prepare scenario for testing
Scenario: Create course content
Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | visible |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 0 |
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | sample@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And "5" "users" exist with the following data:
| username | student[count] |
| firstname | Student |
| lastname | Test[count] |
| email | student[count]@example.com |
And "5" "course enrolments" exist with the following data:
| user | student[count] |
| course | C1 |
| role | student |
@@ -0,0 +1,4 @@
Feature: Prepare scenario for testing
Scenario: only empty scenario 1.
Scenario: only empty scenario 2.
@@ -0,0 +1,5 @@
This is not a valid feature file.
However: it has lines
indented and stuff
like if it was.
@@ -0,0 +1,12 @@
Feature: Invalid texting because background is not compatible
Background:
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | visible |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 0 |
@@ -0,0 +1,14 @@
Feature: Prepare scenario for testing
Scenario Outline: test outline scenarios are not supported yet
Given the following "course" exists:
| fullname | <name> |
| shortname | <shortname> |
| category | 0 |
| numsections | 3 |
Examples:
| name | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
| Course 3 | C3 |
@@ -0,0 +1,13 @@
Feature: Contains wrong steps
Scenario: Scenario with non generator steps
Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | visible |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 0 |
And I click on "Tokens filter" "link"
@@ -0,0 +1,216 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
/**
* Tests for parsedfeature class.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \tool_generator\local\testscenario\parsedfeature
*/
class parsedfeature_test extends \advanced_testcase {
/**
* Get a parsed feature from a content.
* @param string $content the feature content.
* @return parsedfeature the parsed feature.
*/
private function get_feature_from_content(string $content): parsedfeature {
$runner = new runner();
$runner->init();
return $runner->parse_feature($content);
}
/**
* Test for parse_feature.
* @covers ::get_general_error
* @covers ::add_scenario
* @covers ::add_step
*/
public function test_general_error(): void {
$nosteps = get_string('testscenario_nosteps', 'tool_generator');
$invalidfile = get_string('testscenario_invalidfile', 'tool_generator');
$parsedfeature = new parsedfeature();
$this->assertEquals($nosteps, $parsedfeature->get_general_error());
$parsedfeature->add_scenario('Scenario', 'Test scenario');
$this->assertEquals($nosteps, $parsedfeature->get_general_error());
// Add some valid step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | [email protected] |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertEquals('', $parsedfeature->get_general_error());
// Only generator methods are allowed.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Not generator
Given I am in a course
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertEquals($invalidfile, $parsedfeature->get_general_error());
}
/**
* Test for parse_feature.
* @covers ::is_valid
* @covers ::add_scenario
* @covers ::add_step
*/
public function test_is_valid(): void {
$parsedfeature = new parsedfeature();
$this->assertFalse($parsedfeature->is_valid());
$parsedfeature->add_scenario('Scenario', 'Test scenario');
$this->assertFalse($parsedfeature->is_valid());
// Add some valid step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | [email protected] |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertTrue($parsedfeature->is_valid());
// Only generator methods are allowed.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Not generator
Given I am in a course
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertFalse($parsedfeature->is_valid());
}
/**
* Test for ading steps into scenarios.
* @covers ::add_step
* @covers ::add_scenario
* @covers ::get_all_steps
* @covers ::get_scenarios
*/
public function test_add_step(): void {
$parsedfeature = new parsedfeature();
$this->assertEquals(0, count($parsedfeature->get_all_steps()));
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | [email protected] |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertEquals(1, count($parsedfeature->get_all_steps()));
// Should create a default scenario.
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(1, count($scenarios));
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('', $scenarios[0]->name);
$this->assertEquals(1, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals('', $scenarios[0]->error);
// Add a second step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Not generator
Given I am in a course
');
$step2 = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step2);
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(1, count($scenarios));
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('', $scenarios[0]->name);
$this->assertEquals(2, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals($step2, $scenarios[0]->steps[1]);
$this->assertEquals('', $scenarios[0]->error);
// Create a new scenario.
$parsedfeature->add_scenario('scenario', 'Test scenario 2');
$parsedfeature->add_step($step2);
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(2, count($scenarios));
// Scenario 1.
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('', $scenarios[0]->name);
$this->assertEquals(2, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals($step2, $scenarios[0]->steps[1]);
$this->assertEquals('', $scenarios[0]->error);
// Scenario 2.
$this->assertEquals('scenario', $scenarios[1]->type);
$this->assertEquals('Test scenario 2', $scenarios[1]->name);
$this->assertEquals(1, count($scenarios[1]->steps));
$this->assertEquals($step2, $scenarios[1]->steps[0]);
$this->assertEquals('', $scenarios[1]->error);
}
/**
* Test for ading errors into scenarios.
* @covers ::add_error
* @covers ::add_scenario
* @covers ::add_step
* @covers ::get_scenarios
*/
public function test_add_error(): void {
$parsedfeature = new parsedfeature();
// Add some valid step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | [email protected] |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_scenario('scenario', 'Test scenario 1');
$parsedfeature->add_step($step);
$parsedfeature->add_error('Error message');
$parsedfeature->add_scenario('scenario', 'Test scenario 2');
$parsedfeature->add_step($step);
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(2, count($scenarios));
// Scenario 1.
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('Test scenario 1', $scenarios[0]->name);
$this->assertEquals(1, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals('Error message', $scenarios[0]->error);
// Scenario 2.
$this->assertEquals('scenario', $scenarios[1]->type);
$this->assertEquals('Test scenario 2', $scenarios[1]->name);
$this->assertEquals(1, count($scenarios[1]->steps));
$this->assertEquals($step, $scenarios[1]->steps[0]);
$this->assertEquals('', $scenarios[1]->error);
}
}
@@ -0,0 +1,107 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
/**
* Tests for runner class.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \tool_generator\local\testscenario\runner
*/
class runner_test extends \advanced_testcase {
/**
* Test for parse_feature.
* @covers ::parse_feature
* @covers ::execute
*/
public function test_parse_and_execute_feature(): void {
global $CFG, $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Call the init method to include all behat libraries and attributes.
$runner = new runner();
$runner->init();
$featurefile = $CFG->dirroot . '/admin/tool/generator/tests/fixtures/testscenario/scenario.feature';
$contents = file_get_contents($featurefile);
$feature = $runner->parse_feature($contents);
$this->assertEquals(2, count($feature->get_scenarios()));
$this->assertEquals(6, count($feature->get_all_steps()));
$this->assertTrue($feature->is_valid());
$result = $runner->execute($feature);
$this->assertTrue($result);
// Validate everything is created.
$this->assertEquals(
1,
$DB->count_records('course', ['shortname' => 'C1'])
);
$course = $DB->get_record('course', ['shortname' => 'C1']);
$this->assertEquals(
2,
$DB->count_records('course_modules', ['course' => $course->id])
);
$this->assertEquals(
1,
$DB->count_records('user', ['firstname' => 'Teacher'])
);
$this->assertEquals(
5,
$DB->count_records('user', ['firstname' => 'Student'])
);
$context = \context_course::instance($course->id);
$this->assertEquals(
6,
$DB->count_records('role_assignments', ['contextid' => $context->id])
);
}
/**
* Test for parse_feature.
* @covers ::parse_feature
* @covers ::execute
*/
public function test_parse_and_execute_wrong_feature(): void {
global $CFG, $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Call the init method to include all behat libraries and attributes.
$runner = new runner();
$runner->init();
$featurefile = $CFG->dirroot . '/admin/tool/generator/tests/fixtures/testscenario/scenario_wrongstep.feature';
$contents = file_get_contents($featurefile);
$feature = $runner->parse_feature($contents);
$this->assertEquals(1, count($feature->get_scenarios()));
$this->assertEquals(3, count($feature->get_all_steps()));
$this->assertFalse($feature->is_valid());
$result = $runner->execute($feature);
$this->assertFalse($result);
$this->assertEquals(0, $DB->count_records('course', ['shortname' => 'C1']));
}
}
@@ -0,0 +1,238 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
use behat_data_generators;
use Behat\Gherkin\Parser;
use Behat\Gherkin\Lexer;
use Behat\Gherkin\Keywords\ArrayKeywords;
use Behat\Gherkin\Node\StepNode;
/**
* Tests for steprunner class.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \tool_generator\local\testscenario\steprunner
*/
class steprunner_test extends \advanced_testcase {
public static function setUpBeforeClass(): void {
// Call the init method to include all behat libraries and attributes.
$runner = new runner();
$runner->init();
}
/**
* Get a step node from a string.
* @param string $step the step string.
* @return StepNode the step node.
*/
private function get_step(string $step): StepNode {
$content = 'Feature: Test feature
Scenario: Test scenario
' . $step . '
';
$method = new \ReflectionMethod(runner::class, 'get_parser');
$parser = $method->invoke(new runner());
$feature = $parser->parse($content);
$scenario = $feature->getScenarios()[0];
$steps = $scenario->getSteps();
return $steps[0];
}
/**
* Test for parse_feature.
* @covers ::is_valid
* @param string $step the step to validate.
* @param bool $expected if the step is expected to be valid.
* @dataProvider execute_steps_provider
*/
public function test_is_valid(string $step, bool $expected): void {
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step($step);
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertEquals($expected, $steprunner->is_valid());
}
/**
* Test for execute step.
*
* @covers ::is_executed
* @covers ::execute
* @param string $step the step to execute.
* @param bool $expected if the step is expected to be executed.
* @dataProvider execute_steps_provider
*/
public function test_execute(string $step, bool $expected): void {
global $DB;
$this->resetAfterTest();
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step($step);
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertFalse($steprunner->is_executed());
$result = $steprunner->execute();
$this->assertEquals($expected, $result);
$this->assertEquals($expected, $steprunner->is_executed());
if ($expected) {
// Validate everything is created.
$this->assertEquals(
1,
$DB->count_records('course', ['shortname' => 'C1'])
);
}
}
/**
* Data provider for test_execute.
* @return array the data.
*/
public static function execute_steps_provider(): array {
return [
'Following exists' => [
'step' => 'Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |',
'expected' => true,
],
'Following exist' => [
'step' => 'Given the following "course" exist:
| fullname | shortname | category |
| Course test | C1 | 0 |',
'expected' => true,
],
'Repeated entities' => [
'step' => 'Given "1" "courses" exist with the following data:
| fullname | Course test |
| shortname | C[count] |
| category | 0 |
| numsections | 3 |',
'expected' => true,
],
'Invalid step' => [
'step' => 'Given I click on "Tokens filter" "link"',
'expected' => false,
],
];
}
/**
* Test for execute step.
* @covers ::is_executed
* @covers ::execute
* @covers ::get_error
*/
public function test_execute_duplicated(): void {
global $DB;
$this->resetAfterTest();
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step('Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |');
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertFalse($steprunner->is_executed());
$result = $steprunner->execute();
$this->assertTrue($result);
$this->assertTrue($steprunner->is_executed());
$this->assertEquals('', $steprunner->get_error());
// Validate everything is created.
$this->assertEquals(
1,
$DB->count_records('course', ['shortname' => 'C1'])
);
// Execute the same course creation.
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertFalse($steprunner->is_executed());
$result = $steprunner->execute();
$this->assertFalse($result);
$this->assertTrue($steprunner->is_executed());
$this->assertEquals(get_string('shortnametaken', 'error', 'C1'), $steprunner->get_error());
}
/**
* Test for parse_feature.
* @covers ::get_text
* @covers ::get_arguments_string
*/
public function test_get_step_content(): void {
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step('Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |
| numsections | 3 |');
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertEquals(
'the following "course" exists:',
$steprunner->get_text()
);
$data = [
'| fullname | Course test |',
'| shortname | C1 |',
'| category | 0 |',
'| numsections | 3 |',
];
$arguments = explode("\n", $steprunner->get_arguments_string());
$this->assertEquals(
$data,
$arguments
);
}
}