Przedsiębiorstwa przechodzące transformację cyfrową często działają w złożonych środowiskach "brownfield", gdzie krytyczne operacje bankowe są nadal przetwarzane przez dekady starzejące się systemy COBOL na platformach IBM z/OS. Jednocześnie, procesy związane z obsługą klienta, takie jak onboarding oraz usługi, są coraz bardziej obsługiwane przez nowoczesne portale internetowe oparte na React i aplikacje mobilne. Ta technologiczna różnica stwarza znaczące wyzwanie walidacyjne dla zespołów QA, które muszą zapewnić płynny, bezbłędny przepływ danych i spójność transakcyjną w tych zasadniczo odmiennych architekturach.
Tradycyjnie, wysiłki automatyzacyjne w takich środowiskach stają się mocno podzielone, z wyspecjalizowanymi zespołami utrzymującymi oddzielne zestawy narzędzi do emulacji terminali mainframe (takich jak Jagacy lub Extra!), ogólnej automatyzacji UI (Selenium lub Cypress) oraz walidacji API (Rest-Assured lub Postman). Ta fragmentacja prowadzi do delikatnych zestawów integracyjnych napisanych w wysoce technicznym żargonie, które analitycy biznesowi nie mogą przeglądać ani weryfikować w odniesieniu do wymagań. Ponadto, katastrofalne problemy z integralnością danych często pojawiają się, gdy test kończy się niepowodzeniem w trakcie jego wykonania, mogąc pozostawić konto mainframe stworzone, podczas gdy weryfikacja portalu sieciowego pozostaje niekompletna, tym samym zanieczyszczając środowiska pochodne danymi testowymi.
To konkretne pytanie powstało w firmie świadczącej usługi finansowe w rankingu Fortune 500, zmagającej się z walidacją złożonego przepływu pracy "onboardingu nowego klienta", który obejmował aplikację mobilną React, bus zdarzeń Kafka, warstwę mikroserwisów Java oraz ostateczna zasoby konta na systemie mainframe IBM z/OS. Organizacja wymagała zjednoczonej strategii automatyzacji, która mogłaby zbliżyć te techniczne podziały, jednocześnie zachowując elastyczność oczekiwaną w nowoczesnych potokach DevOps. Wyzwaniem było dodatkowo to, że analitycy biznesowi musieli pisać i rozumieć scenariusze testowe bez znajomości podstawowych technicznych implementacji każdego systemu.
Główne wyzwanie leży w zasadniczej niezgodności między synchronizowaną automatyzacją webową, która oczekuje natychmiastowych aktualizacji DOM oraz interakcji opartych na zdarzeniach, a emulacją terminali w trybie blokowym mainframe 3270, która opiera się na jednoznacznym skanowaniu ekranu i precyzyjnym położeniu kursora. API REST wprowadza dodatkową złożoność, operując w stateless paradigmacie żądania-odpowiedzi, który nie ma kontynuacji sesji inherentnej dla sesji terminali. Przełamanie tych architektonicznych stylów wymaga warstwy abstrakcji zdolnej do tłumaczenia wysokopoziomowych działań biznesowych na polecenia specyficzne dla systemu, nie ujawniając szczegółów technicznych implementacji w scenariuszach testowych.
Utrzymanie spójnego Języka Specyficznego dla Danej Dziedziny (DSL) przy użyciu narzędzi takich jak Gherkin staje się niezwykle trudne, gdy techniczne implementacje kroków testowych tak bardzo różnią się między systemami. Elementy webowe zwykle identyfikowane są za pomocą selektorów CSS lub wyrażeń XPath, walidacje API opierają się na asercjach ścieżek JSON i walidacji schematów, podczas gdy interakcje z mainframe zależą od współrzędnych pól, etykiet ekranowych lub specyficznych sekwencji klawiszy, takich jak F1 lub Enter. Bez solidnej strategii abstrakcji, DSL szybko staje się zagracony technicznymi lokalizatorami i żargonem specyficznym dla systemu, co czyni jego funkcję jako medium komunikacyjne między interesariuszami biznesowymi a technicznymi nieskuteczną.
Ponadto, zapewnienie prawdziwej integralności transakcyjnej w tych rozproszonych systemach wymaga implementacji wzorca Saga lub Transakcji Kompensacyjnej bezpośrednio w architekturze frameworka testowego, co jest niebanalne, gdy warstwa testowa nie ma natywnych punktów zaczepienia w protokołach dwuetapowej instalacji mainframe lub menedżerach transakcji rozproszonych. Gdy test kończy się niepowodzeniem w portalu sieciowym po zatwierdzeniu transakcji mainframe, framework musi mieć inteligencję i zdolności do wyzwolenia explicite procedur cofania, aby przywrócić spójność środowiska. To wymaga zaawansowanego śledzenia stanu i mechanizmów obsługi błędów, które wykraczają daleko poza standardowe blokady try-catch.
Na koniec, framework automatyzacji musi bezpiecznie obsługiwać różne mechanizmy uwierzytelniania, nie wplatając wrażliwych poświadczeń bezpośrednio w skrypty testowe. Portale internetowe często wykorzystują nowoczesne przepływy OAuth2 lub SAML z wieloskładnikowym uwierzytelnianiem (MFA), API REST opierają się na kluczach API lub tokenach JWT, podczas gdy stare systemy mainframe uwierzytelniają się wyłącznie za pomocą dostawców RACF lub ACF2 korzystających ze statycznych profili użytkowników. Centralny, zaszyfrowany skarbczyk z poświadczeniami oraz możliwościami wstrzykiwania specyficznymi dla środowiska są niezbędne do utrzymania postury bezpieczeństwa, jednocześnie umożliwiając płynne uwierzytelnianie między systemami.
Aby zająć się tymi złożonościami, framework powinien być zaprojektowany z użyciem wzorca Architektura Sześciokątna (Ports and Adapters), który wymusza ścisłe oddzielenie między logiką domeny testu a interakcjami z systemami zewnętrznymi. Zdefiniuj abstrakcyjny interfejs portu ApplicationDriver, deklarując wysokopoziomowe metody domenowe, takie jak enterCustomerData(), verifyAccountCreation(), i rollbackTransaction(). Ten interfejs działa jako jedyny kontrakt, z którym Twoja warstwa DSL (taka jak definicje kroków Cucumbera lub powiązania SpecFlow) ma prawo współdziałać, zapewniając pełną izolację od szczegółów implementacji.
Konkretne implementacje adapterów obsługują techniczne szczegóły specyficzne dla systemu: SeleniumWebAdapter tłumaczy metody portów na interakcje z przeglądarką, RestAssuredAdapter wykonuje wywołania HTTP i analizuje odpowiedzi JSON, a HllapiMainframeAdapter wykorzystuje High-Level Language API do wysyłania klawiszy, odczytywania buforów ekranowych i walidacji treści pól na emulatorze 3270. Każdy adapter kapsułkuje swoją własną logikę ponownego próby, mechanizmy oczekiwania i strategie obsługi błędów stosowne do swojej technologii. Gdy adapter pomyślnie zakończy działanie, które modyfikuje stan, publikuje zdarzenie domenowe (takie jak AccountCreatedEvent) do centralnego TestEventBus zamiast zwracać prymitywne typy danych.
Dla integralności transakcyjnej, zaimplementuj Test Saga Orchestrator, który utrzymuje uporządkowany dziennik wszystkich wykonanych obiektów CompensableAction podczas scenariusza testowego. Jeśli jakikolwiek krok w przepływie pracy kończy się niepowodzeniem z wyjątkiem, orchestrator automatycznie wykonuje metodę compensate() poprzednich, udanych działań w odwrotnej kolejności, efektywnie prowadząc transakcję kompensacyjną w celu usunięcia konta mainframe lub unieważnienia rezerwacji API. Ten wzorzec zapewnia, że środowisko testowe pozostaje nienaruszone, nawet gdy testy zawodzą w trakcie ich wykonywania, zapobiegając gromadzeniu się osieroconych danych, które dręczą tradycyjne zestawy end-to-end.
Zarządzanie stanem w heterogenicznym stosie osiągane jest poprzez traktowanie TestContext jako obywatela pierwszej klasy, używając ThreadLocal<DomainContext> do przechowywania bogatych obiektów domenowych, a nie prymitywnych ciągów, tym samym zapobiegając silnemu sprzężeniu między krokami testowymi. Adapter React może wypełnić obiekt CustomerProfile w kontekście, który adapter mainframe następnie pobiera, aby wykonać swoją część przepływu pracy. To podejście zapewnia, że DSL pozostaje skoncentrowany na podmiotach biznesowych, a nie na technicznych identyfikatorach, takich jak identyfikatory sesji czy współrzędne ekranowe.
Aby połączyć te komponenty, wykorzystaj lekką magistralę komunikacyjną, taką jak Google Guava EventBus lub strumień reaktywny, aby umożliwić adapterom komunikację o zmianach stanu bez bezpośredniego wywoływania metod, tym samym decouplując przepływ mainframe od przepływu weryfikacji webowej. Gdy HllapiMainframeAdapter pomyślnie utworzy konto, publikuje zdarzenie zawierające szczegóły konta, które następnie jest konsumowane przez SeleniumWebAdapter, aby automatycznie nawigować do odpowiedniego ekranu weryfikacji. To podejście oparte na zdarzeniach w ramach testu odzwierciedla nowoczesną architekturę mikroserwisów i znacząco zmniejsza obciążenie konserwacyjne, gdy interfejsy poszczególnych systemów się zmieniają.
// Definicja interfejsu portu public interface BankingDriver { void enterCustomerData(Customer customer); AccountDetails submitAccountCreation(); void verifyAccountInPortal(AccountDetails account); void rollbackAccountCreation(AccountDetails account); } // Adapter mainframe używający HLLAPI public class MainframeAdapter implements BankingDriver { private final HllapiWrapper hllapi; private final EventBus eventBus; @Override public AccountDetails submitAccountCreation() { hllapi.sendKey("@E"); // Symulacja klawisza Enter waitForScreen("Konto Utworzone"); String accountId = hllapi.getTextByLabel("Numer Konta:"); AccountDetails details = new AccountDetails(accountId); eventBus.post(new AccountCreatedEvent(details)); return details; } @Override public void rollbackAccountCreation(AccountDetails account) { hllapi.sendKeys("USUŃ " + account.getId()); hllapi.sendKey("@E"); verifyScreen("Usunięcie Potwierdzone"); } } // Orkiestrator Saga dla Integralności Transakcyjnej public class TestSagaOrchestrator { private final List<CompensableAction> executedActions = new ArrayList<>(); public void execute(Runnable action, Runnable compensation) { try { action.run(); executedActions.add(new CompensableAction(action, compensation)); } catch (Exception e) { compensate(); throw new TestFailureException(e); } } private void compensate() { Collections.reverse(executedActions); for (CompensableAction action : executedActions) { try { action.compensate(); } catch (Exception ex) { publishToDeadLetterQueue(action, ex); } } } }
Podczas zaangażowania w 2022 roku z globalnym dostawcą usług ubezpieczeniowych przechodzącym transformację cyfrową, napotkałem krytyczny proces biznesowy "Pierwsze Zgłoszenie Szkody" (FNOL), który odzwierciedlał dokładnie te wyzwania. Przepływ pracy wymagał od posiadacza polisy zgłoszenia roszczenia za pośrednictwem aplikacji mobilnej React Native, przesyłając zdjęcia wypadku, co wyzwalało mikroserwis oparty na Pythonie do oceny szkód i wykrywania oszustw, zanim ostatecznie zaktualizowano system mainframe Unisys, aby przydzielić rezerwy finansowe i zweryfikować pokrycie polisy. Istniejąca strategia automatyzacji polegała na trzech różnych, niekomunikujących się zestawach: Cypress dla aplikacji mobilnej, Pytest dla API i Jagacy dla emulacji terminala mainframe.
Podejście silosowe wymagało ręcznej korelacji numerów roszczeń między zespołami przy użyciu wspólnych arkuszy Excel, a zanieczyszczenie środowiska stało się poważną przeszkodą podczas cykli regresyjnych. Krytyczny moment nastąpił, gdy timeout sieci komórkowej spowodował, że test zakończył się niepowodzeniem po tym, jak mainframe już zatwierdził alokację rezerwy w wysokości 50 000 USD, pozostawiając dane finansowe w niespójnym stanie, który wymagał czterech godzin ręcznego czyszczenia przez programistę systemów mainframe. Incydent ten naruszył bezpośrednio politykę "czystego środowiska" zespołu i zablokował potok CI/CD przez cały dzień roboczy.
Oceniliśmy trzy potencjalne strategie naprawcze, aby zapobiec przyszłym wystąpieniom. Pierwsza opcja polegała na pisaniu skryptów do czyszczenia baz danych po testach w celu ręcznego wycofania transakcji mainframe, ale została ona odrzucona, ponieważ polityki bezpieczeństwa zabraniały bezpośredniego dostępu SQL do produkcyjnego środowiska UAT. Druga podejście proponowało wdrożenie wspólnej puli danych testowych z mechanizmami blokady pesymistycznej, aby zserializować wykonanie testów, ale to zwiększyłoby czas wykonania zestawu z dwudziestu minut do ponad czterech godzin, co całkowicie negowałoby korzyści z równoległości w CI/CD. Trzecia strategia, którą ostatecznie wybraliśmy, polegała na wdrożeniu wzorca Saga w frameworku automatyzacji testów samym, odzwierciedlając model ostatecznej spójności aplikacji, zachowując jednocześnie możliwość uruchamiania setek testów w równoległych.
Wdrożone rozwiązanie wprowadziło orkiestrator ClaimSaga, który przechwytywał każdą akcję wykonaną przez adaptery mobilne i mainframe. Gdy adapter mobilny zgłaszał StaleElementReferenceException z powodu timeoutu sieci, saga natychmiast uruchomiła transakcję kompensacyjną reverseReserveAllocation() na adapterze mainframe, korzystając z identyfikatora roszczenia przechowywanego w kontekście ThreadLocal. Ten automatyczny mechanizm wycofania zmniejszył zanieczyszczenie danych środowiskowych o dziewięćdziesiąt osiem procent i pozwolił zespołowi pewnie uruchomić pięćset równoległych wątków w swoim potoku Jenkins, nie obawiając się tworzenia osieroconych rekordów finansowych.
Ta dramatyczna poprawa niezawodności testu pozwoliła zespołowi QA skupić się na testach eksploracyjnych i analizie przypadków brzegowych. Analitycy biznesowi mogli w końcu pisać i przeglądać scenariusze testowe napisane w prostym języku, takie jak Biorąc pod uwagę, że posiadacz polisy zgłasza poważny wypadek, gdy zdjęcia są ładowane, ale usługa oceny AI czasami się werbalizuje, to żadna rezerwa finansowa nie zostanie przydzielona. To zapewniło, że zestaw automatyzacji działał jako dokładna dokumentacja żyjąca odzwierciedlająca złożone zasady biznesowe we wszystkich trzech warstwach technologicznych.
Jak radzisz sobie z trwałością stanu sesji między emulatorem a portalem internetowym bez tworzenia mocnego sprężenia między adapterami?
Nowicjusze często próbują rozwiązać ten problem, zwracając surowe identyfikatory sesji lub klucze podstawowe bazy danych bezpośrednio z metod definicji kroków, tworząc delikatne zależności, w których Krok B nie może zostać wykonany, dopóki Krok A wyraźnie nie zwrócił określonej wartości ciągu. To podejście zasadniczo łamie zasady projektowania sterowanego domeną i zmusza kroków Gherkina, aby były ułożone w ściśle technicznej kolejności, a nie w logicznym przepływie biznesowym. Ponadto, ujawnia szczegóły implementacji w warstwie DSL, co sprawia, że testy stają się delikatne, gdy zmieniają się formaty identyfikatorów technicznych.
Solidne rozwiązanie architektoniczne implementuje Kontext Scenariusza lub Kontekst Danych Testowych, który działa jako przejrzysty rejestr przez czas wykonania testu, zwykle realizowany przy użyciu ThreadLocal<Map<Class<?>, Object>>, aby zapewnić bezpieczeństwo wątków podczas wykonania równoległego. Adaptery nie zwracają wartości prymitywnych do warstwy DSL; zamiast tego publikują silnie typowe zdarzenia domenowe lub obiekty w tym kontekście. Na przykład, gdy adapter mainframe pomyślnie utworzy konto, publikuje zdarzenie AccountCreatedEvent zawierające pełną jednostkę konta, które adapter webowy następnie pobiera, słuchając magistrali zdarzeń lub konsultując kontekst.
To podejście oparte na zdarzeniach zapewnia, że warstwa DSL pozostaje całkowicie obojętna wobec pochodzenia danych, niezależnie od tego, czy numer polisy został pobrany z ekranu zielonego, czy zgłoszony w odpowiedzi JSON. Opierając się na abstrahujących wartościach, a nie konkretnych implementacjach, framework przestrzega Zasady Inwersji Zależy, co pozwala na refaktoryzowanie lub zastępowanie poszczególnych adapterów bez wpływu na czytelne scenariusze testowe, znacznie zmniejszając długoterminowe koszty konserwacji.
Jaki konkretny mechanizm zapobiega awarii transakcji kompensacyjnej, co potencjalnie mogłoby pozostawić system w niespójnym stanie?
Wielu inżynierów juniorów pomija krytyczne tryby awarii, w których logika kompensacji sama napotyka na błąd. Takie błędy mogą obejmować timeouty sieciowe podczas próby usunięcia rekordu z mainframe lub błędy walidacyjne, ponieważ rekord został już zmodyfikowany przez równoległy proces w tle. Scenariusz ten prowadzi do gromadzenia "toksycznych danych", gdzie oryginalna akcja się powiodła, ale wycofanie się nie udało, pozostawiając środowisko testowe w stanie trwałego uszkodzenia.
Rozwiązanie wymaga implementacji idempotentnych akcji kompensacyjnych, które są zaprojektowane tak, aby można je było bezpiecznie powtarzać wielokrotnie bez powodowania błędów usunięcia. Powinny być one połączone z solidnym mechanizmem ponownej próby z wykorzystaniem opóźnienia wykładniczego i wzorców wyłącznika obwodowego, aby elegancko obsłużyć przejściowe awarie infrastruktury. Jeśli wszystkie próby zakończą się niepowodzeniem, framework musi opublikować szczegóły nieudanej kompensacji w trwałej Kolejce "Dead-Letter Queue" (DLQ). Ta DLQ może być implementowana jako tabela bazy danych lub temat wiadomości zawierający pełne identyfikatory korelacyjne i stos wywołań.
Dodatkowo, implementuj bramy walidacyjne przed próbą kompensacji, aby zweryfikować bieżący stan systemu podrzędnego. Na przykład, potwierdź, że konto mainframe istnieje, ma zerowe salda i nie ma ostatnich modyfikacji użytkowników przed wydaniem polecenia usunięcia. Automatyczne zadanie rekonsyliacyjne, które działa co noc, może wtedy przetworzyć DLQ, aby ręcznie obsłużyć te osierocone rekordy, co zapewnia, że środowisko testowe samonaprawia się i zapobiega krytycznym regresjom maskowanym przez istniejącą zanieczyszczenie danych.
Dlaczego korzystanie z opartego na współrzędnych skanowania ekranu (HLLAPI) w systemach mainframe uważa się za obciążenie, i jak można to uprościć, aby zredukować koszty utrzymania, gdy układy ekranów nieuchronnie się zmieniają?
Kandydaci często opowiadają się za bezpośrednimi współrzędnymi wierszy i kolumn, takimi jak getText(10, 45, 10), aby odczytać dziesięć znaków zaczynając od wiersza dziesiątego, kolumny czterdziestej piątej. Preferują to podejście, ponieważ wydaje się precyzyjne i deterministyczne podczas początkowego rozwoju testu. Jednak ta strategia prowadzi do poważnego obciążenia związanym z utrzymaniem, ponieważ aplikacje mainframe często przechodzą modyfikacje ekranu, w których nowe pola są wstawiane, co powoduje, że wszystkie późniejsze przesunięcia współrzędnych zmieniają się, unieważniając całe zestawy testowe bez ostrzeżenia.
Solidne rozwiązanie architektoniczne implementuje Model Obiektów Ekranowych, który mapuje logiczne nazwy pól (takie jak FIELD_NR_KONTA) na dynamiczne kryteria wyszukiwania zamiast statycznych współrzędnych. Wykorzystuje on możliwości Identyfikacji Pola emulatora mainframe, dostępne za pośrednictwem funkcji HLLAPI, takich jak FindFieldPosition lub SearchField, aby lokalizować pola według ich powiązanych etykiet (na przykład, wyszukując tekst "Numer Konta:"). W momencie wykonania adapter przeszukuje bufor ekranu pod kątem tekstu etykiety i oblicza względne przesunięcie do odpowiedniego pola wejściowego. Kiedy układ ekranu się zmienia, wymaga aktualizacji tylko pliku konfiguracyjnego JSON, który mapuje etykiety na przesunięcia, pozostawiając skompilowany kod Java nietknięty.
Dla jeszcze większej odporności wprowadź mechanizm Sumy Kontrolnej Ekranu lub Hashu, który przechwytuje kryptograficzny hash niechronionych treści pola na początku interakcji. Jeśli hash nie pasuje do oczekiwanej wartości bazowej, framework kończy działanie z jasnym błędem "Niezgodność Ekranu", zamiast próbować odczytać dane z niepoprawnych pozycji. To zapobiega wykonaniu testów z nieprawidłowymi danymi, które mogłyby generować fałszywe negatywy lub fałszywe pozytywy, a automatycznie alarmuje zespół automatyzacji o zmianach na ekranie wymagających aktualizacji konfiguracji.