{"id":5814,"date":"2019-01-23T22:16:15","date_gmt":"2019-01-23T21:16:15","guid":{"rendered":"https:\/\/blog.nevercodealone.de\/?p=5814"},"modified":"2019-01-23T22:27:23","modified_gmt":"2019-01-23T21:27:23","slug":"groovy-shell-scripting-testing-teil-3","status":"publish","type":"post","link":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/","title":{"rendered":"Groovy Shell Scripting Teil 3 &#8211; Testing"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Dies ist Teil 3 unserer Serie zu Groovy Shell Scripting. Im <a href=\"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-teil-1\/\">ersten Teil<\/a> haben wir Groovy mit Hilfe von SDKMAN installiert und ein erstes &#8222;Hello World&#8220; auf der Kommandozeile geschrieben. In <a href=\"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-pipes-und-fifos-teil-2\/\">Teil 2<\/a> haben wir mit Pipes und FIFOs Prozesse miteinander kommunizieren und unsere Groovy Skripte mit anderen Linux Tools zusammen arbeiten lassen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Szenario<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Im vorigen Teil von Groovy Shell Scripting haben wir einen Producer gebaut, der Zahlen in eine Named Pipe legt. Diese Zahlen werden dann von einem Consumer gelesen und wieder auf der Kommandozeile ausgegeben. Den Consumer wollen wir jetzt so erweitern, dass er die Zahlen nicht nur lesen, sondern auch inkrementieren kann. Diese einfache Simulation echter Gesch\u00e4ftslogik wollen wir testen &#8211; einmal in einem klassischen Unit Test und einmal in einem <a href=\"https:\/\/blog.thecodewhisperer.com\/permalink\/integrated-tests-are-a-scam\">integrierten Test<\/a>. Wir werden sehen, dass wir daf\u00fcr nicht einmal mehr Tools nachinstallieren m\u00fcssen, weil Groovy bereits viele Werkzeuge mitbringt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Source Code organisieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die meisten meiner Skripte sind recht klein und gehen nicht \u00fcber wenige Klassen hinaus. Ich starte sie direkt aus der Shell, statt sie in ein ausf\u00fchrbares JAR zu packen. Mein Repository f\u00fcr diese Serie sieht gerade so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\n\u251c\u2500\u2500 README.md\n\u2026\n\u2514\u2500\u2500 testing\n    \u251c\u2500\u2500 Deserializer.groovy\n    \u251c\u2500\u2500 DeserializerTest.groovy\n    \u251c\u2500\u2500 Inkrementer.groovy\n    \u251c\u2500\u2500 InkrementerTest.groovy\n    \u251c\u2500\u2500 PipeConsumerTest.groovy\n    \u251c\u2500\u2500 fifo\n    \u251c\u2500\u2500 pipe_consumer.groovy\n    \u2514\u2500\u2500 pipe_producer.groovy<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wie ihr seht, habe ich f\u00fcr diesen Post einfach einen Ordner <code>testing<\/code> angelegt, der sowohl Tests als auch Produktionsklassen nebeneinander enth\u00e4lt. Aus diesem Ordner heraus werde ich die Beispiele weiter unten aufrufen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Packages<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Angenommen, mein Repository s\u00e4he so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\n\u251c\u2500\u2500 hello\n\u2502   \u2514\u2500\u2500 Hello.groovy\n\u2514\u2500\u2500 hello.groovy<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">dann k\u00f6nnte ich mit diesem Layout vom Skript <code>hello.groovy<\/code> auf die Klasse <code>Hello<\/code> folgenderma\u00dfen zugreifen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env groovy\ndef hello = new hello.Hello()\nhello.doSomething()<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">oder alternativ auch mit einem Import:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env groovy\nimport hello.Hello\n\ndef hello = new Hello()\nhello.doSomething()<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Tests von Produktionsklassen trennen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn das Repository gr\u00f6\u00dfer wird, lohnt es sich irgendwann, Test- und Produktionscode zu trennen. Angenommen mein Repository hat folgenden Inhalt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\n\u251c\u2500\u2500 src\n\u2502   \u2514\u2500\u2500 bar\n\u2502       \u2514\u2500\u2500 Foo.groovy\n\u2514\u2500\u2500 test\n    \u2514\u2500\u2500 bar\n        \u2514\u2500\u2500 FooTest.groovy<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Mein Produktionscode liegt in Packages unter <code>src<\/code>. Jedes Package bekommt ein eigenes Verzeichnis. Unter-Packages werden zu Unterverzeichnissen. Mit den Tests verfahren wir genauso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Meine Testklasse sieht so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package bar\nimport org.junit.jupiter.api.*\n\nclass FooTest {\n    @Test\n    void \"the answer is\"() {\n        def foo = new Foo()        \n\n        def something = foo.doSomething()\n\n        assert something == 42\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Meine Produktionsklasse so:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package bar\n\nclass Foo {\n    int doSomething() {\n        42\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dann kann ich den Test <strong>im Root-Verzeichnis<\/strong> meines Repositorys so starten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u279c groovy -cp test:src test\/bar\/FooTest.groovy\nJUnit5 launcher: passed=1, failed=0, skipped=0, time=51ms<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Aufruf mit <code>-cp path1:path2<\/code> sagt Groovy, wo nach Klassen gesucht werden soll. Wenn ich teste, brauche ich sowohl den Verzeichnisbaum unter <code>test<\/code> als auch den unter <code>src<\/code>. Wenn ich nur Produktionscode starten will, nehme ich <code>test<\/code> einfach nicht in den Classpath auf. So ist dann sicher gestellt, dass sich kein Testcode unter meinen Produktionscode gemischt hat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Details wie genau der Classloader der JVM nach Klassen sucht, findet ihr in der Java Documentation <a href=\"https:\/\/docs.oracle.com\/javase\/8\/docs\/technotes\/tools\/findingclasses.html\">&#8222;How classes are found&#8220;<\/a> im Oracle Tech Network.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Wirklich komplexe Projekte<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sp\u00e4testens wenn ihr einen echten Buildprozess f\u00fcr eure Projekte zu braucht, w\u00fcrde ich anfangen, mir \u00fcber Tools wie <a href=\"http:\/\/maven.apache.org\/\">Maven<\/a> oder <a href=\"https:\/\/gradle.org\/\">Gradle<\/a> Gedanken zu machen. Damit baut man aber meistens Artefakte wie JAR oder WAR Dateien und wir verlassen die Gefilde des Groovy Shell Scripting. Es kann sich aber lohnen, wiederverwendbare Komponenten f\u00fcr Skripte zu schreiben, und diese sp\u00e4ter via Dependency Management dort einzubinden. Dazu kommen wir im n\u00e4chsten Teil.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Batteries included: JUnit + Power Assertions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Groovy bringt allerlei Hilfmittel bereits mit. Integriert sind inzwischen ganze drei Versionen von JUnit: 3, 4 und 5. Man muss keine zus\u00e4tzlichen Bibliotheken mehr einbinden, sondern kann sofort loslegen. Wir beschr\u00e4nken uns hier auf JUnit 4 und 5.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">JUnit 4<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Auf der Shell ist der einzige Unterschied zu regul\u00e4ren JUnit Tests, dass wir die aus Teil 1 von Groovy Shell Scripting bekannte Shebang an den Anfang der Datei setzen m\u00fcssen. Danach folgen die bekannten Imports und eine Testklasse mit den Annotations von JUnit 4:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env groovy\nimport org.junit.Test\n\nclass MyTest {\n\n    @Test\n    void \"very simple test\"() {\n        assert 2 + 2 == 5\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Groovy erlaubt Strings als Methodennamen. Dies machen wir uns bei Tests zunutze, um besonders sprechende Namen zu vergeben. Wenn wir den Test starten, bekommen wir folgende Fehlermeldung:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u279c .\/5_junit4.groovy\nJUnit 4 Runner, Tests: 1, Failures: 1, Time: 10\nTest Failure: myTest(MyTest)\nAssertion failed:\n\nassert 2 + 2 == 5\n         |   |\n         4   false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Diese sprechenden Fehlermeldungen sind ein weiteres Feature von Groovy, die <a href=\"http:\/\/groovy-lang.org\/testing.html#_power_assertions\">&#8222;Power Assertions&#8220;<\/a>. Wir sehen alle Teile der fehlgeschlagenen Assertion auf einen Blick: Die ganze Expression, ihre Teile und das Ergebnis ihrer Auswertungen. Auf der rechten Seite des Vergleichsoperators sehen wir den erwarteten Wert und direkt darunter, dass der Vergleich fehlgeschlagen ist. Bei komplexeren Evaluationen hilft uns das enorm bei der Analyse des Problems.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">JUnit 5<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Auch JUnit 5 funktioniert ohne weiteres Zutun:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env groovy\nimport org.junit.jupiter.api.Test\n\nclass MyTest {\n\n    @Test\n    void \"simple JUnit 5 Test\"() {\n        assert 1 + 1 == 3\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Und auch dieser Test schl\u00e4gt fehl:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u279c .\/5_junit5.groovy\nJUnit5 launcher: passed=0, failed=1, skipped=0, time=51ms\n\nFailures (1):\n  JUnit Jupiter:MyTest:simple JUnit 5 Test()\n    MethodSource [className = 'MyTest', methodName = 'simple JUnit 5 Test', methodParameterTypes = '']\n    => Assertion failed:\n\nassert 1 + 1 == 3\n         |   |\n         2   false<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Stubs und Mocks<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Groovy kann sowohl interpretiert als auch kompiliert ausgef\u00fchrt werden. Seine bekanntesten <a href=\"http:\/\/groovy-lang.org\/testing.html#_mockfor_and_stubfor\">Hilfmittel<\/a> f\u00fcr Stubbing (<code>StubFor<\/code>) und Mocking (<code>MockFor<\/code>) k\u00f6nnen aber nur im dynamischen Kontext verwendet werden. Skripte und Tests, die wir direkt mit <code>groovy SomethingTest<\/code> starten, werden vorkompiliert und k\u00f6nnen sich deswegen diese dynamischen Features der Sprache nicht zunutze machen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Stattdessen werden wir das Feature &#8222;Map Coercion&#8220; benutzen, das uns erlaubt Maps mit dem <a href=\"http:\/\/groovy-lang.org\/operators.html#_coercion_operator\">Coercion Operator<\/a> <code>as<\/code> zu Implementierungen von Interfaces zu machen. Dies funktioniert auch im kompilierten Kontext.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Unit Tests<\/h2>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignleft is-resized\"><a href=\"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting.jpg\" data-rel=\"penci-gallery-image-content\" ><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting.jpg\" alt=\"Groovy Shell Scripting\" class=\"wp-image-5827\" width=\"356\" height=\"356\" srcset=\"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting.jpg 800w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-150x150.jpg 150w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-300x300.jpg 300w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-768x768.jpg 768w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-585x585.jpg 585w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-640x640.jpg 640w\" sizes=\"auto, (max-width: 356px) 100vw, 356px\" \/><\/a><figcaption>Groovy Shell Scripting<\/figcaption><\/figure><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Unit Tests sind Tests von Entwicklern f\u00fcr Entwickler. Sie laufen isoliert von der Au\u00dfenwelt und wissen also nichts von Dateien, Netzwerk, Datenbanken oder &#8211; frei nach Robert Martin &#8211; anderen <a href=\"http:\/\/blog.cleancoder.com\/uncle-bob\/2012\/05\/15\/NODB.html\">Implementierungsdetails<\/a> aus der Au\u00dfenwelt unseres Systems. Hier wird also nur das einzelne Unit und auch nur ein spezieller Usecase getestet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In unserem Szenario wollen wir das Inkrementieren einer Zahl testen, die unser Skript aus der Named Pipe gelesen hat. Die reine Logik unseres Systems beschr\u00e4nkt sich also darauf, eine Zahl zu nehmen und die inkrementierte Zahl zur\u00fcck zu liefern. Woher die Zahl kommt in diesem Moment, ist ihr egal. Das entkoppelt das Holen der Zahl vom Inkrementieren. Wir achten dadurch auf das Software Pattern des Single Responsibility Principle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine weitere Komponente unseres Systems hat die Aufgabe, eine Nachricht (d.h. eine Zeile) aus der Named Pipe zu lesen und in eine Zahl umzuwandeln. Auch daf\u00fcr werden wir einen isolierten Test schreiben. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zum Schluss werden wir im letzten Abschnitt das ganze System in einem integrierten Test pr\u00fcfen, also die Funktionalit\u00e4t \u00fcber das Unit hinaus sicherstellen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Incrementer<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import org.junit.jupiter.api.*\n\nclass IncrementerTest {\n\n    private def sut\n\n    @BeforeEach\n    void \"setup\"() {\n        sut = new Incrementer()\n    }\n\n    @Test\n    void \"increment 1\"() {\n        assert sut.increment(1) == 2 \n    }\n\n    @Test\n    void \"increment 2\"() {\n        assert sut.increment(2) == 3 \n    }\n\n    @Test\n    void \"increment 3\"() {\n        assert sut.increment(3) == 4\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Auch TDD funktionert auf der Kommandozeile gut. Getrieben von unseren Tests sieht die Logik der Produktionsklasse zum Schluss so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Incrementer {\n    public int increment(int number) {\n        number + 1\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ihr habt vielleicht bemerkt, dass ich hier keine Shebang an die Klassen geschrieben habe, obwohl dies m\u00f6glich gewesen w\u00e4re. Diese schreibe ich normalerweise nur an den Einstiegspunkt unseres Systems, um diesen als solchen zu markieren. Den Test starte ich auf die traditionelle Weise mit<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u279c groovy InkrementerTest.groovy\nJUnit5 launcher: passed=3, failed=0, skipped=0, time=13ms<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Deserializer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Der Deserialisierer soll eine Zeile aus der Pipe lesen, die Zeile in eine Zahl umwandeln und diese an den Inkrementierer weitergeben. Letzteres werden wir durch einen Interaktionstest gegen einen Mock verifizieren. Den Mock erzeugen wir durch die oben erw\u00e4hnte Map Coercion. Er nimmt einfach das \u00fcbergebene Argument an und speichert es ins Field <code>receivedDeserialized<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import org.junit.jupiter.api.*\n\nclass DeserializerTest {\n\n    private def receivedDeserialized\n\n    private def incrementerMock\n    private def sut\n\n    @BeforeEach\n    void \"setup\"() {\n        incrementerMock = [increment: { int number -> \n            receivedDeserialized = number }] as Incrementer\n\n        sut = new Deserializer(incrementerMock)\n    }\n\n    @Test\n    void \"deserialize 1 on one line\"() {\n        def line = \"1\\n\"\n\n        sut.deserialize(line)\n\n        assert receivedDeserialized == 1\n    }\n\n    @Test\n    void \"deserialize 2 on one line\"() {\n        def line = \"2\\n\"\n\n        sut.deserialize(line)\n\n        assert receivedDeserialized == 2\n    }\n\n    @Test\n    void \"deserialize 3 on one line\"() {\n        def line = \"3\\n\"\n\n        sut.deserialize(line)\n\n        assert receivedDeserialized == 3\n    }\n\n    @Test\n    void \"deserialize 1 on one line without newline\"() {\n        def line = \"1\"\n\n        sut.deserialize(line)\n\n        assert receivedDeserialized == 1\n    }\n\n    @Test\n    void \"deserialize non-numbers does not call increment\"() {\n        def garbage = \"lkajdfoiaer\"\n\n        sut.deserialize(garbage)\n\n        assert !receivedDeserialized\n    }\n\n    @Test\n    void \"deserialize NULL does not call increment\"() {\n        sut.deserialize(null)\n\n        assert !receivedDeserialized\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Deserialisierer sieht getrieben von unseren Tests so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Deserializer {\n\n    private def incrementer\n\n    Deserializer(Incrementer incrementer) {\n        this.incrementer = incrementer\n    }\n\n    Integer deserialize(String line) {\n        try {\n            def deserialized = Integer.parseInt(line?.trim())\n\n            return incrementer.increment(deserialized)\n        }\n        catch(NumberFormatException e) {\n            System.err.println(\"Received malformed message: '${line}'\")\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Fehler geben wir auf <code>System.err<\/code> aus. Damit k\u00f6nnen wir sie falls n\u00f6tig gesondert weiter verarbeiten. Sie landen also nicht in den Pipes, die wir mit dem <code>System.out<\/code> unseres Skripts verbinden. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Von Ende zu Ende<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Producer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Zum Schluss testen wir einmal die gesamte Strecke. Hier zur Erinnerung nochmal der Producer f\u00fcr die Zahlen. Diesmal schreibt er direkt nach <code>System.out<\/code> und produziert dank <code>BigInteger<\/code> und <code>Stream.iterate()<\/code> ohne \u00dcberlauf einen potenziell <a href=\"https:\/\/docs.oracle.com\/en\/java\/javase\/11\/docs\/api\/java.base\/java\/util\/stream\/Stream.html#iterate(T,java.util.function.UnaryOperator)\">unendlich langen Stream<\/a> von Zahlen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env groovy\nimport java.util.stream.*\n\ndef autoFlush = true\ndef pipe = new PrintWriter(System.out, autoFlush)\n\ndef numbers = Stream.iterate(0G) { i ->\n    i.add(1G)\n}\n\nnumbers.each {\n    pipe.println(\"${it}\")\n    sleep 1000\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Da der Producer unendlich viele Zahlen produziert, ist es nicht m\u00f6glich, ihn direkt zu testen. Au\u00dferdem verwenden wir nur wenige Methoden aus der Standardbibliothek. Wir verzichten daher auf einen Test und wenden uns der Logik des Consumers zu.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Consumer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Um den Consumer integriert zu testen, m\u00fcssen wir seine Eingaben kontrollieren und seine Ausgaben verifizieren k\u00f6nnen. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Groovy hat die API von <code>String<\/code> um die Methode <code>execute()<\/code> erweitert. Diese startet einen <code>Process<\/code>, einen Kindsprozess unseres Skriptes, indem es den Befehl im String ausf\u00fchrt und eine Instanz von <code>Process<\/code> liefert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auf dieser Instanz k\u00f6nnen wir <code>stdin<\/code> und <code>stdout<\/code> setzen. Um in die Eingabe bequem schreiben zu k\u00f6nnen, verwenden wir wieder einen <code>PrintStream<\/code>. Die Ausgabe lesen wir mit der Methode <code>readLines()<\/code> aus dem Ausgabe-Stream des Prozesses. Auch diese Methode ist eine Erweiterung von Groovy.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import org.junit.jupiter.api.*\n\nclass PipeConsumerTest {\n\n    @Test\n    void \"consumer increments text from stdin\"() {\n        def process = \".\/pipe_consumer.groovy\".execute()\n        def stdin = new PrintStream(process.getOutputStream(), true)\n        def stdout = process.getInputStream()\n\n        stdin.println(\"1\")\n        stdin.println(\"2\")\n        stdin.println(\"3\")\n        stdin.close()\n\n        assert stdout.readLines() == [\"2\", \"3\", \"4\"]\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nach dem Aufruf von <code>execute()<\/code> l\u00e4uft der Prozess eigentlich schon. Da er aber keine Eingaben \u00fcber sein <code>stdin<\/code> bekommt, passiert auch noch nichts und wir m\u00fcssen uns nicht darum k\u00fcmmern, den Prozess erst dann zu starten, wenn wir sein <code>stdin<\/code> und <code>stdout<\/code> gesetzt haben. Er blockiert einfach so lange, bis er Input bekommt &#8211; ein weiterer Vorteil des Arbeitens mit Pipes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Etwas verwirrend sind die Bezeichnungen der Methoden: Um an das <code>stdin<\/code> des Prozesses zu kommen, muss man <code>getOutputStream<\/code> aufrufen. Die Blickrichtung hier ist aus der Sicht des aufrufenden Prozesses, statt aus der Sicht des Objekts, dessen Methode aufgerufen wird. Gleiches gilt in Gegenrichtung f\u00fcr <code>stdout<\/code> und <code>getInputStream()<\/code>. Wir h\u00e4tten auch \u00fcber die Properties <code>outputStream<\/code> und <code>inputStream<\/code> auf die Streams zugreifen k\u00f6nnen, aber das h\u00e4tte die Verwirrung nur noch vergr\u00f6\u00dfert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der Consumer selbst sieht so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env groovy\ndef deserializer = new Deserialisierer(new Inkrementer())\n\nSystem.in.eachLine {\n   deserializer.deserialize(it) \n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Alles zusammen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Zum Schluss lassen wir den Producer und zwei Consumer einmal zusammen laufen. Das Ergebnis sieht folgenderma\u00dfen aus:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"425\" src=\"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/pipe-end-to-end-1024x425.png\" alt=\"\" class=\"wp-image-5815\" srcset=\"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/pipe-end-to-end-1024x425.png 1024w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/pipe-end-to-end-300x124.png 300w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/pipe-end-to-end-768x319.png 768w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/pipe-end-to-end-1170x486.png 1170w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/pipe-end-to-end-1920x797.png 1920w, https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/pipe-end-to-end-585x243.png 585w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Producer und Consumer in Aktion. Ganz links der laufende Producer. In der Mitte und rechts zwei Consumer, die aus der Named Pipe lesen und die inkrementierten Zahlen ausgeben.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Unsere Tests sind gr\u00fcn und wir k\u00f6nnen uns sicher sein, dass der Consumer so funktioniert wie er soll. Das haben wir sowohl in Isolation als auch integriert getestet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Da Skripte von Groovy kompiliert werden, konnten wir einige dynamische Features f\u00fcr Stubbing und Mocking nicht einsetzen, aber Groovy bringt gen\u00fcgend andere Mittel mit, die auch kompiliert funktionieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dank Pipes und FIFOs haben wir ein einfaches Mittel zur Interprozesskommunikation, f\u00fcr das wir sonst vielleicht eine Messaging-L\u00f6sung h\u00e4tten benutzen m\u00fcssen. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zudem konnten wir durch das einheitliche Interface auch f\u00fcr integrierte Tests mit wenig Aufwand die Ein- und Ausgaben manipulieren und testen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verpasst nicht den n\u00e4chsten Teil von Groovy Shell Scripting! Dort werden wir \u00fcber Dependency Management f\u00fcr eure Skripte sprechen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dies ist Teil 3 unserer Serie zu Groovy Shell Scripting. Im ersten Teil haben wir&hellip;<\/p>\n","protected":false},"author":28,"featured_media":5824,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_crdt_document":"","_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"cybocfi_hide_featured_image":"","footnotes":""},"categories":[4,8,12],"tags":[61,18,68,42,46],"class_list":["post-5814","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-gastbeitrag","category-software-qualitaet","category-webdevelopment-tools","tag-best-practice","tag-code","tag-tools","tag-tutorial","tag-webdevelopment"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.0 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Groovy Shell Scripting Teil 3 - Testing - Employer Branding und Tutorials Web Development<\/title>\n<meta name=\"description\" content=\"Wie man Groovy Shell Scripts testet - sowohl isoliert mit TDD als auch integriert. Wie man den Code daf\u00fcr organisiert. Testing mit Groovy allgemein.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Groovy Shell Scripting Teil 3 - Testing - Employer Branding und Tutorials Web Development\" \/>\n<meta property=\"og:description\" content=\"Wie man Groovy Shell Scripts testet - sowohl isoliert mit TDD als auch integriert. Wie man den Code daf\u00fcr organisiert. Testing mit Groovy allgemein.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/\" \/>\n<meta property=\"og:site_name\" content=\"Employer Branding und Tutorials Web Development\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/nevercodealone\/\" \/>\n<meta property=\"article:published_time\" content=\"2019-01-23T21:16:15+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-01-23T21:27:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"820\" \/>\n\t<meta property=\"og:image:height\" content=\"312\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Georg Berky\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@georgberky\" \/>\n<meta name=\"twitter:site\" content=\"@nevercodealone\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Georg Berky\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"10\u00a0Minuten\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Groovy Shell Scripting Teil 3 - Testing - Employer Branding und Tutorials Web Development","description":"Wie man Groovy Shell Scripts testet - sowohl isoliert mit TDD als auch integriert. Wie man den Code daf\u00fcr organisiert. Testing mit Groovy allgemein.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/","og_locale":"de_DE","og_type":"article","og_title":"Groovy Shell Scripting Teil 3 - Testing - Employer Branding und Tutorials Web Development","og_description":"Wie man Groovy Shell Scripts testet - sowohl isoliert mit TDD als auch integriert. Wie man den Code daf\u00fcr organisiert. Testing mit Groovy allgemein.","og_url":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/","og_site_name":"Employer Branding und Tutorials Web Development","article_publisher":"https:\/\/www.facebook.com\/nevercodealone\/","article_published_time":"2019-01-23T21:16:15+00:00","article_modified_time":"2019-01-23T21:27:23+00:00","og_image":[{"width":820,"height":312,"url":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg","type":"image\/jpeg"}],"author":"Georg Berky","twitter_card":"summary_large_image","twitter_image":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg","twitter_creator":"@georgberky","twitter_site":"@nevercodealone","twitter_misc":{"Verfasst von":"Georg Berky","Gesch\u00e4tzte Lesezeit":"10\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#article","isPartOf":{"@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/"},"author":{"name":"Georg Berky","@id":"https:\/\/blog.nevercodealone.de\/#\/schema\/person\/d3ae4d469af341690c2dade093567b9c"},"headline":"Groovy Shell Scripting Teil 3 &#8211; Testing","datePublished":"2019-01-23T21:16:15+00:00","dateModified":"2019-01-23T21:27:23+00:00","mainEntityOfPage":{"@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/"},"wordCount":1576,"commentCount":0,"publisher":{"@id":"https:\/\/blog.nevercodealone.de\/#organization"},"image":{"@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg","keywords":["Best Practice","Code","Tools","Tutorial","Webdevelopment"],"articleSection":["Gastbeitrag","Software-Qualit\u00e4t","Webdevelopment-Tools"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/","url":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/","name":"Groovy Shell Scripting Teil 3 - Testing - Employer Branding und Tutorials Web Development","isPartOf":{"@id":"https:\/\/blog.nevercodealone.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#primaryimage"},"image":{"@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg","datePublished":"2019-01-23T21:16:15+00:00","dateModified":"2019-01-23T21:27:23+00:00","description":"Wie man Groovy Shell Scripts testet - sowohl isoliert mit TDD als auch integriert. Wie man den Code daf\u00fcr organisiert. Testing mit Groovy allgemein.","breadcrumb":{"@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#primaryimage","url":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg","contentUrl":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2019\/01\/Groovy-Shell-Scripting-Teil-3-Testing.jpg","width":820,"height":312,"caption":"Groovy Shell Scripting Teil 3 - Testing"},{"@type":"BreadcrumbList","@id":"https:\/\/blog.nevercodealone.de\/groovy-shell-scripting-testing-teil-3\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Startseite","item":"https:\/\/blog.nevercodealone.de\/"},{"@type":"ListItem","position":2,"name":"Groovy Shell Scripting Teil 3 &#8211; Testing"}]},{"@type":"WebSite","@id":"https:\/\/blog.nevercodealone.de\/#website","url":"https:\/\/blog.nevercodealone.de\/","name":"Employer Branding und PHP Training","description":"Employer Branding f\u00fcr Web Development Jobs, viele Tutorials und HowTo Gastbeitr\u00e4ge der Community","publisher":{"@id":"https:\/\/blog.nevercodealone.de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.nevercodealone.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/blog.nevercodealone.de\/#organization","name":"Never Code Alone","url":"https:\/\/blog.nevercodealone.de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/blog.nevercodealone.de\/#\/schema\/logo\/image\/","url":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2016\/03\/nca_2.png","contentUrl":"https:\/\/blog.nevercodealone.de\/wp-content\/uploads\/2016\/03\/nca_2.png","width":212,"height":130,"caption":"Never Code Alone"},"image":{"@id":"https:\/\/blog.nevercodealone.de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/nevercodealone\/","https:\/\/x.com\/nevercodealone","https:\/\/www.instagram.com\/nevercodealone\/","https:\/\/www.youtube.com\/c\/NeverCodeAlone"]},{"@type":"Person","@id":"https:\/\/blog.nevercodealone.de\/#\/schema\/person\/d3ae4d469af341690c2dade093567b9c","name":"Georg Berky","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/blog.nevercodealone.de\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/892f0dbed8c441dcc08eee0185271abe522c26829ea56967a156c5802a035ca5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/892f0dbed8c441dcc08eee0185271abe522c26829ea56967a156c5802a035ca5?s=96&d=mm&r=g","caption":"Georg Berky"},"description":"Ich bin leiderschaftlicher Programmierer, haupts\u00e4chlich in JVM-Sprachen, z.B. Java, Groovy oder Clojure. Programmieren ist mein Handwerk, aber dazu geh\u00f6ren auch Themen wie die Automatisierung von Builds oder Deployments und Scripting in der Shell und Agilit\u00e4t im Team. Seit einigen Jahren bin ich Co-Organisator der Software Craftsmanship Communities im Ruhrgebiet und in D\u00fcsseldorf. Wenn ich nicht programmiere praktiziere ich Aikido oder spiele Trompete.","sameAs":["https:\/\/twitter.com\/georgberky","https:\/\/x.com\/georgberky"],"url":"https:\/\/blog.nevercodealone.de\/author\/georgberky\/"}]}},"_links":{"self":[{"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/posts\/5814","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/users\/28"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/comments?post=5814"}],"version-history":[{"count":10,"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/posts\/5814\/revisions"}],"predecessor-version":[{"id":5835,"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/posts\/5814\/revisions\/5835"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/media\/5824"}],"wp:attachment":[{"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/media?parent=5814"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/categories?post=5814"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.nevercodealone.de\/wp-json\/wp\/v2\/tags?post=5814"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}