Czym jest BDD? Behavior Driven Development jest procesem wytwarzania oprogramowania w oparciu o konkretną strukturę formułowania wymagań. Cała aplikacja budowana jest z komponentów, małymi historyjkami które opowiadają o tym jak powinien zachować się program gdy zajdą pewne okoliczności – z ang. stories. Każde story budowany jest na schemacie given,when,then :

given – określa warunki początkowe, przedstawia aktora, zapoznaje odbiorcę z kontekstem historyjki,
when – opisuje akcje, występujące zdarzenie,
then – informuje o oczekiwanych rezultatach.

prosty przykład:

given: mając dwie dowolne liczby naturalne
when: postępując zgodnie z algorytmem Euklidesa
then: powinienem otrzymać liczbę będącą ich największym wspólnym dzielnikiem.

Zbyt proste? Zbyt mało praktyczne? Pójdźmy dalej:

given: jako zalogowany użytkownik internetowego serwisu bankowego, który ma na koncie kwotę X
when: próbując wykonać przelew na kwotę > X
then: powinienem otrzymać komunikat o braku możliwości wykonania transakcji.

Trzeba przyznać, że takie wymaganie, choć proste, już wygląda jakby mogło istnieć w specyfikacji jakiegoś istniejącego serwisu.

Co zatem osiągamy formując tak wymagania? Przede wszystkim każda historyjka to konkretna wartość biznesowa, definiując zadania w ten sposób nie marnujemy czasu na rzeczy które nie są istotne dla klienta.

Druga rzecz: czytelność. Taki tekst zostanie dobrze odebrany zarówno przez programistę jak i osoby kompletnie nietechniczne. Po przedstawieniu klientowi tej metody możemy szybko sprawnie ustalać wymagania które będą zatwierdzone, a być może i tworzone przez właściciela biznesowego.

Kolejną rzeczą jest właśnie testowanie i weryfikacja wymagań. Chyba każdy programista spotkał się kiedyś z sytuacją w której oddał ukończone zadanie, natomiast przy samej prezentacji okazało się, że klientowi (lub kierownikowi) nie do końca chodziło o takie rozwiązanie.

Trzeba było wrócić do biurka i przerabiać kod. Definiując zadania w BDD, możemy od razu zacząć od konkretnych testów, które będą wyznacznikiem ukończenia  zadania. Język programowania jest nieistotny, ważny jest schemat given,when, then, przykładowo dla javy:

    @Test
    void testname() {
        //given

        //when

        //then
    }

(osobiście polecam dodanie sobie takiego szablonu testów do IDE)

możemy wtedy zająć się problemem rozwiązywania problemów z podanych przykładów:

   @Test
    void runningEuclideanAlgorithmShouldReturnGCD() {
        //given
        EuclideanAlgorithm euclideanAlg = new EuclideanAlgorithm();
        Integer[] testCase1 = {4, 6};
        Integer[] testCase2 = {12, 12};

        //when
        Integer result1 = euclideanAlg.calculate(testCase1[0], testCase1[1]);
        Integer result2 = euclideanAlg.calculate(testCase2[0], testCase2[1]);

        //then
        assertEquals(2, result1);
        assertEquals(12, result2);
    }

Stosując dobre praktyki TDD możemy spokojnie zacząć od napisania takiego testu, następnie wystarczy tak zaimplementować metodę „calculate” aby test się zazielenił* ;). W bardziej zawiłym systemie:

    @Test
    void runningTransactionOnLowBalanceShouldCauseError() {
        //given
        ClientUser user = BankMocks.createLoggedClientUser();
        ClientUser.getAccount().setBalance(100);
        Account destAccount= BankMocks.createAccount()

        //when
        Transfer transfer = user.transfer(destAccount.getAccountNo(), 150);

        //then
        assertFalse(transfer.isSuccess());
        assertEquals(transfer.getErrorMessage(), "Lack of founds for this transaction");
    }

Realizując tę historyjkę najpewniej programista nie musiałby tworzyć metody „transfer” a jedynie ją modyfikować, co w przypadku dobrego pokrycia testami jest dużo bardziej bezpieczne.

Oczywiście, jak wspomniałem język programowanie jest całkowicie nieistotny, jako przykład mogę podać fragment testu produkcyjnego kodu napisanego w angularze:

describe('service', function() {

    var service, $httpBackend;

    beforeEach(module('restangular'));
    beforeEach(module('simulator.services'));

    beforeEach(inject(function (simulatorService, $injector) {
        $httpBackend = $injector.get("$httpBackend");
        service = simulatorService

    }));

    it("should contain sendRequest function", inject(function () {
        expect(service).toBeDefined();
        expect(service.sendRequest).toBeDefined();
    }));

    it("should run callback after sending request with data from backend", inject(function () {
        //given
        var data = {resourceUrl: "sth"};
        var mock = {callback: function (data) {
        }};
        spyOn(mock, "callback");
        var respondData = ["str", "sad"];

        //when
        service.sendRequest(data, mock.callback);
        $httpBackend.when("POST", /.*/).respond(function (method, url, data) {
            return [200, respondData , {}];
        });

        //then
        $httpBackend.flush();
        expect(mock.callback).toHaveBeenCalledWith(respondData);
    }));

    it("call backend with data in URL", inject(function () {

        //given
        var data = {resourceUrl: "group/{nb}", requestParameters: [
            {name: "nb", value: 123}
        ]};
        var mock = {callback: function () {
        }};
        spyOn(mock, "callback");

        //when
        $httpBackend.when("POST", /.*api\/group\/123/).respond(function (method, url, data) {
            return [200, [""], {}];
        });
        service.sendRequest(data, mock.callback);

        //then
        $httpBackend.expectPOST(/.*api\/group\/123/);
        $httpBackend.flush();
        expect(mock.callback).toHaveBeenCalled();
    }));

Więcej o testowaniu javascriptów za pomoca frameworka jasmine można poczytać we wpisie Michała Piątka tutaj.

Zainteresowanym samym BDD polecam blog Karola Sójko – trójmiejskiego orędownika BDD ;) http://blog.karolsojko.com

* oczywiście Ci bardziej ortodoksyjni zwolennicy TDD powiedzieliby, że najpierw należałoby sprawić aby metoda rozwiązywała jedynie przypadek trywialny, np „calculate(1,1)” ale to chyba temat na cały inny wpis ;).