Separating test suites in Behat - php

I'm trying to reproduce isolated test suites with Behat like I have with Cucumber. I've been trying to tweak the behat.yml file using suites and paths as well as moving files and folders around. Behat results make so little sense that I am starting to conclude that it was not designed with this in mind at all. And the documentation does not describe expected files & folder layouts
For each test suite I want:
a specific xxx.feature file
a specific xxxContext.php file, defining a its own xxxContext class
I don't want a monolithic FeatureContext.php file. My project is so huge that it would become a nightmare to maintain, not to mention the potential collateral damage between steps. It's OK to have a single behat.yml file if that helps.
I also read this post but I still haven't solved the problem. How I can create isolated test suites with Behat?
Note: I am using behat 3.0-dev
Edit: Here is the "best" I could achieve. I have two suites, one called "OAuth2" and another called "Achievements".
beyat.yml:
default:
suites:
OAuth2:
contexts:
- OAuth2Context
Achievements:
contexts:
- AchievementsContext
File hierarchy:
├── behat.yml
└── features
├── Achievements.feature
├── OAuth2.feature
└── bootstrap
└── OAuth2Context.php <-- Contains the OAuth2Context and AchievementsContext classes. Also, classes are found only if I name this file this way, I don't know why.
My main problem is that when I run behat, it complains that there are missing steps. Basically, the OAuth2Context class is missing steps that were defined in Achievements and the AchievementsContext is missing steps that were defined in OAuth2. This output completely baffles me:
--- OAuth2Context has missing steps. Define them with these snippets:
/**
* #When I say yes
*/
public function iSayYes()
{
throw new PendingException();
}
/**
* #Then you say no
*/
public function youSayNo()
{
throw new PendingException();
}
--- AchievementsContext has missing steps. Define them with these snippets:
/**
* #When I ask for an Access Token with my credentials
*/
public function iAskForAnAccessTokenWithMyCredentials2()
{
throw new PendingException();
}
/**
* #Then I receive an Access Token
*/
public function iReceiveAnAccessToken2()
{
throw new PendingException();
}
/**
* #When I ask for an Access Token with a Partner User grant type where:
*/
public function iAskForAnAccessTokenWithAPartnerUserGrantTypeWhere(TableNode $table)
{
throw new PendingException();
}
I believe that this is because I defined two separate classes in the php file. But I really don't want to make a single monolitic class. In fact, I'd even like two separate PHP files. The Classes are defined this way:
class OAuth2Context implements Context, SnippetAcceptingContext
{
...
/**
* #When I ask for an Access Token with my credentials
*/
public function iAskForAnAccessTokenWithMyCredentials()
{
...
}
...
}
class AchievementsContext implements Context, SnippetAcceptingContext
{
...
/**
* #When I say yes
*/
public function iSayYes()
{
...
}
...
}
Other (smaller) issue is that I have to name my php file OAuth2Context.php instead of FeatureContext.php, otherwise the classes are not found at all. I don't understand this, as it was my understanding that FeatureContext.php was behat's default name.
Edit: SOLUTION, thanks to Jakub Zalas :
Here is the file layout that works:
├── behat.yml
└── features
├── achievements
│   └── Achievements.feature
├── bootstrap
│   ├── AchievementsContext.php <-- AchievementsContext class
│   └── OAuth2Context.php <-- OAuth2Context class
└── oauth
└── OAuth2.feature
With the following beyat.yml file:
default:
suites:
OAuth2:
contexts:
- OAuth2Context
paths: [ %paths.base%/features/oauth ]
Achievements:
contexts:
- AchievementsContext
paths: [ %paths.base%/features/achievements ]
This way I have steps in separated PHP files, which was my main requirement, and separated suites with separated feature files. I am a happy camper!

With the way you currently organised your suites each feature file will be run for each suite.
Use filters or paths to define which feature files should match a given suite:
# behat.yml
default:
suites:
OAuth2:
contexts:
- OAuth2Context
paths: [ %paths.base%/features/oauth ]
Achievements:
contexts:
- AchievementsContext
paths: [ %paths.base%/features/achievements ]
Having a single feature file per suite is probably not worth it, as you'll end up with lots of suites without a real benefit. Better group features in slightly bigger suites, and register multiple contexts per suite.

Related

Overriding classes with class_alias

I know this is a really bad idea, but I ran out of ideas, so I'd like to hear some opinions about it.
I'm working on a PHP project which allows the development of addons so that other developers can add their own functions, not so different from thousands of other open-source projects:
root
└── addons
└── addon-a
└── addon-b
└── addon-c
I have to implement some changes to an already made addon (let's say Addon A) without changing the original files or using Composer PSR-4. Unfortunately, the original author did not follow good practices that would help to accomplish this task. Here's how the Addon A is:
Structure
addon-a
└── main.php
└── classes
└── Foo.php
└── Bar.php
addon-a/main.php
include_once __DIR__ . '/classes/Foo.php';
function run() {
echo AddonA\Classes\Foo::returnTwo();
}
addon-a/classes/Foo.php
namespace AddonA\Classes
use AddonA\OtherFolder\FooBar
abstract class Foo {
public static function returnTwo() {
return 2;
}
public static function returnFooBar() {
return new FooBar();
}
}
I need to override the returnTwo function and return another value. Without changing other files, the only way I was able to do it was through the class_alias method, copying the whole Foo class:
First, I created another addon and made it ran before the Addon A "run" function:
Structure
new-addon-a
└── main.php
└── classes
└── Foo.php
new-addon-a/main.php
function init() {
include_once __DIR__ . '/classes/Foo.php';
class_alias( 'NewAddonA\Classes\Foo', 'AddonA\Classes\Foo', true );
}
init();
new-addon-a/classes/Foo.php
namespace NewAddonA\Classes
use AddonA\OtherFolder\FooBar // No changes to the previous "uses."
abstract class Foo {
public static function returnTwo() {
return 3;
}
public static function returnBar() {
return new AddonA\Classes\Bar(); // Here I had to change because the namespace has changed
}
}
Now, when Addon A executes the function run, the AddonA\Classes\Foo::returnTwo will be my own class.
Well, as I said before, this is pretty ugly, and I would lose any update to the Foo code since I'm overriding the entire class. However, this was the only way I thought. Do you think it would be possible to use another method?
Thanks!

Codeception autoloading classes

I'm having issues using the Codeception autoloader to load some abstract test classes. The reason for the abstract class is to mimic the structure of classes that are used in the application to cut down on the amount of code needed to test the application thoroughly.
Lets say I have an abstract class for testing, let say "AbstractRepositoryTester" which is only used in the "repositories" test suite (I like to separate things out for organisational purposes).
Each and every repository that I test that implements a "RepositoryContract" will have a test that also extends the "AbstractRepositoryTester" with some overridden abstract methods.
Now when doing this, the abstract class won't be loaded during testing and has to be loaded manually in a bootstrap file. There is also another abstraction that extends the vanilla Codeception test class so that I can set a few variables (namely for laracasts/testdummy).
Both classes will fail to load with out manual entry in to the _boostrap file. To add to this, the suite specific bootstrap file fails to load files or seemingly execute at all so I am forced to place all bootstrap code for all suites in to the global _bootstrap file.
I also attempted to use Codeceptions autoloading class \Codeception\Util\Autoload:: with the "load" method but it doesn't seem to work.
Right now I'm using require_once in the global _bootstrap so finally to the question:
Is there a correct way of autoloading (or just loading) a class to be used as part of a test both globally and per suite?
Am I on the right track overall in abstracting my tests like this? TDD is new to me and I am trying to better my development workflow (with help from Laracasts).
I've searched every where for an answer to load the classes I need but usually all I'll find is PHPUnit specific answers which don't appear to work. I have also peered through the Codeception documentation which feels a bit sparse on the subject and the API docs don't explain the method call usage in the case of Autoload::load
Cheers,
- Everon.
You can do this for your whole test suit, or just individual components. For example, for Unit tests only, do the following:
Add bootstrap: my_bootstrap_file.php to tests/unit.suite.yml:
# Codeception Test Suite Configuration
#
# Suite for unit or integration tests.
actor: UnitTester
bootstrap: my_bootstrap_file.php
modules:
enabled:
- Asserts
- \Helper\Unit
Call my_bootstrap_file.php something sensible like just bootstrap.php
Create tests/unit/my_bootstrap_file.php
<?php
\Codeception\Util\Autoload::addNamespace('', 'src');
The directory structure should look like this:
<project root>
src/
tests/
unit/
my_bootstrap_file.php
unit.suite.yml
Replace unit in the instructions above with acceptance, functional, etc. to apply it to different single components.
The PhpDoc for \Codeception\Util\Autoload::addNamespace():
/**
* Adds a base directory for a namespace prefix.
*
* Example:
*
* ```php
* <?php
* // app\Codeception\UserHelper will be loaded from
* '/path/to/helpers/UserHelper.php'
*
* Autoload::addNamespace('app\Codeception', '/path/to/helpers');
*
* // LoginPage will be loaded from '/path/to/pageobjects/LoginPage.php'
* Autoload::addNamespace('', '/path/to/pageobjects');
*
* Autoload::addNamespace('app\Codeception', '/path/to/controllers');
* ?>
* ```
*
* #param string $prefix The namespace prefix.
* #param string $base_dir A base directory for class files in the namespace.
* #param bool $prepend If true, prepend the base directory to the stack instead
* of appending it; this causes it to be searched
* first rather than last.
* #return void
*/
public static function addNamespace($prefix, $base_dir, $prepend = false)
If you want this to apply to your whole test suite, not just Unit tests, use codeception.yml instead of tests/unit.suite.yml, and tests/my_bootstrap_file.php instead of tests/unit/my_bootstrap_file.php.
<project root>
src/
tests/
my_bootstrap_file.php
codeception.yml

Behat not autoloading additional classes to a test context

Behat version 3.0.12, PHP 5.3.3
I'll describe the problem before showing my setup: I'm creating a small test Behat project, but when I try and create additional classes in other files to pull into my context file, Behat doesn't autoload them. The docs say that anything in features/bootstrap should be autoloaded and available. The specific error is this:
Fatal error: Class 'TestObject' not found in /data/drupal7/sites/behat-test/projects/test-project/features/bootstrap/TestContext.php on line 23
In the version 3 docs, the tutorial defines a class in a new PHP file and brings it in effortlessly. Trying to do the same on my version doesn't work.
I've got a Behat setup like this:
composer.*
projects
vendor
This is a standard result of installing Behat, except there's a new folder where my individual projects have their own folder in project/<project name>. For instance, project/test_project:
behat.yml
features
|-- test.feature
|-- bootstrap
|-- TestContext.php
|-- Test.php
My config file, behat.yml looks like this:
default:
suites:
default:
paths: [ %paths.base%/features ]
bootstrap: [ $paths.features%/bootstrap ]
contexts: [ TestContext ]
The TestContext.php looks like this:
<?php
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
/**
* Behat context class.
*/
class TestContext implements Context
{
private $test;
public function __construct()
{
$this->test = new TestObject();
}
/**
* #Given I go to the Google homepage
*/
public function iGoToTheGoogleHomepage()
{
throw new PendingException();
}
}
And finally, Test.php
<?php
final class TestObject {
private $price = 0;
public function setPrice($amount) {
$this->price = $amount;
}
public function getPrice() {
return $this->price;
}
}
Any ideas?
Turns out that additional files need to follow the PSR-0 standard - and in the case of this, the TestObject class needed to be in a file with the same name, e.g. TestObject.php

Behat - Implement Step definitions

I am having difficulties in setting Behat framework correctly. The test runs fine, however the step definitions are not being picked up from the FeatureContext file. Have tried out answers from other questions with no luck. The behat.yml file is currently stored in the root of the project:
default:
paths:
features: 'bin/features'
bootstrap: 'bin/features/bootstrap'
context:
class: 'FeatureContext'
extensions:
Behat\MinkExtension\Extension:
goutte: ~
selenium2: ~
In the root of the project I have a bin folder which contains the standard behat files: behat.bat, webunit.bat etc. Within the bin folder I have the features folder which contains the file search.feature:
Feature: Search
In order to find a word
As a website user
I need to be able to search for a word
#javascript
Scenario: Searching for a word that does exist
Given I am on "http://drupalcamp.mx/search/"
When I fill in "Escriba las palabras clave" with "behat, mink"
And I press "Buscar"
Then I should see "Behavior Driven Development"
And I follow "Behavior Driven Development"
And I wait for 5 seconds
Within the features folder I there is a bootstrap folder which contains the file "FeatureContext"
namespace features;
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
use Behat\MinkExtension\Context\MinkContext;
/**
* Features context.
*/
class FeatureContext extends MinkContext
{
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* #param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters)
{
// Initialize your context here
}
/**
* #Given /^I wait for (\d+) seconds$/
*/
public function iWaitForSeconds($seconds)
{
$this->getSession()->wait($seconds*1000);
}
When I run "behat" from the root directory of the project in CL, it fires the test in the browser, passes all but fails to recognise the step definition, it states "You can implement step definitions for undefined steps with these snippets:" which then it gives an example.
You need to configure the autoloader to load classes from your custom bootstrap folder.
If you're using composer, here's an example of how it could be done:
"autoload": {
"psr-0": { "": ["src/", "bin/features/bootstrap/"]}
}
Note: bin folder is a really odd location to put your features or context files.
Related question with an answer: Behat + Symfony, Custom definitions are not loaded (actually, might be a duplicate).

Symfony 2 - Where should I put a utility class?

I'm creating a class that will have one public method, which returns a value indexed by a parameter. I have a single bundle at present. The directories inside the bundle I currently have are:
/Controller
/DataFixtures
/DependencyInjection
/Document
/Entity
/Resources
/Tests
What is the convention for placement of a class like this?
Symfony official website suggest src/AppBundle/Utils
Source : http://symfony.com/doc/current/best_practices/business-logic.html
Your question is a bit subjective, but according to what is outlined in Bundle Structure and Best Practices, a Bundle is just namespaced code. If the utility class is of first-grade, why don't you place it into the root-dir of the Bundle?
Namespace Bundle\HelloBundle;
Class Utility {
public static function returnIndexedValueByParameter($parameter) {
...
}
}
Filename:
Bundle/HelloBundle/Utility.php

Categories