TDD kontra BDD: Kluczowe różnice, jedna funkcja zbudowana w obie strony i kiedy używać każdej z nich
TDD pomaga programistom zaprojektować poprawny kod. BDD pomaga całemu zespołowi uzgodnić, co oprogramowanie powinno robić. Zobacz tę samą funkcję rabatów utworzoną w przypadku każdego podejścia i dowiedz się, jak te dwie funkcje ze sobą współdziałają.
- TDD oznacza najpierw napisanie testu, który zakończył się niepowodzeniem, potem tylko kodu wystarczającego do zaliczenia, a potem uporządkowanie.
- BDD opisuje zachowanie we wspólnym języku, zwykle jako „Podano”, „Kiedy”, „Wtedy”.
- TDD kieruje głównie projektowaniem kodu. BDD buduje głównie wspólne zrozumienie.
- Nie są rywalami i wiele zespołów używa BDD do obsługi funkcji i TDD do szczegółów poniżej.
Debata TDD kontra BDD często jest przedstawiana jako wybór strony. W rzeczywistości odpowiadają na różne pytania. Rozwój oparty na testach pyta, czy kod działa zgodnie z zamierzeniami programisty, podczas gdy rozwój oparty na zachowaniu pyta, czy zespół w ogóle zgadza się co do tego, co oprogramowanie powinno w ogóle robić.
Aby różnica była konkretna, przy każdym podejściu tworzymy jedną małą funkcję rabatową. Uruchomiliśmy kod w Pythonie 3.13, korzystając ze standardowej biblioteki, a poniższe wyniki pochodzą z tego uruchomienia. Część BDD wykorzystuje nasz własny mały moduł uruchamiający do pokazania mechanizmu, więc prawdziwy projekt korzystałby z narzędzia takiego jak Cucumber lub zamiast tego zachowywałby się.
Krótka odpowiedź
Programowanie oparte na testach (TDD) to praktyka kodowania. Piszesz mały test, który kończy się niepowodzeniem, przeprowadzasz go za pomocą najprostszego kodu, a następnie ulepszasz projekt. Testy są pisane w kodzie i głównie przez programistów. Kent Beck spopularyzował tę praktykę.
Rozwój oparty na zachowaniu (BDD) to praktyka współpracy, która wyrosła z TDD, a Dan North wprowadził ten termin. Programiści, testerzy i ludzie biznesu uzgadniają przykłady zachowania systemu, zapisane prostym językiem, a przykłady te można następnie zautomatyzować.
TDD kontra BDD obok siebie
| Aspekt | TDD | BDD |
|---|---|---|
| Główny nacisk | Poprawny, dobrze zaprojektowany kod | Wspólne zrozumienie zachowań |
| Wpisane | Język programowania | Prosty język, taki jak korniszon |
| Napisane przez | Deweloperzy | Biznes, kontrola jakości i programiści razem |
| Czytelne przez | Deweloperzy | Wszyscy w zespole |
| Typowy poziom | Jednostka | Cecha lub akceptacja |
| Punkt wyjścia | Nieudany test | Rozmowa z przykładami |
| Cykl | Czerwony, zielony, refaktor | Odkrywaj, formułuj, automatyzuj |
| Wspólne narzędzia | pytest, JUnit, Jest | Ogórek, zachowuj się, SpecFlow, Reqnroll |
Funkcja
Oto zasada biznesowa, którą wdrożymy: Zamówienia 1000 lub więcej otrzymują 10% zniżki. Członkowie otrzymują dodatkowe 5% zniżki na te zamówienia.
Może to brzmieć jasno, ale jest już niejednoznaczne. Czy członek wydający 500 otrzyma 5% zniżki? W zdaniu jest napisane „te rozkazy”, więc zakładamy, że nie. Należy o tym pamiętać, ponieważ jest to dokładnie ten rodzaj pytań, które BDD ma za zadanie stawiać na wczesnym etapie.
Zbuduj go za pomocą TDD
TDD podąża za krótką pętlą znaną jako czerwony, zielony, refaktor:
| Krok | Co robisz |
|---|---|
| Czerwony | Napisz jeden test na zachowanie, które jeszcze nie istnieje, i obserwuj, jak kończy się niepowodzeniem |
| Zielony | Napisz najprostszy kod, który sprawi, że przejdzie |
| Refaktoryzacja | Ulepsz kod, utrzymując zielone testy |
Oto nasze testy, po jednym dla każdej reguły:
class DiscountTests(unittest.TestCase):
def test_no_discount_below_1000(self):
self.assertEqual(discount(999, False), 0)
def test_ten_percent_from_1000(self):
self.assertEqual(discount(1000, False), 100)
def test_member_gets_extra_five_percent(self):
self.assertEqual(discount(1000, True), 150)
Następnie napisaliśmy kod w trzech etapach, po każdym przeprowadzając testy. Ścisła sesja TDD dodaje jeden test na raz, co pokazuje ten sam postęp:
Etap 1: najprostsza rzecz
def discount(total, is_member):
return 0
# 3 tests, 2 failed
# test_member_gets_extra_five_percent
# test_ten_percent_from_1000
Zwracanie zera wygląda głupio, ale dowodzi, że testy mogą w rzeczywistości zakończyć się niepowodzeniem, a jedynym testem, który przechodzi, jest zasada „braku rabatu”.
Etap 2: dodaj próg
def discount(total, is_member):
if total >= 1000:
return total * 0.1
return 0
# 3 tests, 1 failed
# test_member_gets_extra_five_percent
Etap 3: dodaj członków i posprzątaj
def discount(total, is_member):
rate = 0.1 if total >= 1000 else 0
if is_member:
rate += 0.05
return round(total * rate, 2)
# 3 tests, 0 failed
Wszystkie trzy przechodzą. Ale przeczytaj uważnie tę wersję. Daje członkowi wydającemu 500 5% zniżki, co nie jest tym, co zakładaliśmy, że ta zasada oznacza. Żaden test nie obejmował tego przypadku, więc nic go nie oznaczyło. TDD chroni tylko zachowanie, które miałeś przetestować. To jest prawdziwe ograniczenie i powód, dla którego rozmowy o przykładach są tak ważne.
Dlaczego cykl ma znaczenie
- Czerwony oznacza, że test działa. Test, który nigdy nie zawiódł, może niczego nie testować.
- Ekologia pozwala Ci się skoncentrować. Piszesz tylko taki kod, jaki jest potrzebny, co pomaga uniknąć nadmiernej inżynierii.
- Refactor utrzymuje wysoką jakość. Testy dają pewność, że można bezpiecznie zmienić projekt.
- Ma to wpływ na projekt. Kod, który jest trudny do przetestowania, jest często zbyt ściśle powiązany, co można zauważyć od razu.
Nasz samouczek dotyczący testów Pythona pokazuje wygodniejszy sposób pisania takich testów, a analiza wartości granicznych wyjaśnia, dlaczego jako wartości wybraliśmy 999 i 1000.
Zbuduj go za pomocą BDD
BDD zaczyna się od rozmowy, a nie od narzędzi. Często jest to krótkie spotkanie zwane trzema amigosami, podczas którego specjalista ds. produktu, programista i tester omawiają konkretne przykłady:
Tester: What about a member spending 500?
Product: Good question. No discount. It has to reach 1000 first.
Dev: And exactly 1000 counts?
Product: Yes, 1000 or more.
Ta jedna wymiana wyłapałaby problem w naszym kodzie TDD. Następnie zapisują swoje przykłady w ustrukturyzowanym formacie zwanym Ogórkiem:
Feature: Order discounts
Scenario: Member reaches the discount threshold
Given a member with a cart worth 1000
When they view the amount to pay
Then the amount to pay is 850
Scenario: Member below the threshold gets no discount
Given a member with a cart worth 500
When they view the amount to pay
Then the amount to pay is 500
| Słowo kluczowe | Cel |
|---|---|
| Biorąc pod uwagę | Sytuacja wyjściowa |
| Kiedy | Akcja lub wydarzenie |
| Potem | Oczekiwany wynik |
Każdy w zespole może to przeczytać i powiedzieć „tak, to prawda” lub „nie, to źle”. Nawet jeśli nigdy tego nie zautomatyzujesz, scenariusz wykonał już pożyteczną pracę.
Łączenie kroków z kodem
Narzędzia łączą każdą linię w prostym języku z funkcją zwaną definicją kroku. Aby pokazać pomysł, sami napisaliśmy małą wersję i uruchomiliśmy pierwszy scenariusz:
@step("a member with a cart worth 1000")
def given(ctx):
ctx["cart"] = Cart(is_member=True)
ctx["cart"].add(1000)
@step("they view the amount to pay")
def when(ctx):
ctx["amount"] = ctx["cart"].payable()
@step("the amount to pay is 850")
def then(ctx):
assert ctx["amount"] == 850
# ok Given a member with a cart worth 1000
# ok When they view the amount to pay
# ok Then the amount to pay is 850
Prawdziwe narzędzia dodają parametry, dzięki czemu pojedynczy krok może odpowiadać zarówno „1000”, jak i „500”, plus raportowanie. Rezultatem jest żywa dokumentacja: prosty opis systemu, który głośno zawodzi, jeśli przestaje być prawdziwy.
Wskazówka: Wartość BDD leży w rozmowie. Zespoły składające się z jednej osoby piszącej samodzielnie Gherkina po ukończeniu kodu ponoszą koszt narzędzia, prawie bez żadnych korzyści.
Pisanie dobrych scenariuszy
| Słaby | Lepiej |
|---|---|
Kiedy klikam przycisk o identyfikatorze „btn-77” |
Kiedy zastosuję kupon |
Wtedy wynik jest prawidłowy |
Wtedy kwota do zapłaty wynosi 850 |
| Dwadzieścia kroków w jednym scenariuszu | Jedno zachowanie na scenariusz |
| Opisuje ekran | Opisuje zamiar biznesowy |
Scenariusze opisujące kliknięcia i identyfikatory pól psują się przy każdym przeprojektowaniu, a czytelnicy biznesowi przestają je czytać. Zachowaj szczegóły interfejsu użytkownika w kodzie kroku. Scenariusze te są bliskimi kuzynami przykładów z przypadku testowego a scenariusza testowego i często są używane także w testach akceptacyjnych użytkownika.
Używanie obu razem
Obydwa w naturalny sposób pasują do przepływu pracy „z zewnątrz do wewnątrz”:
- Zespół uzgadnia zachowanie i pisze scenariusz BDD. To się nie udaje, bo jeszcze nic nie istnieje.
- Deweloper używa TDD do budowy potrzebnych małych jednostek.
- Testy jednostkowe przechodzą pomyślnie i ostatecznie także scenariusz.
- Wszystko działa w twoim potoku CI/CD.
Utrzymuj każdy na właściwym poziomie. Powinieneś mieć kilka scenariuszy ważnych zachowań i wiele szybkich testów jednostkowych dla szczegółów. Nasz przewodnik po testowaniu jednostkowym i integracyjnym opisuje równowagę.
Narzędzia
| Język | TDD (testowanie jednostkowe) | BDD |
|---|---|---|
| Pyton | pytest, test jednostkowy | zachowuj się, pytest-bdd |
| JavaScript | Jest, Vitest | Cucumber.js |
| Jawa | JUnit, TestNG | Ogórek-JVM |
| .NET | xUnit, NUnit | Reqnroll, SpecFlow |
Kiedy używać każdego z nich
TDD sprawdza się dobrze, gdy:
- Piszesz złożoną logikę, taką jak ustalanie cen, analizowanie lub uprawnienia.
- Naprawiasz błąd, ponieważ możesz go odtworzyć najpierw za pomocą testu zakończonego niepowodzeniem.
- Pracujesz nad kodem, który będzie często się zmieniać.
BDD sprawdza się dobrze, gdy:
- Reguły biznesowe są złożone, a nieporozumienia kosztowne.
- Osoby nietechniczne naprawdę przeczytają i omówią scenariusze.
- Kilka zespołów potrzebuje wspólnej definicji zachowania czegoś.
Może to być przesada, gdy: piszesz jednorazowy prototyp lub badasz niejasny pomysł techniczny. Wielu programistów najpierw wykonuje skoki, a następnie przepisuje je za pomocą testów. Narzędzia BDD są również słabą inwestycją, jeśli nikt poza inżynierią nigdy nie będzie patrzył na scenariusze. W takim przypadku zwykłe testy o dobrych nazwach są prostsze.
Typowe błędy
- Traktowanie BDD jako narzędzia testowego. Ogórek bez współpracy to po prostu szczegółowy sposób pisania testów.
- Testowanie implementacji w TDD. Testy powiązane z prywatnymi szczegółami psują się podczas każdego refaktora.
- Pomijanie kroku refaktoryzacji. Otrzymujesz pozytywne testy na bałaganie w kodzie.
- Pisanie testów po kodzie i wywoływanie go TDD. Jest to nadal przydatne, ale tracisz opinię o projekcie.
- Pisanie zbyt wielu scenariuszy interfejsu użytkownika. Stają się one powolne i niestabilne. Zobacz testy niestabilności.
- Pogoń za liczbami. Dobry zasięg wciąż pomija zachowania, których nikt nie brał pod uwagę, jak pokazuje nasz przykład.
Podsumowanie
TDD pomaga zbudować właściwą rzecz, a BDD pomaga zbudować właściwą rzecz. Nasz przykład rabatowy pokazał obie strony: kod przeszedł wszystkie testy, a już prosta rozmowa ujawniła błędne założenie. Wypróbuj to w swojej następnej małej funkcji. Zanim napiszesz jakikolwiek kod, napisz ze współpracownikiem trzy konkretne przykłady, a następnie zbuduj go po jednym teście, który zakończy się niepowodzeniem.
Najczęstsze pytania
Jaka jest główna różnica między TDD i BDD?
TDD koncentruje się na programistach piszących nieudane testy kodu przed wdrożeniem. BDD skupia się na tym, że cały zespół uzgadnia zachowanie, posługując się prostymi przykładami.
Czy można używać TDD i BDD razem?
Tak i wiele zespołów tak robi. Scenariusze BDD definiują funkcje z zewnątrz, a TDD steruje mniejszymi jednostkami kodu znajdującymi się pod nimi.
Czy BDD jest lepsze niż TDD?
Żadne z nich nie jest ogólnie lepsze, ponieważ rozwiązują różne problemy. BDD pomaga we wspólnym zrozumieniu, a TDD pomaga w projektowaniu kodu i szybkiej informacji zwrotnej.
Co to jest korniszon?
Korniszon to ustrukturyzowany, prosty format językowy do pisania scenariuszy przy użyciu słów kluczowych, takich jak Biorąc, Kiedy i Wtedy. Narzędzia takie jak Cucumber mogą go uruchomić.
Czy TDD spowalnia rozwój?
Na początku może wydawać się wolniejszy, szczególnie podczas nauki. Zespoły często odzyskują czas dzięki mniejszej liczbie błędów i bezpieczniejszym zmianom, chociaż efekt różni się w zależności od projektu.
Ucz się dalej
Potrzebujesz pomocy przy testowaniu, WordPressie lub SEO?
Pomagamy zespołom dostarczać lepsze oprogramowanie i rozwijać ich witryny. Powiedz nam, nad czym pracujesz, a my zaproponujemy jasny następny krok.
Więcej poradników na testowanie, sztuczna inteligencja i nowoczesny rozwój
Podobał Ci się ten artykuł? Wybierz temat i przejdź bezpośrednio do naszych najnowszych tutoriali.
Wyjaśnienie halucynacji AI: dlaczego modele językowe tworzą rzeczy i jak je zmniejszyć
Wyjaśniono halucynacje AI: czym są, dlaczego powstają w modelach językowych, typowe typy, jak je zredukować za pomocą uziemień i podpowiedzi oraz pr…
Przeczytaj artykuł →DevOpsWyjaśnienie zmiennych środowiskowych: pliki .env, sekrety i konfiguracja wykonana prawidłowo
Zmienne środowiskowe wyjaśnione na przykładach Pythona, które przeprowadziliśmy: ich odczytywanie i ustawianie, dlaczego każda wartość jest ciągiem …
Przeczytaj artykuł →Tworzenie oprogramowaniaWyjaśnienie ograniczenia szybkości interfejsu API: algorytmy, nagłówki i obsługa błędów 429
Ograniczanie szybkości API wyjaśnione za pomocą przeprowadzonej przez nas symulacji: zasobnik tokenów, stałe okno, przesuwane okno, 429 odpowiedzi, …
Przeczytaj artykuł →Automatyzacja testówWyśmiewanie w Pythonie: unittest.mock, patch i błędy, które sprawiają, że testy kłamią
Wyśmiewanie w Pythonie za pomocą unittest.mock, przetestowane: Mock i zwracana wartość, efekt uboczny, łatanie tam, gdzie używana jest nazwa, specyf…
Przeczytaj artykuł →Testowanie oprogramowaniaWyjaśnienie zasięgu kodu: pokrycie linii i oddziału oraz co naprawdę oznacza 100%
Wyjaśnienie pokrycia kodu za pomocą prawdziwego pliku cover.py: pokrycie linii, gałęzi i warunków, przeczytanie raportu, dlaczego 100% pokrycia może…
Przeczytaj artykuł →Bazy danych i SQLDELETE vs TRUNCATE vs DROP w SQL: różnice, wycofywanie i kiedy używać każdego z nich
DELETE vs TRUNCATE vs DROP wyjaśnione za pomocą przetestowanego SQL: co każdy usuwa, wycofywanie, wyzwalacze, resetowanie tożsamości, prędkość i czy…
Przeczytaj artykuł →Bazy danych i SQLWyjaśnienie widoków SQL: tworzenie, używanie i unikanie pułapek wraz z przykładami
Widoki SQL wyjaśnione za pomocą zapytań, które uruchomiliśmy: tworzenie widoku, wyniki na żywo, ukrywanie kolumn, wydajność, widoki z możliwością ak…
Przeczytaj artykuł →Tworzenie oprogramowaniaInterfejs TypeScript a typ: rzeczywiste różnice, testowane za pomocą kompilatora
Interfejs TypeScript a typ, sprawdzony z prawdziwym kompilatorem: co oba mogą zrobić, co tylko jeden, łączenie deklaracji, rozszerzenia a przecięcia…
Przeczytaj artykuł →Tworzenie oprogramowaniaPython *args i **kwargs wyjaśnione: elastyczne funkcje z przejrzystymi przykładami
Python *args i **kwargs wyjaśnione za pomocą uruchomionego przez nas kodu: pakowanie i rozpakowywanie, kolejność parametrów, argumenty zawierające t…
Przeczytaj artykuł →Tworzenie oprogramowaniaFunkcje Lambda w Pythonie: składnia, rzeczywiste zastosowania i kiedy ich nie używać
Funkcje lambda w Pythonie wyjaśnione za pomocą uruchomionego przez nas kodu: składnia, sortowanie za pomocą klucza, mapa i filtr, domknięcia, pułapk…
Przeczytaj artykuł →Sztuczna inteligencjaKim są agenci AI? Jak one działają, dzięki prostej pętli agenta, którą możesz uruchomić
Kim są agenci AI? Prosty przewodnik po pętli agenta, narzędziach, pamięci i poręczach, z małą pętlą w Pythonie, którą uruchomiliśmy, prawdziwymi zag…
Przeczytaj artykuł →DevOpsMonitorowanie a obserwowalność: prawdziwa różnica z logami, wskaźnikami i śladami
Wyjaśnienie monitorowania a obserwowalności: znane i nieznane awarie, dzienniki, metryki i ślady, opis incydentu przy kasie, złote sygnały, OpenTele…
Przeczytaj artykuł →DevOpsWoluminy platformy Docker a mocowania powiązań: utrwalanie danych kontenera we właściwy sposób
Wyjaśniono woluminy Dockera a montowanie powiązań: dlaczego dane kontenera znikają, nazwane woluminy, montowanie powiązań, tmpfs, przykłady tworzeni…
Przeczytaj artykuł →Automatyzacja testówTDD vs BDD: kluczowe różnice, jedna funkcja zbudowana w obie strony i kiedy używać każdej z nich
TDD vs BDD wyjaśnione za pomocą jednej funkcji rabatowej zbudowanej w obie strony: refaktoryzacja czerwono-zielona z uruchomionym przez nas kodem, s…
Przeczytaj artykuł →Tworzenie oprogramowaniaGit merge vs rebase: różnice, przykłady i kiedy czego używać
Git merge vs rebase wyjaśnione na prawdziwym wyniku logu, z tabelą porównawczą, złotą zasadą rebase i bezpiecznym procesem pracy zespołu.
Przeczytaj artykuł →Tworzenie oprogramowaniaPython list comprehension: składnia, przykłady i kiedy jej unikać
Python list comprehension wyjaśnione: składnia, filtrowanie, if-else, zagnieżdżone pętle, zmierzone czasy i sytuacje, w których zwykła pętla for jes…
Przeczytaj artykuł →Bazy danych i SQLSQL JOIN – wyjaśnienie z przykładami (Inner, Left, Right, Full)
Zrozum złączenia SQL krok po kroku na przykładowych tabelach i zapytaniach: INNER, LEFT, RIGHT, FULL, CROSS i SELF, plus typowe błędy i wskazówki wy…
Przeczytaj artykuł →