Behaviour-driven development jest wspaniałe, czyż nie? Poprawia komunikacja w Zespole, sprawia że wymagania biznesowe są zrozumiałe dla wszystkich zaangażowanych w projekt, przyspiesza development w dłuższym okresie… Po prostu nie ma wad! Albo… jednak ma?
Niestety, nie ma idealnych systemów czy idealnych metodologii. Wszystkie mają swoje minusy - kompromisy, na które musimy się zgodzić kiedy implementujemy je w naszych procesach. I nie ma w tym nic złego, dopóki jesteśmy ich świadomi. Pracując przez wiele lat z Syliusem pokochałem BDD, TDD, czy podejście test-driven jako takie, ale zrozumiałem też koszt ich użycia w innych projektach.
Prezentacja skupia się na plusach i minusach metodologii BDD, z perspektywy członka Core Teamu dużego, open-source’owego projektu. Stara się również pochylić nad perspektywą przeciętnego użytkownika takiego produktu… która może być jednocześnie podobna jak i zaskakująco odmienna.
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