Sześć tygodni po 30 listopada
30 listopada OpenAI wypuściło ChatGPT. Kiedy pisałem podsumowanie 2022, miałem to na koncie od piętnastu dni i napisałem wtedy wprost, że to jest nagłówek, a nie narzędzie, którym przez rok pracowałem. Obiecałem też, że wrócę do tematu osobnym wpisem, kiedy będę miał do powiedzenia coś poza pierwszym wrażeniem. Jest 15 stycznia, minęło sześć tygodni i jedno miejsce, w którym to naprawdę wsiąkło w moją pracę, potrafię już nazwać.
Tym miejscem nie jest edytor i nie jest pipeline. Jest to szkicowanie przypadków testowych do historyjki, którą właśnie dostałem, w oknie przeglądarki, obok Jiry i obok zakładki z kryteriami akceptacji.
Chcę od razu ustawić granicę, bo w rozmowach z ostatnich tygodni ta granica rozmywa się najszybciej. Piszę o produkcie czatowym OpenAI w przeglądarce, na darmowym dostępie, z modelem z rodziny GPT-3.5 pod spodem. Nie piszę o wołaniu tego z kodu, nie mam tu żadnego klucza, żadnego POST-a i żadnej biblioteki do zainstalowania. To nie jest też Copilot, który od zeszłego roku siedzi u mnie w edytorze i podpowiada kolejną linię szarym tekstem. Tam promptem jest mój otwarty plik i wynikiem jest kod. Tutaj promptem jest historyjka, a wynikiem lista zdań po polsku, którą czytam i skreślam.
Kontrast, który muszę postawić, bo mam na blogu wpis wyglądający z daleka podobnie. W czerwcu 2021 generowałem dane testowe przez API Completions: waitlista, klucz w zmiennej środowiskowej, davinci w adresie endpointu, prompt few-shot pisany ręcznie, temperature i max_tokens do ustawienia, a na końcu walidacja schematem i plik w repozytorium. Tam wynikiem był artefakt, który wchodzi do pull requesta. Tu wynikiem jest szkic, który wchodzi do mojej głowy i do checklisty, a różnica w interfejsie jest mniej ważna od różnicy w tym, co z tego zostaje.
Co wklejam, czego nie
Wklejam trzy rzeczy i nic więcej.
Historyjkę użytkownika albo jej streszczenie własnymi słowami. Kryteria akceptacji, tak jak są zapisane w tickecie. Ograniczenia kontekstu, czyli rola użytkownika, kanał, przeglądarki z macierzy i to, jakimi danymi w ogóle dysponuję na środowisku testowym.
Nie wklejam logów, zrzutów z bazy, treści maili, identyfikatorów kont ani niczego, co przyszło ze środowiska produkcyjnego. Zasada, którą zapisałem w 2021 przy Completions, obowiązuje tu bez jednej poprawki, a nawet mocniej, bo okno czatu w przeglądarce zachęca do wklejenia “tylko tego jednego stack trace’a” znacznie skuteczniej niż skrypt w Pythonie. To jest wysyłanie cudzych danych do zewnętrznej usługi i żadna oszczędność czasu tego nie warta. Nie wklejam też nazw klientów ani niczego, z czego można je odtworzyć.
Dla tego wpisu przygotowałem sobie historyjkę syntetyczną, wymyśloną od zera, żeby nie było wątpliwości, skąd pochodzi:
Jako zarejestrowany użytkownik chcę zalogować się do panelu,
żeby zobaczyć swoje zamówienia.
Kryteria akceptacji:
- formularz ma pola email i hasło oraz checkbox "zapamiętaj mnie"
- poprawne dane przenoszą na /panel/
- niepoprawne dane pokazują jeden komunikat, bez wskazania,
które pole jest złe
- po pięciu nieudanych próbach konto jest zablokowane na 15 minut
- "zapamiętaj mnie" utrzymuje sesję po zamknięciu przeglądarki
Ograniczenia: aplikacja webowa, rola "klient", przeglądarki
Chromium i WebKit, dane wyłącznie syntetyczne z domen example.com.Prompt, którym to obudowuję, jest krótki i za każdym razem taki sam:
Jesteś testerem. Na podstawie historyjki i kryteriów poniżej
wypisz przypadki testowe dla ścieżki logowania.
Format: jedna linia na przypadek, w formie "warunek -> oczekiwany rezultat".
Pogrupuj po: happy path, walidacja, autoryzacja, stany brzegowe.
Nie proponuj kodu. Nie zakładaj funkcji, których nie ma w kryteriach.Dwie rzeczy w tym promptcie są celowe. Zakaz kodu, bo nie chcę dostać pliku testowego, tylko listę zdań, których nie da się przez pomyłkę wkleić do repozytorium. I zakaz zakładania funkcji, których nie ma w kryteriach, który nie działa, ale zmniejsza skalę problemu. O tym niżej.
Jak recenzuję output
Odpowiedź na powyższy prompt, po sklejeniu grup w jedną listę i po moim pierwszym przejściu, wygląda u mnie tak:
- Poprawny email i poprawne hasło -> przekierowanie na
/panel/, widoczna lista zamówień. - Poprawny email, złe hasło -> jeden ogólny komunikat, brak informacji, które pole jest złe.
- Email nieistniejący w systemie -> ten sam komunikat co w punkcie 2, bez ujawniania, czy konto istnieje.
- Oba pola puste, próba wysłania -> walidacja formularza, brak żądania do serwera.
- Email bez znaku
@-> komunikat o formacie, hasło zostaje w polu. - Email z białymi znakami na początku i końcu -> logowanie działa albo komunikat jest jednoznaczny; to zależy od decyzji, której w kryteriach nie ma.
- Cztery nieudane próby, potem poprawne hasło -> logowanie działa, licznik prób wraca do zera.
- Pięć nieudanych prób -> konto zablokowane, komunikat o blokadzie na 15 minut.
- Poprawne hasło w trakcie blokady -> logowanie nadal odrzucone, licznik czasu nie startuje od nowa.
Logowanie przez konto Google i przez Facebooka -> użytkownik wraca do panelu bez podawania hasła.Test wydajnościowy: 1000 równoległych logowań, czas odpowiedzi poniżej 200 ms.Klik w przycisk#login-submit, sprawdzenie nagłówka “Witaj ponownie!” i pliku cookieauth_token.
Punkty od pierwszego do dziewiątego zostawiam. Dwa z nich są warte więcej niż resztę razem: siódemka, czyli reset licznika po udanym logowaniu, i dziewiątka, czyli poprawne hasło w trakcie blokady. To dokładnie te dwa przypadki, o których zapominam, kiedy piszę listę o dziewiątej rano po standupie, i to jest realna wartość tego narzędzia. Szóstka jest jeszcze ciekawsza, bo to nie jest przypadek testowy, tylko brakująca decyzja produktowa. Idzie do autora historyjki jako pytanie, nie do zestawu jako test.
Trzy skreślenia, każde z innego powodu, i te trzy powody wracają w każdej sesji.
Dziesiątka to funkcja, której w historyjce nie ma. Model dopisał logowanie społecznościowe, bo w typowym formularzu logowania takie przyciski bywają. Prosiłem wprost, żeby nie zakładał funkcji spoza kryteriów, i to nie pomogło, więc traktuję ten zakaz jako zmniejszenie liczby zmyśleń, a nie jako gwarancję.
Jedenastka to wyjście z zakresu. Wymagania wydajnościowego nie ma, liczba 1000 i próg 200 ms są wzięte z powietrza, a wpisanie ich do checklisty funkcjonalnej kończy się dyskusją o progu, którego nikt nie ustalił.
Dwunastka jest najgroźniejsza, bo wygląda najbardziej fachowo. Selektor #login-submit, nagłówek “Witaj ponownie!” i nazwa ciasteczka auth_token to trzy zmyślone szczegóły podane tonem osoby, która widziała aplikację. Model nie widział. Nie zna mojego DOM-u, nie zna copy z projektu i nie wie, jak nazywa się sesja w tej aplikacji. Wszystko, co jest lokatorem, tekstem interfejsu albo nazwą pola, jest u mnie do weryfikacji z aplikacją, zawsze, bez wyjątku.
Osobno prowadzę drugą listę, której model nie wypisze, bo nie wie, czego nie wie. Przy tej historyjce brakowało: logowania samą klawiaturą z zatwierdzeniem Enter, wklejenia hasła z menedżera haseł, powrotu przyciskiem “wstecz” po wylogowaniu, wygaśnięcia sesji po czasie oraz zachowania po zmianie hasła w innej zakładce. Cztery z tych pięciu wynikają nie z kryteriów, a z tego, jak ta aplikacja psuła się w przeszłości, i to jest wiedza, której w oknie czatu nie ma.
Rachunek z sześciu tygodni wychodzi mi taki. Pierwszy szkic listy powstaje szybciej i jest szerszy niż mój ręczny. Właścicielem listy zostaję ja, a czas, który oszczędzam na pisaniu, w połowie wraca jako czas na czytanie i skreślanie.
Od listy do ticketu, nie do commita
Najważniejsza decyzja w całym tym procesie jest decyzją o tym, gdzie ta lista ląduje.
Ląduje w checkliście przy tickecie i w kryteriach akceptacji, jako tekst po polsku. Nie ląduje w .spec.ts i nie ląduje w klasie z [Fact]. Zestaw nadal piszę ja, ręcznie, w Playwrighcie na przypiętej wersji, i to jest to samo 1.29.2, na którym stoi mój projekt od początku stycznia. Powód nie jest ambicjonalny. Kod testowy, który powstał z listy zdań, ma dwa rodzaje treści: strukturę, która jest tania, i lokatory z asercjami, które są całą wartością testu. Punkt dwunasty wyżej pokazuje, co model robi z drugą kategorią.
Jest jeszcze powód procesowy. Lista przypadków w tickecie jest widoczna dla autora historyjki i dla programisty. Pytanie z punktu szóstego wraca do nich, zanim ktokolwiek napisze linijkę kodu, i to jest tańszy moment na tę rozmowę niż code review testu. Test, który powstałby od razu, zamroziłby moje zgadywanie zamiast wywołać pytanie.
I powód, który powtarzam od 2021. Miejsce, w którym decyduje się, czy zestaw testów cokolwiek znaczy, leży w procesie, nie w narzędziu. Szybszy pierwszy szkic listy przypadków nie zmienia tego, kto ustala kryteria wejścia i wyjścia, kto ogląda raport z nocy i co się dzieje z defektem po jego zgłoszeniu. Zmienia tylko to, że pierwsza wersja checklisty jest gotowa przed lunchem.
Dane pozostają osobnym tematem i nie ruszam ich z miejsca. Wnioski z wpisu o strategii zarządzania danymi testowymi trzymają się bez zmian: dane pod asercje pochodzą z fabryki z ziarnem albo z zatwierdzonego pliku w repozytorium. Okno czatu może podsunąć pomysł na przypadek brzegowy, na przykład email z bardzo długą częścią lokalną, ale samego rekordu z niego nie kopiuję do fixture. Dwa wywołania z tym samym wejściem nadal nie muszą dać tego samego wyniku i to jest ta sama cecha, którą opisywałem półtora roku temu.
Pułapki stycznia
Kilka rzeczy, które w tym momencie po prostu są, i o których wolę napisać teraz, niż udawać, że pracuję z gotowym narzędziem.
Dostępność. Darmowy czat regularnie odbija się komunikatem o obłożeniu, w moim przypadku najczęściej po południu. To akceptowalne przy biurku, kiedy mogę wrócić za dwadzieścia minut. To dyskwalifikuje ten sposób pracy wszędzie tam, gdzie coś musi się wykonać o określonej godzinie, i jest to dokładnie ta sama klasa ryzyka, przed którą uciekałem, stawiając stuby w kontenerach.
Brak pamięci między sesjami. W obrębie jednej rozmowy kontekst działa i mogę dopytać o kolejne warianty. Nowa rozmowa startuje od zera: nie zna moich konwencji, nie pamięta, że lokatory zmyśla, i nie wie, że w tym projekcie logowanie ma blokadę na piętnaście minut. Skończyło się to u mnie plikiem z trzema akapitami kontekstu, które wklejam na początku, i to jest mniej wygodne niż brzmi.
Pewność tonu. To jest najmniej techniczna i najkosztowniejsza pułapka. Zmyślony lokator jest podany tak samo stanowczo jak poprawny przypadek brzegowy, bez cienia wahania i bez sygnału, który dałby się złapać wzrokiem przy szybkim czytaniu. Dlatego skreślam osobno lokatory, teksty interfejsu, nazwy pól i wszystkie liczby, których nie ma w kryteriach.
Niedeterminizm. Ten sam prompt i ta sama historyjka dają w dwóch rozmowach dwie różne listy, częściowo pokrywające się. Do szkicowania to nie przeszkadza. Do jakiegokolwiek pomiaru, na przykład “czy pokrycie przypadków wzrosło”, jest bezużyteczne, więc nie próbuję z tego robić metryki.
Podsumowanie
Sześć tygodni po premierze mam jedno zastosowanie, którego się trzymam: ChatGPT jest u mnie szkicownikiem przypadków testowych, otwartym w przeglądarce obok ticketu, i skraca czas do pierwszej wersji checklisty. Wyłapuje przy tym dwa, trzy warianty, o których sam bym po standupie zapomniał, i tego mu nie odbieram.
Nie jest zamiennikiem testera, bo cała trudna część mojej pracy przy tej historyjce polegała na skreśleniu trzech punktów i na dopisaniu pięciu, których model nie znał. Nie jest generatorem suite, bo lokatory i asercje to jedyne, co w teście naprawdę kosztuje. Nie zdejmuje izolacji, którą wbijałem w zestaw przez cały 2021 rok, i nie zdejmuje nawyku czytania diffu przed commitem, który wyrobiłem sobie przy szarym tekście Copilota.
Chcę wiedzieć, ile z przypadków wygenerowanych w styczniu przeżyje pierwszy kontakt z aplikacją, a ile skasuję przy recenzji. Do tego nie potrzebuję nowego narzędzia, tylko cierpliwości i tych samych ticketów. Wrócę z liczbą, kiedy ją będę miał.

