Workshop RabbitMQ + BDD

De RabbitMQ workshop wordt aangeboden via GitHub Classrooms. Je krijgt automatisch toegang tot de workshop repository wanneer de assignment wordt geopend.

Les 1: RabbitMQ Fundamentals

Wat is RabbitMQ?

RabbitMQ is een open-source message broker die het Advanced Message Queuing Protocol (AMQP) implementeert. Het fungeert als een tussenpersoon voor berichten tussen verschillende services in een gedistribueerd systeem.

Waarom Message Queuing?

In traditionele monolithische applicaties communiceren componenten direct met elkaar via functie-aanroepen. In microservices architecturen is dit anders:

  • Loose coupling: Services hoeven niet direct van elkaar af te weten
  • Asynchrone verwerking: Services kunnen onafhankelijk van elkaar werken
  • Betrouwbaarheid: Berichten worden opgeslagen en kunnen opnieuw worden verwerkt
  • Schaalbaarheid: Services kunnen onafhankelijk worden geschaald

AMQP Componenten

RabbitMQ gebruikt het AMQP-protocol met de volgende kerncomponenten:

  • Producer: Service die berichten verstuurt
  • Exchange: Router die berichten ontvangt en doorstuurt naar queues
  • Queue: Buffer waar berichten worden opgeslagen
  • Consumer: Service die berichten verwerkt
  • Binding: Regel die bepaalt welke berichten naar welke queue gaan

Exchange Types

RabbitMQ ondersteunt verschillende soorten exchanges:

  1. Direct Exchange: Routeert op basis van exacte routing key match
  2. Topic Exchange: Routeert op basis van wildcard patterns
  3. Fanout Exchange: Stuurt berichten naar alle gebonden queues
  4. Headers Exchange: Routeert op basis van message headers

Real-world Analogie

RabbitMQ werkt als een postkantoor:

  • Producer = Afzender die een brief post
  • Exchange = Postkantoor dat brieven sorteert
  • Queue = Postvak waar brieven wachten
  • Consumer = Ontvanger die brieven ophaalt
  • Routing Key = Postcode die bepaalt waar de brief heen gaat

Les 2: BDD en Cross-Service Testing

BDD: Behaviour Driven Development

BDD (Behaviour Driven Development) is een softwareontwikkelingsproces dat voortbouwt op TDD (Test Driven Development). BDD combineert de algemene technieken en principes van TDD met ideeën uit domain-driven design en object-oriented analysis and design om softwareontwikkeling te voorzien van gemeenschappelijke tools en een gedeelde proces voor samenwerking tussen softwareontwikkelaars en domeinexperts.

BDD Principes

  • Gemeenschappelijke taal: Gebruik van een gedeelde taal die zowel technische als niet-technische stakeholders begrijpen
  • Gherkin syntax: Given-When-Then structuur voor het beschrijven van gedrag
  • Living documentation: Tests die als levende documentatie dienen
  • Collaboration: Samenwerking tussen developers, testers en business stakeholders

Voorbeelden van BDD scenarios

Feature: User Registration
  As a new user
  I want to register for an account
  So that I can access the system

  Scenario: Successful registration
    Given I am on the registration page
    When I fill in valid registration details
    And I submit the form
    Then I should be registered successfully
    And I should receive a confirmation email

BDD definitie en relatie met andere *DD's

We beginnen met de definitie van BDD op Wikipedia (2023):

"In software engineering, behavior-driven development (BDD) is a software development process that goes well with Agile software development process that encourages collaboration among developers, quality assurance experts, and customer representatives in a software project. It encourages teams to use conversation and concrete examples to formalize a shared understanding of how the application should behave.

[...] Behavior-driven development combines:

  • the general techniques and principles of TDD
  • with ideas from domain-driven design (DDD)
  • and object-oriented analysis and design

to provide software development and management teams with shared tools and a shared process to collaborate on software development."

BDD's relatie met andere *DD's: Ook test driven en testen 'inherente complexiteit'

Een belangrijk basisbegrip is 'self testing code'. Het is altijd goed het origine artikel van Martin Fowler hierover te lezen. Zijn onderstaande plaatje vat dit kort samen met beeld van code 'met een knop' die zichzelf kan testen. Maar dit kan een onervaren ICT'er onterecht het beeld geven dat je code kunt maken die op een of andere magische wijze zichzelf test (met een tool ofzo). En dat is natuurlijk niet het geval.

Figuur 1: Self testing code (Fowler, 2014)
Figuur 1: Self testing code (Fowler, 2014)

Een iets uitgebreidere samenvatting van 'self testing code' is dat je deze 'knop' realiseert door in een code naast de applicatiecode ook test code op te nemen. Deze test code roept de applicatiecode aan, en controleert dat deze de verwachte output krijgt. Hierbij is ook het doel dat er een hoge 'code coverage' haalt, typisch een line coverage van 80% in onze opleiding. Maar de afspraak hierover verschilt per bedrijf, en is ook afhankelijk van de complexiteit van de codebase (die - bij DDD aanpak - als het goed is vooral afhangt van de complexiteit van het onderliggende domein...). De vereiste code coverage hangt ook af of de codebase een monoliet is, of een microservices architectuur (MSA) heeft. Bij die laatste krijgen integratie testen meer belang en unit tests wat minder, en is code coverage minder van belang (want bij integratie tests is het geautomatiseerd bepalen van code coverage onmogelijk, of in ieder geval ingewikkelder/nog niet standaard).

Hoe dan ook, de interne werking is dus:

digraph G { rankdir=LR; // This sets the direction from left to right subgraph cluster_SelfTesting { label = "Self-Testing System"; A [label="Test code"]; B [label="Application code"]; A -> B; } }

Figuur 2: Intern onderscheid tussen test code (grote O 1) en (vangt de 'inherente' complexiteit conform DDD)

Net als TDD gaat BDD een stap verder dan enkel 'self testing code', want BDD is ook 'test driven'. Door eerst de test code te schrijven, denk je meteen vanuit 'client' kant aan welke interface je methode moet hebben, hoe je deze aanroept, en wat deze teruggeeft. Deze 'HOE roep ik het aan?' zit meer aan de WAT kant: "WAT is de interface", omdat de echte 'hoe' kant is: 'Hoe implementeer ik de code zodat deze daadwerkelijk het verwachte resultaat' teruggeeft. TDD grondlegger Kent Beck (van het Agile Manifesto) noemt dit in zijn article 'Canon-TDD' (2022) de 'Interface/Implementation Split':

"The first misunderstanding is that folks seem to lump all design together. There are two flavors:

  1. How a particular piece of behavior is invoked.
  2. How the system implements that behavior."

BDD -> Geen unit maar integratie tests!

Laten we in de nieuwe 'AI age' waar we in lijken te zitten, sinds de (hype rondom) de introductie van ChatGPT

Me: Wat voor soort testen doe je bij behaviour driven development (BDD)? Kun je deze kort samenvatten in Jip en Janneke taal!?

ChatGPT: Bij Behavior-Driven Development (BDD) worden verschillende soorten testen gebruikt om softwaregedrag te specificeren en te valideren:

  • Acceptatietesten: Test of de software doet wat de baas wil.
  • Scenario's: Verhalen schrijven over hoe de software moet werken.
  • Unit-tests: Kleine stukjes code testen om te zien of ze goed werken.
  • Integratietests: Controleren of alle stukjes samenwerken zonder problemen.
  • Automatisering: Tests laten de computer uitvoeren om tijd te besparen.
  • Regression Testing: Kijken of nieuwe veranderingen geen oude fouten veroorzaken.
De test pyramid - headspin

Figuur 3: De test pyramide (Bron: headspin.io - Open commons licentie)

ChatGPT geeft een mooi overzicht van verschillende soorten tests, die je als developer moet kennen. Maar om unit tests op te nemen in dit overzicht van voor BDD relevante testen is wel een fout van ChatGPT (terzijde: AI modellen/systemen laten zich helaas NIET goed unit testen vanwege hun non-deterministische gedrag). Dus daarom is dit doorgestreept. Want BDD richt zich dus op juist op integratietests of zelfs end-to-end tests (integratie tests over alle lagen/units/functionaliteit heen). Lees bv. op de Cucumber Blog website (z.d.), dat je Cucumber gebruikt voor integratie en end-to-end tests, en NIET voor unit tests:

"BDD should also be used to verify the wanted behaviour using integration tests.

[...] These unit tests will not be validated by a stakeholder as they are written in code, and most stakeholders don't read code. If they did we wouldn't need tools like Cucumber."

De regressie tests die ChatGPT vermeld zijn GEEN wezenlijk verschillende tests van de andere tests die hij eerder noemt. Want je gebruikt je unit-tests en integratie-tests ook als 'regressie tests' om eventuele regressie fouten op te sporen en/of uit te sluiten. Regressiefouten zijn fouten uit stukjes code die 'omvallen' als je later wijzigingen aanbrengt in andere delen van de code waarvan je denkt dat die ongerelateerd zijn, maar die toch aan elkaar gekoppeld blijken (te weinig 'loos coupling').

Je moet de kracht en bedoeling van de verschillende tests kennen:

  • Het voordeel van een unit test boven een acceptatietest is dat deze duidelijker aanwijst waar de fout vandaan komt (namelijk 1 specifieke unit).
  • Het nadeel van units apart is dat deze soms geen fouten aantonen gaan, maar dat er alsnog integratie errors tussen de units buiten beeld blijft, die een acceptatie test wel aanwijst.
  • Het voordeel van een integratie test is dat deze die fout wel laat zien
  • Net nadeel van een integratie test is dat deze een fout niet aanweijzen
  • En een nadeel is dat deze langzamer zijn, om dat bv. echt de database gebruikt word. Bij een unit test moet je externe dependencies/systemen als database altijd mocken

The Three Amigo's

Figuur 1: The three amigo's en het BDD proces met 3 stappen+1(van Pluralsight)

"The 'Three Amigos' is a meeting that takes user stories and turns them into clean, thorough Gherkin scenarios. It involves three voices (at least):

  • The product owner
  • The tester
  • The developer

https://johnfergusonsmart.com/wp-content/uploads/2017/11/iStock-147318638-1024x683.jpg

Figuur 2: The three amigo's, niet per se 3, niet per se Spaans, maar wel per se >=3, divers en onderling vriendschappelijke omgang :)

BDD: 'Executable specifications' en synonieme acro's voor BDD

Gherkin is the most commonly used plain-text language for writing executable specifications in Given-When-Then-And-But steps. These scenarios are commonly saved as .feature files.

Feature: Users should be able to book appointments.

Scenario: A user wants to book an appointment.

Given a user has booked an appointment

When the booking status is timed out

Then the appointment will be not confirmed

Bron: 'What are executable specifications?' - Cucumber.io blog, 7-2-2019

... "BDD aims to express requirements unambiguously, not simply create tests. The result may be viewed as an expression of requirements or a test, but the result is the same. Acceptance tests record the decisions made in the conversation between the team and the Product Owner so that the team understands the intended behavior.

There are three alternative labels to this detailing process:

  • Behavior Driven Design (BDD)
  • Acceptance Test–Driven Development (ATDD)
  • Specification by Example (SBE)

Bron & ©: Scaled Agile, Inc.

Cross-Service Testing en de "Testing Pyramid of Hell"

In microservices architecturen verandert de traditionele test pyramid drastisch. Waar je vroeger veel unit tests en weinig integratie tests had, is dit in een microservices omgeving niet meer effectief.

The Testing Pyramid of Hell

Figuur 4: The Testing Pyramid of Hell (Bron: TechBeacon)

In microservices architecturen krijg je de "Testing Pyramid of Hell":

  • Veel integratie tests: Omdat services via APIs of message queues communiceren
  • Contract tests: Om te zorgen dat services compatibel blijven
  • End-to-end tests: Om complete workflows te testen
  • Minder unit tests: Omdat individuele services vaak eenvoudiger zijn

BDD voor Cross-Service Testing met RabbitMQ

BDD is ideaal voor het testen van service-to-service communicatie via RabbitMQ:

Feature: Order Processing Across Services

Scenario: Order flows through multiple services via RabbitMQ
  Given a customer places an order
  And the order service is running
  And the inventory service is listening to the "orders" queue
  And the payment service is listening to the "payments" queue

  When the order service publishes an order message to RabbitMQ
  And the order service publishes a payment request to RabbitMQ

  Then the inventory service should receive the order message
  And the payment service should receive the payment request
  And the customer should receive an order confirmation

Huiswerk/opdracht

a. Bestudeer de theorie over Behaviour Driven Development zodat je theorie achter de BDD leerdoelen van deze week haalt. De keuze is afhankelijk van of je Java of .NET hebt gekozen in je weekopdracht:

  1. Bestudeer het materiaal van de 'Cucumber' (Java)
  2. Of SpecFlow (.NET) ReqNRoll (.NET) frameworks.

Edit 2024: De praktijk opdracht van BDD is in 2024 optioneel vanwege problemen overgang SpecFlow naar ReqNRoll en dat de 'Dare2Date' casus pas in week 4 volgt. De theorie van BDD moet je uiteraard wel kennen. Je zult de theorie dan in het Beroepsproduct van week 6&7 en het grote project moeten toepassen.

b. Opdrachten (maak tot en met opdracht 4, de rest in de les):

  1. Pak uit de Opdracht voor de DevWorkshop de EuroCard service, maak hier (eigen keuze) een Java of .NET project voor aan, voeg BDD dependencies en tools toe (zie PluralSight) en voeg volgens BDD stijl Cucumber of Specflow tests toe en realiseer zo test-first de methoden.
  2. Installeer hiervoor IntelliJ als je deze nog niet hebt/had (jaar gratis Ultimate editie voor HAN/AIM studenten) of Microsoft Visual Studio
  3. Schrijf Gherkin code (given, when, then) voor 4 testgevallen credit cards nummers: 1, 2, 1023 en 208724
  4. Schrijf glue code ('Step definitions')
  5. Refactor de 4 tests naar één enkele Gherkin 'datatable'
  6. Schrijf evt. ook dezelfde tests in TDD stijl (JUnit/XUnit).
  7. Moet er nog een validatie en aparte melding bij voor credit card nummers met ongeldig format? (eigenlijk zouden ze zo moeten zijn: 6703 4444 4444 4449 (bron) en zou je ook een 'issuer check` kunnen doen).
  8. Waarom wel, of waarom niet? En zo ja, hoe documenteer je dit? (want het staat niet in huidige spec)

Lesvragen

Behandelen opgekomen studentenvragen n.a.v. huiswerk of behandelde theorie. Als er geen vragen zijn, dan staan we even stil bij deze vragen:

  1. Kun je in algemeen zowel TDD als BDD doen in een project, of moet je kiezen?
  2. Welke CDMM checkpunten zijn relevant voor BDD?
  3. Voor Java is er Cucumber, voor .NET is erSpecFlow of ReqNRoll, maar welke taal gebruik je wel in beide voor BDD?
  4. Wat is een testsuite? Heeft een applicatie één testsuite?
  5. Het Gherkin voorbeeld op de pagina toont één feature met één scenario. Is dit typisch, of horen bij één feature vaak meer scenario's, of bij één scenario meerdere features?
  6. Waarom is de traditionele test pyramid niet meer geschikt voor microservices?
  7. Hoe kun je BDD gebruiken om cross-service communicatie via RabbitMQ te testen?

Quiz

Test je '101 kennis' over RabbitMQ met deze korte multiple choice quiz:

Leerdoelen

RabbitMQ Leerdoelen

  • Je kunt uitleggen wat een message broker is en de rol beschrijven van RabbitMQ in verbeteren van 'loose coupling' van systemen.
  • Je kunt het verschil beschrijven tussen verschillende messaging patterns, zoals work queues, publish-subscribe, routing en topics en gegeven een situatie/toepassing aangeven welke het geschiktst is.
  • Je kunt de componenten van het AMQP-protocol beschrijven, zoals exchanges, queues, bindings en routing keys, en uitleggen hoe ze samenwerken in RabbitMQ.
  • Je kunt uitleggen hoe RabbitMQ berichten routeert op basis van binding keys en routing keys.
  • Je kunt van de verschillende types exchanges in RabbitMQ in ieder geval fanout uitleggen en bijbehorende use-cases benoemen.
  • Je kunt een real-world analogie geven voor de werking van RabbitMQ, bijvoorbeeld door het te vergelijken met het sorteer- en bezorgproces van een pakketbezorgdienst.

BDD Leerdoelen

  • Je kunt uitleggen wat BDD is en hoe het verschilt van traditionele test-driven development
  • Je kunt Gherkin syntax gebruiken om user stories te beschrijven
  • Je kunt BDD scenarios schrijven in Given-When-Then format
  • Je begrijpt hoe BDD bijdraagt aan betere communicatie tussen stakeholders
  • Je kent de relatie van BDD met TDD, DDD en Agile/DevOps
  • Je kent de BDD three amigo's, en de 3 stappen van het iteratieve BDD proces
  • Je kunt de verhouding uitleggen tussen BDD test scenario's, test cases en suites met user stories en acceptatiecriteria uit Scrum
  • Je kent de test pyramide, het belang van de verschillende soorten testen hierin en de link met BDD
  • Je begrijpt waarom de traditionele test pyramid niet meer geschikt is voor microservices architecturen
  • Je kunt BDD gebruiken om cross-service communicatie via message queues te testen

Bronnen

Last change: 2025-07-02