Odczytanie rzuconego wyjątku z CMD za pomocą ProcessBuilder.

0

Cześć! Testuję aplikację konsolową, która pobiera wzór String jako pierwszy parametr oraz kolejny ciąg znaków String jako drugi parametr i sprawdza czy wzór znajduje się w ciągu oraz po którym znaku z ciągu występuje.
Testuję z użyciem JUnit, Cucumber i Gherkin.

Chcę przetestować, że aplikacja rzuci ArrayIndexOutOfBoundsException w momencie gdy pierwszy, drugi lub obydwa parametry są puste. Jak dotąd najbardziej sensowną metodą na to wydaje mi się, otworzenie CMD za pomocą ProcessBuilder, tam podanie ścieżki do aplikacji oraz podanie parametrów. Ta część działa, faktycznie otwiera mi się okno konsoli i widzę, że wyjątek jest rzucony. Jednak chcę to potwierdzić za pomocą asercji, a żeby to zrobić potrzebuję odczytać wyjątek z konsoli z pomocy metody testowej w moim step definition. No i tutaj rodzi się problem, czy coś takiego jest w ogóle możliwe, a jeśli tak to w jaki sposób mogę to zrobić? Wydaje mi się, że powinienem kombinować z getInputStream() / getOutputStream() z klasy Process niestety zupełnie nie mam pomysłu jak to zrobić. Poniżej moje metody @Given i @When ze step definition. Sama asercja wyjątku planowana jest w metodzie @Then.

@Given("Parameters list is empty.")
    public void parametersListIsEmpty() {
        args = new String[2];
        args[0] = " ";
        args[1] = " ";

        stringBuilder = new StringBuilder();
        stringBuilder.append("\" java PatternSearch.java ");
        stringBuilder.append(args[0]);
        stringBuilder.append(" ");
        stringBuilder.append(args[1]);
        stringBuilder.append("\"");
    }

    @When("App is run with invalid parameters")
    public void appIsRunWithInvalidParameters() {
        String filePath = FileSystems.getDefault().getPath("./"
                + "\\src\\main\\java\\com\\sample\\textsearch\\").toAbsolutePath().toString();
        String command = stringBuilder.toString();
        String[] processCommand = {"cmd.exe", "/c", "start", "cmd.exe", "/k", command};

        System.out.println(filePath);

        File log = null;

        try {
            ProcessBuilder proBuilder = new ProcessBuilder(processCommand);
            proBuilder.directory(new File(filePath));
            proBuilder.inheritIO();

            log = new File("CMDOutput.txt");

            proBuilder.redirectErrorStream(true);
            proBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(log));

            Process process = proBuilder.start();

            //one attempt to see CMD output
            String output = new String(process.getInputStream().readAllBytes());
            System.out.println(output);

            //another attempt to see CMD output
            System.out.println(processOutput(process));

        } catch (IOException e) {
            e.printStackTrace();
            excMessage = e.getMessage();
            System.out.println(excMessage);
        }
    }

    public String processOutput(Process process) {
         String output = null;

        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            output = reader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return output;
    }

ProcessBuilder i Process to na razie dla mnie czarna magia. Mój kod tworzy jeden proces cmd.exe i w nim dopiero drugi taki proces wraz ze ścieżką do aplikacji oraz komendą, domyślam się więc, że potrzebuję wyłuskać output z tego drugiego procesu.
Z góry ogromnie dziękuję za jakiekolwiek wskazówki.

0

Aplikacji się (ZTCW) nie testuje. W ogóle trudno mówić o testowaniu czegoś, co być może - nie znamy wnętrza "czarnej skrzynki" jest proceduralnym spagetti (mam na myśli, ze potrafię szanować DOBRE programowanie strukturalne)

testuje się np metody KLASY, która to klasa może stanowić 98% aplikacji, ale to jednak coś innego.
Wystarczy, że będziesz myślał kategoriami "lightweight main", czyli taki mający jedną, góra dwie linijki.

Podobnie, jak nie będziesz testował loadera JAR-a z twoim main'em itd - a od tego, jak od setki innych powodów, zależy przechodzenie wyjątku zabijającego do środowiska

0

@AnyKtokolwiek: Napisałem "aplikacja" ponieważ całość mieści się w jednej klasie. Jeśli dobrze rozumiem, sugerujesz w ogóle nie testować takiego przypadku? Chciałem po prostu przetestować możliwe najwięcej scenariuszy.

Biorąc pod uwagę, że testuję z użyciem Cucumber czyli BDD, postanowiłem testować tę aplikację end-to-end. W ogóle nie testuję pojedynczych metod ponieważ jak rozumiem nie na tym polega BDD i chodzi właśnie o przetestowania zachowania całej aplikacji/klasy. Z tego też względu wydawało mi się, że rzucenie wyjątku (czyli zakończenie działania aplikacji) jest jednym z zachowań, w przypadku gdy nie podamy danych wejściowych.

Nie ukrywam, że dopiero zaczynam się uczyć BDD i o ile składania jest banalna to mam trochę problem z określeniem co powinno zostać przetestowane, a co zostawić dla testów jednostkowych.

0

Proces który odpalasz to cmd.exe i on działa ok. Trzeba by odpalić bezpośrednio to co odpalasz, żeby móc sensownie z tym pracować. Niemniej powinno się dać odczytać stdout stderr z tego procesu i coś wyłuskać z outputu, jeśli ta twoja aplikacja w ogóle wypisuje jakieś komunikaty błędów.

Niemniej może prościej byłoby w teście zwyczajnie zawołać main() z tymi parametrami? ;)

0
Shalom napisał(a):

Niemniej może prościej byłoby w teście zwyczajnie zawołać main() z tymi parametrami? ;)

Dokładnie to chciałem powiedzieć ... prawie ... bo w moim marzeniu main() jest tak minimalny, że niemal zerowy

testowanie całego runtimu windowsa / pokoleń loaderów javy / antywirusów / ustawienia terminala / h wie czego jeszcze ... to mam mały sens. Coś wykaże na czerwono lub zielono, ale co naprawdę?

2
AnyKtokolwiek napisał(a):

Aplikacji się (ZTCW) nie testuje.

Testuje się. Nie raz stawiałem całą aplikację webową żeby uderzać w testach po prawdziwym http :)
W przypadku aplikacji konsolowych jest to na pewno rzadsze (ale aplikacje konsolowe rzadziej się pisze w Javie) i trudniejsze (interfejs CLI jest różny między systemami, w przeciwieństwie do http) oraz ma mniej sensu (aplicacje konsolowe są zwykle małe i łatwiej je testować po kawałku niż aplikacje webowe).
Kiedyś próbowałem testować tak swoją aplikację konsolową i mało z tego zysku było. Ostatecznie większość testów przepisałem na normalne testy gdzie wywoływałem metodę main, albo moją funkcję run zawierającą interesujący mnie kod biznesowy

nie znamy wnętrza "czarnej skrzynki"

Jakbyś znał wnętrze czarnej skrzynki to nie była by już czarną skrzynką tylko białą skrzynką. Na tym polegają testy czarnoskrzynkowe że nie znasz wnętrza a interesuje cię tylko API :)

0

@Shalom: @AnyKtokolwiek: Moje wcześniejsze metody testowe opierają się na wyniku wywołania metody main() jednak to nie zadziała w momencie testowania pustej listy argumentów ponieważ wtedy rzucony zostaje NullPointerException, a to nie to samo co zostaje rzucone gdy odpalę aplikację z konsoli bez podania argumentów. Jeśli w konsoli nie podam argumentów to rzucony jest ArrayIndexOutOfBoundException, stąd pomysł na zrobienie tego przy pomocy ProcessBuildera ale jak rozumiem z powyższych wypowiedzi nie ma to większego sensu.

0

@AnyKtokolwiek: niestety nie znam pojęcia "kod rozbiegowy".
Wiem, że pusty String linii komendy != null ale to nadal nie rozwiązuje problemu ponieważ, jeśli przygotuję parametry tak aby stanowiły je puste Stringi to kod działa bez zarzutu. Po prostu najzwyczajniej kod oblicza, że pierwszy parametr występuje po "pierwszym znaku" w tekście, który jest drugim parametrem (pustym Stringiem). No i to się zgadza z punktu widzenia kodu, niestety nijak nie testuje to kodu ponieważ, raz jeszcze, aplikacja rzuca ArrayIndexOutOfBoundException gdy jest odpalona z pustą listą.
Nie mogę też zadeklarować, że lista jest nullem, to skutkuje NullPointerem.
A w takim wypadku nie mam pojęcia jak inaczej miałbym zadeklarować parametry tak, aby test wykazał rzeczywisty rezultat wywołania kodu.

1

@novice: Trochę poza tematem przewodnim... Chcę przetestować, że aplikacja rzuci ArrayIndexOutOfBoundsException w momencie gdy pierwszy, drugi lub obydwa parametry są puste. - serio masz taką aplikację, która rzuca tym wyjątkiem jak nie podasz właściwych parametrów? Z perspektywy szarego użytkownika jest to nieczytelna informacja. Przeważnie aplikacje produkują coś w stylu: usage: --help, -h for help, jeśli parametry są wymagane i użytkownik nie poda wymaganych.

Jeśli masz możliwość, to wysiłek włóż w poprawienie aplikacji, a nie w obejścia na potrzeby testów. Może warto rozdzielić "parsowanie" parametrów uruchomieniowych od biznesowego użycia tychże?

0

@yarel: Nie ja jestem twórcą tej aplikacji, a sam testuję ją w ramach ćwiczeń. Gdyby to była moja apka to inaczej rozwiązałbym kwestię tego wyjątku. Poza tym tak na prawdę nie chodzi mi o przetestowanie tego konkretnego wyjątku, a jedynie poprawne przetestowanie, że aplikacja się wysypuje jeśli jeden lub obydwa argumenty są puste. Stąd wydaje mi się, że przetestowanie tego wyjątku byłoby najbardziej sensowne, bo nie widzę innej możliwości.

0

Właśnie @yarel w pełni sie zgadzam, coś mi nie gra pod względem zaangażowania energii.

Nie rozumiem @novice dlaczego


String[] args = ...

AnEntryClass.main(args)

nie miałoby zaskutkować

0

@yarel: @Shalom: @AnyKtokolwiek: Dobra, nie jestem zbyt bystry... Z uporem maniaka chciałem "coś" wepchnąć do tej listy parametrów. Albo puste Stringi albo nulle, stąd te niechciane wyniki. A wystarczyło zadeklarować tablicę parametrów bez tworzenia instancji lub stworzyć instancję tablicy o długości 0...
Dziękuję w każdym razie za wszystkie wskazówki, aż mi głupio z powodu tej ignorancji :/

1 użytkowników online, w tym zalogowanych: 0, gości: 1