Discussion:
System.setIn + zwei Scanner auf ByteArrayInputStream
(zu alt für eine Antwort)
Robert Hartmann
2015-11-06 20:13:35 UTC
Permalink
Hallo zusammen,

Unabhängig von der grundsätzlichen Frage,
ob man zwei verschiedene java.util.Scanner-Instanzen
auf System.in überhaupt "los lassen" können sollte,
stellt sich mir nun die tatsächlich praktische Frage:

Warum erhält im unteren Beispiel, die zweite
Scanner-Instanz nicht den Teil "Welt" ?
Wenn ich System.in per System.setIn auf
den ByteArrayInputStream umbiege?



Aufschließende Frage:
Wenn ich ein Modul-Teste (per JUnit4),
und dieses Modul nutzt den Scanner,
wie kann ich System.in so umbiegen,
dass eine beliebige Anzahl von Instanzen des Scanners
die vorgegebenen Testeingaben zu Gesicht bekommt?

Konkreter:
Es soll also für den Test kein Unterschied machen,
ob im zu testenden Modul

Scanner sc = new Scanner(System.in);
String A=sc.next(); String B=sc.next()

oder

Scanner scA = new Scanner(System.in);
Scanner scB = new Scanner(System.in);
String A=scA.next(); String B=scB.next()

steht.


Ich wünsche ein schönes Wochenende,
Gruß Robert









/******** 8< ****************/

import java.io.ByteArrayInputStream;
import java.util.Scanner;

public class SystemInTest {

public static void main(String[] args) {
ByteArrayInputStream inbuf
= new ByteArrayInputStream("Hallo\nWelt\n".getBytes());

System.setIn(inbuf);

Scanner sc1 = new Scanner(System.in);
String s1 = sc1.next();
if (sc1.hasNext()){
System.out.println("sc1: found next okay, open new Scanner:");
Scanner sc2 = new Scanner(System.in);
if (sc2.hasNext()){
String s2 = sc2.next();
System.out.println("S2= "+s2);
} else {
System.err.println("sc2 no next");
}
sc2.close();
}
System.out.println("S1= "+s1);
sc1.close();
}
}
Stefan Ram
2015-11-07 00:31:02 UTC
Permalink
Post by Robert Hartmann
Warum erhält im unteren Beispiel, die zweite
Scanner-Instanz nicht den Teil "Welt" ?
Weil der erste Scanner schon früh erst einmal alles aus
dem java.io.ByteArrayInputStream destruktiv in einen Puffer
(1024 Bytes?) liest.

public final class Main
{ public static void main( final java.lang.String[] args )
{ final byte[] bytes = "Hallo\nWelt\n".getBytes();
java.lang.System.setIn( new java.io.ByteArrayInputStream( bytes ));
final java.util.Scanner sc
= new java.util.Scanner( java.lang.System.in );
java.lang.System.setIn( new java.io.ByteArrayInputStream( bytes ));
final java.util.Scanner sc1
= new java.util.Scanner( java.lang.System.in );
final java.lang.String s = sc.next();
System.out.println( "s= "+ s );
final java.lang.String s1 = sc1.next();
System.out.println( "s1= "+ s1 ); }}
Stefan Ram
2015-11-07 00:33:59 UTC
Permalink
Post by Stefan Ram
Post by Robert Hartmann
Warum erhält im unteren Beispiel, die zweite
Scanner-Instanz nicht den Teil "Welt" ?
Weil der erste Scanner schon früh erst einmal alles aus
dem java.io.ByteArrayInputStream destruktiv in einen Puffer
(1024 Bytes?) liest.
Am besten ist es natürlich, die Nutzung von java.lang.System.in
aufzugeben, und für jeden Scanner möglichst unabhängige Quellen
zu schaffen.
Robert Hartmann
2015-11-09 18:07:05 UTC
Permalink
Hallo Stefan,
Post by Stefan Ram
Am besten ist es natürlich, die Nutzung von java.lang.System.in
aufzugeben, und für jeden Scanner möglichst unabhängige Quellen
zu schaffen.
Naja, das Modul, was per Junit-Test getestet werden soll,
benutzt den Scanner tatsächlich, um Benutzer-Eingaben
von der Konsole einzulesen.
Diese Benutzer-Eingabe wird im Unit-Test simuliert, indem
System.in eben nicht die tatsächliche Konsole ist,
sondern umgelenkt wird und seine Eingaben aus einem irgendgearteten
Objekt, in dem die "Eingaben drinstehen", erhalten soll.


Diese Umlenkung im Test funktioniert tatsächlich prima, solange
nur eine Instanz des Scanners genutzt wird.

Ich werde Deinen Code aus dem anderen Posting entsprechend
morgen in den UnitTest übernehmen und dann mal sehen.

Auf jeden Fall schon vielen Dank.

Gruß Robert
Stefan Ram
2015-11-09 23:33:33 UTC
Permalink
Post by Robert Hartmann
Ich werde Deinen Code aus dem anderen Posting entsprechend
morgen in den UnitTest übernehmen und dann mal sehen.
Der Code war nur ein Diskussionsbeispiel, aber sollte nicht
in Produktivumgebungen eingesetzt werden, weil er nur die
Pufferung von java.util.Scanner illustrieren sollte, aber
das dadurch auftretende Problem nicht lösen kann.

Testbarer Code sollte nicht java.lang.System.in
referenzieren, sondern einen beliebigen InputStream, der
dann bei der Exemplarerzeugung als Abhängigkeit übergeben
werden kann. Außerdem sollte eine Zeichenquelle immer nur in
einen einzigen Scanner verpackt werden, wenn ich
java.util.Scanner richtig verstanden habe.

Es ist praktisch so, wie schon Sommerlad schreibt:

public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( "Hallo, Welt" ); }}

ist nicht so gut testbar. Es sollte besser

public final class Main
{ public static void hello( final java.io.OutputStream out )
{ out.println( "Hallo, Welt" ); }
public static void main( final java.lang.String[] args )
{ hello( java.lang.System.out ); }}

heißen, denn dann

public final class Main
{ public static void hello( final java.io.PrintStream out )
{ out.println( "Hallo, Welt" ); }
public static void test
( final java.util.function.Consumer< java.io.PrintStream >consumer )
{ final java.io.ByteArrayOutputStream array
= new java.io.ByteArrayOutputStream();
final java.io.PrintStream printer
= new java.io.PrintStream( array );
consumer.accept( printer );
final java.lang.String output = array.toString();
if( "Hallo, Welt".equals( output.substring( 0, "Hallo, Welt".length() )))
java.lang.System.out.println( "passed." ); }
public static void main( final java.lang.String[] args )
{ test( Main::hello ); }}

.

Lesen Sie weiter auf narkive:
Loading...