Po co recap
Jest 15 grudnia i mam za sobą rok, w którym w moim edytorze pojawiło się coś, czego wcześniej tam nie było. Mógłbym z tego zrobić dwunasty tutorial, wkleić jeszcze jeden plik konfiguracyjny i zamknąć temat. Nie zrobię tego, dokładnie z tego samego powodu, co rok temu: lista narzędzi, które przewinęły się przez ten blog od stycznia, jest do odtworzenia z archiwum w dwie minuty i nikomu nic nie daje.
Recap ma dla mnie sens tylko wtedy, kiedy zadaje pytanie, którego pojedynczy wpis zadać nie potrafi, bo siedzi za blisko swojego narzędzia. W tym roku mam dwa takie pytania i one się ze sobą zaczepiają.
Pierwsze: czy sztuczna inteligencja weszła do mojego codziennego pisania testów, i jeżeli tak, to gdzie konkretnie weszła. Nie w sensie nagłówków, tylko w sensie klawiatury. Odpowiedź, do której doszedłem po dwunastu miesiącach, jest węższa i nudniejsza niż to, co czytam w sieci: weszła do edytora, jako podpowiedź kolejnej linii, i nie weszła nigdzie indziej.
Drugie: czy izolacja, którą przez cały 2021 rok wbijałem w zestaw testów, nadal trzyma, kiedy dokładam do niego nowy runner do przeglądarki. Bo to jest właśnie ten moment, w którym zeszłoroczne wnioski dostają egzamin. Łatwo pisać o czystym stanie, kiedy się niczego nie zmienia.
W zeszłorocznym podsumowaniu obiecałem sobie jedno zdanie: że w 2022 będę mierzył flake rate i czas przebiegu zestawu, a nie liczbę narzędzi, które umiem postawić. Zapisałem to jako intencję i wracam do tego pod koniec, bo intencja spotkała się w tym roku z narzędziem, które kusi dokładnie odwrotną metryką.
Fundament z zeszłego roku
Zacznę od tego, czego ten rok nie skasował, bo to jest większa część odpowiedzi niż cokolwiek, co dołożyłem.
Osi z 2021 nie ruszam. Kontener zamiast instalacji, stub zamiast cudzego środowiska, ziarno zamiast losowych danych, równoległość dopiero wtedy, gdy stan nie wycieka. Testcontainers na MSSQL i polityki ponawiania na Polly nadal są u mnie kanonem i nadal obowiązuje przy nich to samo zastrzeżenie: retry leczy chwilową usterkę cudzej usługi, a nie brak izolacji. Ten rok nie dał mi ani jednego powodu, żeby to zdanie zmiękczyć.
Rok zacząłem od wpisu, który z zewnątrz wygląda na najmniej modny z całych dwunastu miesięcy, a dla mnie jest podstawą wszystkiego, co przyszło później. W styczniu rozpisałem organizację testów w xUnit i NUnit na .NET 6: xUnit 2.4.1 z nową instancją klasy na każdy test i jawnym współdzieleniem przez fixture oraz kolekcje, NUnit 3.13.2 z FixtureLifeCycle jako pierwszym krokiem w starym zestawie. Wniosek z tamtego wpisu brzmiał: organizacja fixture jest warunkiem równoległości, a nie jej skutkiem. Po roku dopisuję do tego drugie zdanie. Organizacja fixture jest też warunkiem sensownego dokładania nowych warstw, bo zestaw, w którym nie wiem, kto jest właścicielem stanu, nie przyjmuje żadnego nowego narzędzia bez awantury.
Dwie rzeczy z infrastruktury zmieniły w tym roku nazwę, a nie strategię, i chcę je odnotować, żeby nikt nie czytał zeszłorocznych wpisów jako listy błędów. Compose w wersji drugiej jest ogólnie dostępny od 26 kwietnia i na nowych jobach wołam go bez myślnika. Moje wpisy o Dockerze dla QA i o środowisku testowym na Compose uczą wariantu z myślnikiem i tak zostaje, bo to jest zmiana komendy, nie zmiana podejścia. Podobnie Selenium Grid w Dockerze zostaje narzędziem od macierzy przeglądarek, a nie od pojedynczego scenariusza na push.
Copilot: preview, boilerplate, GA
Najgłośniejszy wątek roku ma u mnie trzy wpisy i jedną płaską linię między nimi.
W lutym opisałem technical preview GitHub Copilota. Warto pamiętać, co to znaczyło w lutym: waitlista, darmowy dostęp dla wpuszczonych, VS Code w linii 1.64 i szary tekst przed kursorem. Ogłoszenie preview jest z 29 czerwca 2021, więc nawet wtedy nie pisałem o premierze, tylko o narzędziu, które od ponad pół roku istnieje za bramką.
W marcu poszedł wpis konkretniejszy: Copilot przy szkieletach testów NUnit i xUnit. To jest ten moment, w którym przestałem opisywać wtyczkę i zacząłem opisywać własną pracę. Wynik z tamtego miesiąca powtarzam bez zmian, bo dziewięć miesięcy go nie ruszyło: około jednej trzeciej klepania mniej i zero procent decyzji. Kolejny [InlineData], konstruktor przypisujący pola, blok using na górze pliku i szkielet metody, której sygnaturę napisałem sam - tak. Asercja, lokator, dobór danych do gałęzi i cykl życia fixture - nie, i te cztery miejsca były systematycznie prawdopodobne zamiast poprawnych.
21 czerwca Copilot wyszedł z technical preview. Zniknęła lista oczekujących, pojawił się cennik: 10 USD miesięcznie albo 100 USD rocznie dla osób indywidualnych, 60 dni bezpłatnego trialu, darmowy dostęp dla zweryfikowanych studentów i osób utrzymujących popularne projekty open source, a dla ludzi z preview darmowy dostęp jeszcze do 22 sierpnia. Napisałem wtedy zdanie, które jest dla mnie najważniejszym zdaniem całego roku o tym narzędziu: zmieniła się bramka dostępu i billing, nie zmienił się sposób pracy. Nadal Tab i Esc, nadal szary tekst, nadal nie ma miejsca, w którym mógłbym o cokolwiek zapytać.
Reszta zmian w produkcie w tym roku jest po stronie dostępności, nie kategorii. Rozszerzenie do Visual Studio 2022 wyszło 29 marca, więc od wiosny nie trzeba siedzieć w VS Code. Od czerwca w ustawieniach jest opcjonalny filtr blokujący sugestie pokrywające się z publicznym kodem w blokach około stu pięćdziesięciu znaków lub dłuższych. Filtr jest przełącznikiem, nie narzędziem do sprawdzania pochodzenia kodu, i nie zdejmuje ze mnie ani jednej minuty czytania diffu.
Kontrast, który muszę postawić po raz czwarty w tym roku, bo wraca w każdej rozmowie. W czerwcu 2021 opisywałem eksperyment z generowaniem danych testowych przez API Completions: POST na endpoint z davinci w adresie, prompt few-shot pisany ręcznie, temperature i max_tokens do ustawienia, tekst w odpowiedzi, walidacja schematem i dopiero wtedy plik w repozytorium. Efektem były dane, czyli artefakt, który ktoś recenzuje w pull requeście. Copilot jest po drugiej stronie: promptem jest mój otwarty plik, parametrów nie ma, a efektem nie jest artefakt, tylko sposób, w jaki powstał mój kod. Po wciśnięciu Tab nie da się go odróżnić od tego, co wpisałem palcami, i właśnie dlatego recenzja przed commitem jest przy tym narzędziu ważniejsza, a nie mniej ważna.
Podsumowując rok jednym zdaniem: sztuczna inteligencja weszła u mnie do edytora jako przewidywanie kolejnej linii i została tam. Płacę za to abonament z prywatnego konta, włączam to tylko tam, gdzie mam jasną zgodę właściciela kodu, i mam wypracowany nawyk odrzucania czterech kategorii podpowiedzi bez czytania do końca.
Playwright: decyzja, setup, pipeline
Drugi wątek roku jest cichszy i kosztował mnie więcej pracy.
W kwietniu napisałem, dlaczego zaczynam schodzić z Cypressa na Playwright. Zacząłem tamten wpis od zdania, które powtórzę tutaj, bo w recapie roku łatwo je zgubić: Playwright nie jest nowy. Wydanie 1.0.0 jest z 6 maja 2020, a oficjalny runner @playwright/test i Trace Viewer są z czerwca 2021. Nowa była tylko moja decyzja. Powody wypisałem cztery: trzy silniki z jednego API, w tym WebKit na Linuksie, nowy BrowserContext na każdy test, trace z agenta CI zamiast wideo, oraz runner z workerami w paczce zamiast usługi z kluczem. Pin tamtego wpisu to 1.21.0 z 12 kwietnia.
W maju postawiłem pusty projekt na 1.22.0 i tam padły dwa wnioski, które przetrwały cały rok. Wersję przypinam od pierwszego commita, bo paczka ciągnie za sobą konkretne buildy przeglądarek i daszek w package.json oznacza cichą podmianę silnika w środku sprintu. Ciężar konfiguracji przenosi się z testu do jednego pliku: równoległość, retry, artefakty i macierz przeglądarek to cztery pola w configu, a test zostaje krótki.
W sierpniu obie moje warstwy trafiły do jednego pliku pipeline’u: NuGet Testcontainers w 2.1.0 na jobie integracyjnym i Playwright 1.25.0 na jobie przez przeglądarkę. Nawiasem, paczka .NET zmieniła w czerwcu nazwę z DotNet.Testcontainers i to jest jedyna rzecz z tamtego wpisu, która myli ludzi bardziej niż sam YAML. Trzy wnioski z sierpnia zostają: dwa joby czyta się lepiej niż jeden długi, kontener podniesiony w jednym jobie nie istnieje w drugim, a job z Testcontainers stoi na Linuksie, bo demon Dockera na hostowanym agencie Windows nie podniesie linuksowego obrazu bazy.
Teraz część, której nie napiszę, choć byłoby przyjemniej. Nie mam przepisanej suite i nie podam procentu. Mam decyzję z kwietnia, że nowe testy przez przeglądarkę powstają w Playwrighcie, mam garść smoke’ów na CI i mam pytania, na które nie znam odpowiedzi, na przykład ile z obecnych testów Cypressa w ogóle warto przenosić, zamiast napisać od nowa albo skasować. To jest start migracji, nie jej koniec.
I zdanie, które trzeba postawić w recapie wprost, bo dwunastomiesięczny łuk łatwo czyta się jako wyrok. Cypress nie jest martwy. Zestaw, który u mnie chodzi, chodzi dalej, seria wpisów od refaktoru App Actions kontra Page Object Model przez pipeline na Azure DevOps do publikowania wyników zostaje na blogu, a Cypress wydał w tym roku dwie duże wersje: dziesiątkę w czerwcu i jedenastkę w listopadzie. Zmieniam narzędzie dla nowej pracy, nie ogłaszam pogrzebu tego, co działa.
API: kontrakty i in-memory
Trzeci wątek nie ma w sobie ani przeglądarki, ani modelu językowego, a moim zdaniem dał w tym roku najwięcej.
W lipcu dołożyłem do stubów consumer-driven contracts na PactNet, w wersji 4.1.0 na .NET 6. Powód, dla którego temat wrócił, jest techniczny i chcę go nazwać dokładnie: sam pomysł jest z 2006 roku, Pact z 2013, a nowa jest linia 4.x dla .NET, czyli jeden NuGet z rdzeniem w Ruście i mock serverem w procesie zamiast pakietów per system. Kontrakt zamyka lukę, której nie zamykały ani stuby WireMocka, ani kontener z prawdziwą bazą: stub jest moim wyobrażeniem o cudzej odpowiedzi i milczy w dniu, w którym tamta strona zmieni pole. Kontrakt jest kanałem informacji zwrotnej do cudzego pipeline’u.
W październiku wyszedł wpis o DI pod testy w .NET z WebApplicationFactory i to jest trzecia ścieżka do tej samej aplikacji, którą warto zestawić z dwiema poprzednimi, zamiast wybierać z nich jedną. Host żyje w pamięci procesu testowego, zależności podmieniam w kontenerze DI, nie ma Dockera, nie ma zimnego pobierania obrazu i nie ma przeglądarki. Cena jest oczywista: to nie jest prawdziwy serwer bazy i nie jest to prawdziwy silnik renderowania.
Ułożyło mi się z tego pod koniec roku dość proste rozliczenie kosztów. Host w pamięci biorę na to, co jest logiką warstwy API i sklejeniem zależności. Kontener biorę wtedy, kiedy sprawdzam coś, o czym SQL wie więcej niż moje wyobrażenie o SQL-u. Kontrakt biorę na granicę między moim systemem a cudzym. Przeglądarkę biorę na to, co realnie widzi człowiek, i biorę jej najmniej, bo jest najdroższa. Żadna z tych czterech rzeczy nie zastępuje pozostałych i największy błąd, jaki widzę u siebie w archiwum, to próbowanie zamiany jednej na drugą, bo była pod ręką.
Strategia triggerów
Wrześniowy wpis o strategii testów w CI/CD był momentem, w którym przestałem dokładać narzędzia i zacząłem układać to, co już mam.
Podział wyszedł po jednym kryterium: co się stanie, jeżeli ten test padnie dziesięć minut po merdżu zamiast dziesięć minut przed. Pull request bierze to, co jest szybkie i deterministyczne: jednostki, kontrakty po stronie konsumenta i jeden smoke z własnym stanem. main bierze prawdziwe zależności, czyli integrację z bazą w kontenerze i weryfikację kontraktu po stronie dostawcy. Noc bierze pełne E2E i macierz silników, z fail-fast wyłączonym, bo z nocnego przebiegu chcę pełnego obrazu, a nie pierwszej porażki.
Liczba, którą z tego mam, jest jedyną liczbą w tym recapie i celowo nie robię z niej sukcesu: bramka na pull requeście zeszła u mnie z trzydziestu czterech minut do siedmiu, a suma czasu maszyn nie spadła ani o minutę. Te same testy nadal się wykonują, tylko gdzie indziej. Nie zrobiłem nic szybciej, przesunąłem czekanie z człowieka na harmonogram.
Warunkiem wstępnym całej tej układanki jest kolejność, którą rozpisałem w równoległych testach na CI rok temu i której w tym roku nie musiałem korygować. Najpierw izolacja, potem wall-clock. Podział po triggerze na źle izolowanym zestawie daje dokładnie to samo, co równoległość na źle izolowanym zestawie, czyli szybsze migotanie. Do tego dochodzi mechanika z Testcontainers w Azure Pipelines: Docker siedzi na hoście agenta, a nie w środku joba kontenerowego, i to się przez rok nie zmieniło.
Jedna pokusa wraca u mnie co kwartał, więc zapiszę ją i tutaj. Ponawianie nie jest przepustką z nocy na pull request. Test, który potrzebuje trzech prób, żeby zmieścić się w bramce, nie należy do bramki. Dotyczy to tak samo retries w konfiguracji Playwrighta, jak [Retry] w zestawie .NET.
Visual 15 listopada
Listopad zszedł mi na porównaniu narzędzi do visual regression i to jest dobre miejsce, żeby pokazać, co w tej dziedzinie zmienił rok, bez pisania drugiego tutoriala.
Oś jest jedna: gdzie mieszka wzorzec i kto zatwierdza jego zmianę. Wzorzec w repozytorium daje mi Playwright w rdzeniu, bo asercja na screenshocie jest w runnerze od 1.22.0 z maja, oraz BackstopJS 6.1.4 z własnym configiem i silnikiem Playwrighta od linii 6.0. Wzorzec w chmurze i przegląd w interfejsie webowym daje Percy, a Applitools dokłada do tego własną warstwę porównywania układu jako produkt Eyes. Osobno stoi ścieżka, którą sam kiedyś wybrałem: wtyczka do Cypressa z 2020 roku, czyli cypress-image-snapshot w 4.0.1, sprzed zmiany modelu wtyczek w dziesiątce.
Zmiana roku jest w jednym zdaniu: porównywanie obrazków przestało u mnie wymagać osobnego narzędzia, bo weszło do rdzenia runnera, którym i tak zacząłem pisać nowe testy. Wszystko, co trudne, zostało tam, gdzie było, i to nie jest kwestia biblioteki: progi tolerancji, różnice w renderowaniu między moim laptopem a agentem CI, oraz nazwisko osoby, która zatwierdza nowy wzorzec. Dlatego zgodnie z wrześniowym podziałem screenshoty siedzą u mnie w nocnym koszyku, a nie na bramce.
Wpis z 15 listopada wyszedł dwa tygodnie przed najgłośniejszym ogłoszeniem tej branży w tym roku i nie ma w nim o nim ani słowa, bo wtedy tego produktu jeszcze nie było. Zostawiam to w archiwum tak, jak jest.
Nagłówki końca roku
Trzy rzeczy z ostatnich sześciu tygodni istnieją i będą w tekstach o 2022, więc odnotowuję je jako nagłówki, a nie jako narzędzia, którymi pracowałem.
30 listopada OpenAI ogłosiło ChatGPT. To jest piętnaście dni temu. Mam tam konto, kliknąłem w to jak wszyscy i na tym poprzestaję w tym wpisie, bo recap roku ma opisywać rok. Żaden mój test z 2022 nie powstał w rozmowie z modelem, żaden fixture w moim repozytorium nie pochodzi z czatu i żaden krok w moim pipeline’ie nie woła żadnego modelu językowego. Traktowanie tego ogłoszenia jako motywu całego roku byłoby cofnięciem daty o jedenaście miesięcy. Kiedy będę miał coś do powiedzenia poza pierwszym wrażeniem, napiszę osobny wpis i będzie to wpis z przyszłego roku, z uczciwie zmierzonym wynikiem, a nie akapit dopisany do grudniowego podsumowania.
7 grudnia pojawił się GitHub Copilot for Business. To ta sama linia produktowa, którą opisywałem w czerwcu, tylko z planem dla organizacji: w dniu GA można było kupić wyłącznie subskrypcje indywidualne, a wersja dla firm była zapowiedzią na “później w tym roku”. Teraz jest pozycją w cenniku. Dla mnie zmienia to jedno pytanie, i to nie techniczne: przy planie organizacyjnym rozmowa o tym, w których repozytoriach wolno to włączyć, przestaje być moją prywatną decyzją i staje się decyzją właściciela kodu. Nawyk czytania diffu przed commitem nie zmienia się przy tym ani o milimetr.
8 listopada wyszedł .NET 7. Cały mój tegoroczny pack wpisów stoi na .NET 6 i tak zamykam rok, bo grudzień jest złym miesiącem na wymianę fundamentu pod zestawem regresji. Odnotowuję datę, żeby za rok było widać, że wersja była dostępna i że jej nie wziąłem świadomie.
Co świadomie zostawiam
Cztery rzeczy leżały w zasięgu ręki i nie weszły do mojego zestawu. Wypisuję je, bo za rok nie będę pamiętał, że to były decyzje, a nie przeoczenia.
Testy komponentów w Playwrighcie są od maja, razem z asercją na screenshocie, i cały czas są oznaczone jako eksperymentalne. Nie wpinam eksperymentu do pliku, od którego zależy merge do main, i nie buduję na nim konwencji projektu.
Wersje bibliotek zostają tam, gdzie je przypiąłem. Testcontainers w linii 2.x, NUnit w 3.13, xUnit w 2.4, Playwright na 1.27.1 z października, choć 1.28.0 jest od 16 listopada. Podnoszenie pinów w połowie grudnia to najgorszy możliwy moment na taką pracę, a listy zmian mam przeczytane i zapisane na styczeń.
Generowanie danych testowych przez interfejs rozmowy. Mam za sobą eksperyment z Completions z 2021 roku i jego wniosek nadal obowiązuje: dwa wywołania z tym samym wejściem nie muszą dać tego samego wyniku, więc to jest narzędzie na etapie wymyślania danych, a nie źródło prawdy w przebiegu CI. Piętnaście dni z nowym interfejsem tego wniosku nie unieważnia i nie zamierzam na tej podstawie dopisywać warstwy do strategii zarządzania danymi testowymi.
Przepisywanie tego, co działa. Testy Cypressa zostają, refaktor page objectów w C# zostaje regułą dla kodu Selenium, wpisy z 2021 zostają bez korekt na nowe nazwy komend. Zestaw zielony i utrzymywany nie jest długiem technicznym tylko dlatego, że powstał w innym narzędziu albo w innym roku.
Jedno zdanie na 2023
Jedno zdanie: w przyszłym roku chcę doprowadzić migrację UI do stanu, w którym jedna suite pokrywa to, co dziś pokrywają dwie, i nadal mierzyć te same dwie liczby co w tym roku.
Dwie liczby to wall-clock samych wymaganych checków oraz odsetek nocnych przebiegów, w których coś padło bez zmiany w kodzie. Pierwsza mówi, czy ktoś jeszcze czeka na wynik. Druga mówi, czy poranne patrzenie na raport ma sens. Poprawianie pierwszej kosztem drugiej jest łatwe i zawsze kończy się tak samo.
Liczby, której nie będę mierzył, też chcę nazwać, bo to jest pokusa tego konkretnego roku. Nie interesuje mnie, ile modeli i wtyczek mam włączonych w edytorze. Tempo pisania kodu urosło mi w 2022 realnie i nie jest to to samo co poprawa jakości; miejsce, w którym decyduje się, czy zestaw testów cokolwiek znaczy, nadal leży w procesie, a nie w edytorze. Rok temu zamykałem podsumowanie 2021 zdaniem o narzędziach kontra proces i po dwunastu miesiącach z szybszym narzędziem widzę to samo, tylko ostrzej.
Do zobaczenia w styczniu.

