Blog

28 listopada 2014 Wojciech Zawistowski

Jak robić bugfixy w BDD

Tagi:

Jak robić bugfixy w BDD

Rozwijanie nowych funkcjonalności przy pomocy BDD to bardzo czysty proces. Nasze testy zawsze wyprowadzamy z wymagań użytkownika. Rozbijamy te wymagania na kilka poziomów przy pomocy zagnieżdżonych kontekstów. Porządkujemy je w dobrze zorganizowaną strukturę, przypominającą niemalże dokumentację dla użytkownika końcowego – i opowiadającą spójną historię naszego systemu.

Niestety, gdy naprawiamy błędy, ten porządek lubi się rozsypywać.

Nie pozwól poprawkom błędów popsuć spójności Twojej specyfikacji

Gdy naprawiamy błąd, operujemy na bardzo niskim poziomie. Wychodzimy od konkretnego, szczegółowego opisu problemu i próbujemy go debugować, by odkryć jego przyczynę. Patrzymy przez to zazwyczaj z bardziej skupionego na szczegółach, wyjętego z kontekstu punktu widzenia niż wtedy, kiedy wychodzimy od wysokopoziomowych wymagań. W efekcie wykazujemy tendencję do pisania równie skupionych na szczegółach testów.

Nie powinniśmy jednak zapominać, że błędy nigdy nie istnieją w próżni. Błąd wynika albo z brakującego wymagania, albo z dziury w naszej implementacji jednego z istniejących wymagań. Nie ma czegoś takiego jak “niezależny” błąd.

Dlatego nie powinniśmy NIGDY traktować bugfixów, ani testów do tych bugfixów, jako niezależnych patchy. Naprawiając błąd, zawsze powinniśmy myśleć w jaki sposób zintegrować brakujący test z istniejącą specyfikacją, tak by pozostawała spójna.

Przyjrzyjmy się tej idei w działaniu:

Istniejący system

Kodujemy panel kontrolny dla statku kosmicznego. Typowym kompromisem na takich statkach jest rozdział energii pomiędzy poszczególnymi podsystemami. Nasze działka plazmowe i tarcze są tak energożerne, że nie możemy puścić napędu na więcej niż 30% jego maksymalnej mocy gdy są one włączone.

Oczywiście, kontrolowanie wszystkich tych systemów ręcznie jest niewygodne, dodajemy więc do naszego panelu kontrolnego ładny przełącznik, który automatycznie ustawi generator mocy w jeden z dwóch trybow: bitewny (z włączonym uzbrojeniem i napędem przyciętym do 30%) albo rejsowy (z uzbrojeniem wyłączonym i bez przycinania napędu).

Spójrzmy na odpowiadający tym wymaganiom fragment testów naszego panelu kontrolnego:

describe('Power Generator', function() {
describe('in battle mode', function() {
beforeEach(function() {
generator.setBattleMode();
});

it('unlocks weapon systems', function() {
expect(plasmaCannons).toBeUnlocked();
expect(shields).toBeUnlocked();
});

it('caps propulsion drive power', function() {
expect(propulsion.powerCap()).toEqual(propulsion.maxPower() * 0.3);
});
});

describe('in cruise mode', function() {
beforeEach(function() {
generator.setCruiseMode();
});

it('locks weapon systems', function() {
expect(plasmaCannons).toBeLocked();
expect(shields).toBeLocked();
});

it('allows max power to propulsion drive', function() {
expect(propulsion.powerCap()).toEqual(propulsion.maxPower());
});
});
});

Testy przechodzą i możemy wyruszać ku bezkresnym otchłaniom wszechświata.

Niestety, podczas naszego pierwszego spotkania z kosmicznymi piratami, odkrywamy że w systemie jest bug.

Błąd

Nasz strzelec pokładowy lubi siać plazmowymi pociskami przy dźwiękach epickiej bitewnej muzyki. Podkręca głośniki na maksa i… nasze tarcze nagle się wyłączają! Ledwo uchodzimy z życiem.

Sprawdzamy nasz system debuggerem i okazuje się, że nasz nowy Bombastyczny Subwoofer MK III pożera więcej mocy niż nam się wydawało. Nie może być uzywany równocześnie z tarczami.

Naiwny bugfix

Ponieważ jesteśmy w kontekście niskopoziomowych szczegółów, łatwo jest wpaść w pułapkę i “naprawić” naszą suitę testową w sposób dokładnie odpowiadający opisowi problemu, który właśnie odkryliśmy:

describe('Power Generator', function() {
describe('in battle mode', ...);

describe('in cruise mode', ...);

it('locks epic speakers when the shield is on', function() {
shield.turnOn();
expect(epicSpeakers).toBeLocked();
});
});

Wprawdzie taki test pozwala nam załatać błąd, ale wydaje się zupełnie nie na swoim miejscu. Nie opowiada wraz z resztą suity spójnej historii.

Czysty bugfix

Jeśli zastanowimy się przez chwilę, zauważymy, że blokowanie głośników nie powinno być bezpośrednio powiązane z tarczami. Jest częścią bardziej ogólnego, wysokopoziomowego scenariusza (który już się w naszej specyfikacji znajduje):

describe('Power Generator', function() {
describe('in battle mode', function() {
beforeEach(function() {
generator.setBattleMode();
});

it('unlocks weapon systems', ...);

it('caps propulsion drive power', ...);

it('locks epic speakers', function() {
expect(epicSpeakers).toBeLocked();
});
});

describe('in cruise mode', function() {
beforeEach(function() {
generator.setCruiseMode();
});

it('locks weapon systems', ...);

it('allows max power to propulsion drive', ...);

it('unlocks epic speakers', function() {
expect(epicSpeakers).toBeUnlocked();
});
});
});

Teraz wszystko wskakuje na swoje miejsce, tworząc spójną specyfikację.

Spójna historia a nie przypadkowa lista wyjątków

W powyższym przykładzie łatwo jest dostrzec problem, może się więc on wydawać nieco sztuczny. Jednak w prawdziwym kodzie, takie testy nie na swoim miejscu nie zawsze rzucają się w oczy. Jeśli nie podejmiemy świadomego wysiłku by je znaleźć i poprawnie skonsolidować z resztą suity, będzie się ona z biegiem czasu degenerować, zmieniając się z uporządkowanej specyfikacji w przypadkową listę różnych przypadków brzegowych i wyjąków.

Taka suita niewątpliwie będzie działć i wyłapywać błędy regresyjne, nie opowiada jednak spójnej historii, niepotrzebnie utrudniając zrozumienie systemu.


A jak to wygląda w Twoim przypadku? Spotkałeś się z podobnym problemem podczas naprawiania błędów w procesie BDD? Podziel się swoją opinią w komentarzach poniżej!

Zobacz na blogu

09.09.2022
Marcin Jahn
It’s Not Just HTTP It’s Not Just HTTP

In today’s world of cloud-based solutions, distributed systems, and microservices-based architectures, network communication is a...

23.08.2022
Adam Mrowiec
Konferencja IPC 2022 Berlin Konferencja IPC 2022 Berlin

Pandemia wreszcie się kończy, dlatego w tym roku postanowiliśmy wrócić do naszych wyjazdów na konferencje....