Behaviour-driven development is great, isn’t it? It improves communication in the team, makes business requirements understandable for everyone involved in the project, speeds up the development in the long term… It has no disadvantages! Or… has it?
Unfortunately, there are no perfect systems and methodologies. All of them have their drawbacks - tradeoffs we need to agree on when adopting them in the development process. And that’s good until we’re aware of them. Even though I love BDD, TDD, and test-driven approaches in general, I can understand the cost of using them in other projects.
This presentation focuses on the pros and cons of BDD methodology, thinking from the Sylius Core Team Member perspective, but also trying to comprehend the view of an average Sylius and Symfony user.
9. Better TDD
9
TDD on steroids
BDD tests
TDD with
different naming
BDD is UI testing
10. 10
BDD is a way for software teams to work that closes the gap
between business people and technical people
https://cucumber.io/docs/bdd/
Behaviour-driven development is an “outside-in” methodology. It starts
at the outside by identifying business outcomes, and then drills
down into the feature set that will achieve those outcomes.
https://dannorth.net/whats-in-a-story/
BDD is a process designed to aid the management and the delivery of software
development projects by improving communication between engineers
and business professionals. https://inviqa.com/blog/bdd-guide
35. 35
<?php
declare(strict_types=1);
namespace specSyliusBundleCoreBundleEventListener;
use …
final class CustomerDefaultAddressListenerSpec extends ObjectBehavior
{
function it_adds_the_address_as_default_to_the_customer_on_pre_create_resource_controller_event(
ResourceControllerEvent $event,
AddressInterface $address,
CustomerInterface $customer,
): void {
$event->getSubject()->willReturn($address);
$address->getCustomer()->willReturn($customer);
$customer->getDefaultAddress()->willReturn(null);
$customer->setDefaultAddress($address)->shouldBeCalled();
$this->preCreate($event);
}
}
36. 36
<?php
declare(strict_types=1);
namespace specSyliusBundleCoreBundleEventListener;
use …
final class CustomerDefaultAddressListenerSpec extends ObjectBehavior
{
function it_adds_the_address_as_default_to_the_customer_on_pre_create_resource_controller_event(
ResourceControllerEvent $event,
AddressInterface $address,
CustomerInterface $customer,
): void {
$event->getSubject()->willReturn($address);
$address->getCustomer()->willReturn($customer);
$customer->getDefaultAddress()->willReturn(null);
$customer->setDefaultAddress($address)->shouldBeCalled();
$this->preCreate($event);
}
}
37. 37
Scenario: Having cart maintained after logging in
When I add "Stark T-Shirt" product to the cart
And I log in as "robb@stark.com" with "KingInTheNorth" password
And I see the summary of my cart
Then there should be one item in my cart
And this item should have name "Stark T-Shirt"
38. 38
/**
* @Given /^I (?:add|added) ("[^"]+" product) to the (cart)$/
*/
public function iAddProductToTheCart(ProductInterface $product): void
{
$this->productShowPage->open(['slug' => $product->getSlug()]);
$this->productShowPage->addToCart();
$this->sharedStorage->set('product', $product);
}
39. 39
/**
* @Given /^I (?:add|added) ("[^"]+" product) to the (cart)$/
*/
public function iAddProductToTheCart(ProductInterface $product): void
{
$this->productShowPage->open(['slug' => $product->getSlug()]);
$this->productShowPage->addToCart();
$this->sharedStorage->set('product', $product);
}
JUST DO IT!
40. 40
BUT REMEMBER...
/**
* @Given /^I (?:add|added) ("[^"]+" product) to the (cart)$/
*/
public function iAddProductToTheCart(ProductInterface $product): void
{
$this->productShowPage->open(['slug' => $product->getSlug()]);
$this->productShowPage->addToCart();
$this->sharedStorage->set('product', $product);
}
/**
* @Transform /^"([^"]+)" product(?:|s)$/
*/
public function getProductByName(string $productName): ProductInterface
{
return $this->productRepository->findOneByName($productName, $this->locale);
}
41. 41
BUT REMEMBER...
/**
* @Given /^I (?:add|added) ("[^"]+" product) to the (cart)$/
*/
public function iAddProductToTheCart(ProductInterface $product): void
{
$this->productShowPage->open(['slug' => $product->getSlug()]);
$this->productShowPage->addToCart();
$this->sharedStorage->set('product', $product);
}
/**
* @Transform /^"([^"]+)" product(?:|s)$/
*/
public function getProductByName(string $productName): ProductInterface
{
return $this->productRepository->findOneByName($productName, $this->locale);
}
class ShowPage extends SymfonyPage implements ShowPageInterface
{
public function getRouteName(): string
{
return 'sylius_shop_product_show';
}
public function addToCart(): void
{
$this->getElement('add_to_cart_button')->click();
$this->waitForCartSummary();
}
}
42. 42
BUT REMEMBER...
/**
* @Given /^I (?:add|added) ("[^"]+" product) to the (cart)$/
*/
public function iAddProductToTheCart(ProductInterface $product): void
{
$this->productShowPage->open(['slug' => $product->getSlug()]);
$this->productShowPage->addToCart();
$this->sharedStorage->set('product', $product);
}
/**
* @Transform /^"([^"]+)" product(?:|s)$/
*/
public function getProductByName(string $productName): ProductInterface
{
return $this->productRepository->findOneByName($productName, $this->locale);
}
class ShowPage extends SymfonyPage implements ShowPageInterface
{
public function getRouteName(): string
{
return 'sylius_shop_product_show';
}
public function addToCart(): void
{
$this->getElement('add_to_cart_button')->click();
$this->waitForCartSummary();
}
}
class SharedStorage implements SharedStorageI
{
private array $clipboard = [];
private ?string $latestKey = null;
43. /**
* @Given /^I (?:add|added) ("[^"]+" product) to the (cart)$/
*/
public function iAddProductToTheCart(ProductInterface $product): void
{
$this->productShowPage->open(['slug' => $product->getSlug()]);
$this->productShowPage->addToCart();
$this->sharedStorage->set('product', $product);
}
/**
* @Transform /^"([^"]+)" product(?:|s)$/
*/
public function getProductByName(string $productName): ProductInterface
{
return $this->productRepository->findOneByName($productName, $this->locale);
}
class ShowPage extends SymfonyPage implements ShowPageInterface
{
public function getRouteName(): string
{
return 'sylius_shop_product_show';
}
public function addToCart(): void
{
$this->getElement('add_to_cart_button')->click();
$this->waitForCartSummary();
}
}
class SharedStorage implements SharedStorageI
{
private array $clipboard = [];
private ?string $latestKey = null;
43