38719


今、しりとりのゲームを作ろうと思い、Javaでプログラミングしています。

Junitに以下のようなテストケースを書いたところ、NoSuchElementExceptionが出てしまいます。

[code]

    import java.io.ByteArrayInputStream;
    import java.io.InputStream;
    
    import org.junit.Test;
    import static org.junit.Assert.*;
    
    public class IOProcessorTest {
        @Test
        public void testPromptInput() {
            String input = "シリトリ 1 しりとり 2 シリトリ 1 シリトリ f 1";
            InputStream in = new ByteArrayInputStream(input.getBytes());
            System.setIn(in);
            assertEquals("シリトリ", IOProcessor.promptInput("テストします。"));
            assertEquals("シリトリ", IOProcessor.promptInput("テストします。"));
            assertEquals("シリトリ", IOProcessor.promptInput("テストします。"));
        }
        @Test
        public void testConvertToKata() {
            assertEquals("シリトリ", IOProcessor.convertToKata("しりとり"));
            assertEquals("シリトリ", IOProcessor.convertToKata("シリトリ"));
        }   
    }
    
[/code]

テストしているクラスのコードは以下の通りです。

[code]

    import java.util.Scanner;
    import java.lang.StringBuffer;
    
    public class IOProcessor {
    
        static Scanner sc = new Scanner(System.in);
        static FormChecker form = new FormChecker();
        static AnsChecker ans = new AnsChecker();
    
        public static String promptInput(String msg) {
            boolean confirmed = false;
            String word = null;
            while (!confirmed) {
                System.out.println(msg);
                word = sc.next();
                confirmed = confirmInput(word);
            }
            return word;
        }
    
        private static boolean confirmInput(String word) {
            boolean confirmed = true;
            boolean validInput = false;
            while (!validInput) {
                System.out.println("What you hava entered is " + word + ".");
                System.out.println("Do you want to continue?");
                System.out.println("1. Yes, I want to continue. 2. No, I want to change.");
                String input = sc.next();
    
                switch (input) {
                    case "1":
                        validInput = true;
                        break;
                    case "2":
                        confirmed = false;
                        validInput = true;
                        break;
                    default:
                        System.out.println("Enter 1 or 2.");
                }
            }
            return confirmed;
        }
    
        public static String convertToKata(String word) {
            StringBuffer sb = new StringBuffer(word);
            for (int i = 0; i < sb.length(); i++) {
                char c = sb.charAt(i);
                if (c >= 'ぁ' && c <= 'ん') {
                    sb.setCharAt(i, (char) (c - 'ぁ' + 'ァ'));
                }
            }
            return sb.toString();
        }
    }
    
[/code]

今、テストに2つメソッドがありますが、testConvertToKataだけをコメントアウトしてから実行すると、例外が出ません。逆に、testPromptInputだけをコメントアウトしてから実行した場合も例外が出ません。2つのメソッドを同時に実行した場合にのみ例外が出るのですがどうしてでしょうか。

プログラミング初心者で、初めてstackoverflowで質問するので分かりにくい点があるかと思いますが、ご教示いただければ幸いです。


CC BY-SA 3.0
2017-10-15T04:48:48.677
38712
2017-10-17T01:27:40.117
24212
question
java junit exception
junitで、1つのクラスに2つの@Testのアノテーションをつけたメソッドを書くとNoSuchElementExceptionが出る。


### 問題なく動く場合:

  1. System.in を `System.setIn` によって、`シリトリ 1 しりとり 2 シリトリ 1 シリトリ f 1` の ByteArrayInputStream に設定する
  2. `static Scanner sc = new Scanner(System.in)` が実行される
  3. `promptInput` が呼ばれる

### 問題が発生している場合:

  1. `static Scanner sc = new Scanner(System.in)` が実行される(プロセスの標準入力に対するスキャナが定義される)
  2. System.in を `System.setIn` によって、`シリトリ 1 しりとり 2 シリトリ 1 シリトリ f 1` の ByteArrayInputStream に設定する
  3. `promptInput` が呼ばれる -> スキャナが見ている InputStream は変わらず、標準入力から読み込もうとする

### 確認方法:

`promptInput` が最初に `Scanner#next` を呼ぶ直前にブレークポイント仕込んでデバッグ実行、 sc.source.lock
のオブジェクトのクラスが異なる

### 原因:

<https://stackoverflow.com/a/9130560/3090068>

上記によれば、 static ブロックの初期化コードは、「そのクラスが実際に使われる直前まで行われない可能性がある」。

  * 問題なく動いている場合は、 `IOProcessor.promptInput` が最初に呼ばれて、そこで static の初期化コードが走った場合。
  * 問題が発生しているのは、 `IOProcessor.convertToKana` が最初に呼ばれて、そこで static の初期化コードが走り、そののちに System.setIn している。

だと考えられます。

### 対処方法:

promptInput メソッドを、 static メソッドではなく、普通のメソッドとして取り扱い、 `IOProcessor` はフィールドに
InputStream をとるようにする。 promptInput はこのメンバー InputStream から入力を読み込む。

## 対処したコード

以下のようになると思います。

### IOProcessor

[code]

    import java.io.InputStream;
    import java.util.Scanner;
    import java.lang.StringBuffer;
    
    public class IOProcessor {
    
        private final Scanner sc;
    //    static FormChecker form = new FormChecker();
    //    static AnsChecker ans = new AnsChecker();
    
        public IOProcessor(InputStream in) {
            sc = new Scanner(in);
        }
    
        public String promptInput(String msg) {
            boolean confirmed = false;
            String word = null;
            while (!confirmed) {
                System.out.println(msg);
                word = sc.next();
                confirmed = confirmInput(word);
            }
            return word;
        }
    
        private boolean confirmInput(String word) {
            boolean confirmed = true;
            boolean validInput = false;
            while (!validInput) {
                System.out.println("What you hava entered is " + word + ".");
                System.out.println("Do you want to continue?");
                System.out.println("1. Yes, I want to continue. 2. No, I want to change.");
                String input = sc.next();
    
                switch (input) {
                    case "1":
                        validInput = true;
                        break;
                    case "2":
                        confirmed = false;
                        validInput = true;
                        break;
                    default:
                        System.out.println("Enter 1 or 2.");
                }
            }
            return confirmed;
        }
    
        public static String convertToKata(String word) {
            StringBuffer sb = new StringBuffer(word);
            for (int i = 0; i < sb.length(); i++) {
                char c = sb.charAt(i);
                if (c >= 'ぁ' && c <= 'ん') {
                    sb.setCharAt(i, (char) (c - 'ぁ' + 'ァ'));
                }
            }
            return sb.toString();
        }
    }
    
[/code]

### IOProcessorTest

[code]

    import org.junit.Test;
    
    import java.io.ByteArrayInputStream;
    import java.io.InputStream;
    
    import static org.junit.Assert.assertEquals;
    
    public class IOProcessorTest {
        @Test
        public void testPromptInput() {
            String input = "シリトリ 1 しりとり 2 シリトリ 1 シリトリ f 1";
            InputStream in = new ByteArrayInputStream(input.getBytes());
            IOProcessor ioProcessor = new IOProcessor(in);
            assertEquals("シリトリ", ioProcessor.promptInput("テストします。"));
            assertEquals("シリトリ", ioProcessor.promptInput("テストします。"));
            assertEquals("シリトリ", ioProcessor.promptInput("テストします。"));
        }
        @Test
        public void testConvertToKata() {
            assertEquals("シリトリ", IOProcessor.convertToKata("しりとり"));
            assertEquals("シリトリ", IOProcessor.convertToKata("シリトリ"));
        }
    }
    
[/code]


CC BY-SA 3.0
2017-10-15T06:55:59.993
38719
2017-10-17T01:27:40.117
2017-10-17T01:27:40.117
754
754
38712
answer