我如何使用JUnit测试依赖于System.console的代码?

huangapple 未分类评论53阅读模式
英文:

How do I test code dependent on System.console usint JUnit?

问题

CommandLineApp.java

package com.bl.h2;

import java.io.Console;

public class CommandLineApp {
    public static void main(String[] args) {
        final Console c = System.console();
        if (c == null) {
            System.err.println("console not available");
            System.exit(1);
        }

        String userName = c.readLine("Enter your name: ");
        System.out.println("Hello " + userName);
    }
}

CommandLineTest.java

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CommandLineAppTest {
    private final ByteArrayOutputStream out = new ByteArrayOutputStream();
    private final PrintStream originalOut = System.out;

    @BeforeEach
    public void setStreams() {
        System.setOut(new PrintStream(out));
    }

    @AfterEach
    public void restoreInitialStreams() {
        System.setOut(originalOut);
    }

    @Test
    public void testInputOutput() {
        CommandLineApp.main(new String[]{});
        assertEquals("Enter your name: ", out.toString());
    }
}

Run mvn clean test and the test fails:

[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 s - in com.bl.h2.AppTest
[INFO] Running com.bl.h2.CommandLineAppTest
console not available
[INFO] 

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.1:test (default-test) on project reflection-testing: There are test failures.
[ERROR] 
[ERROR] Please refer to /Users/hhimanshu/code/java/reflection-testing/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was /bin/sh -c cd /Users/hhimanshu/code/java/reflection-testing && /usr/local/Cellar/openjdk/13.0.2+8_2/libexec/openjdk.jdk/Contents/Home/bin/java -jar /Users/hhimanshu/code/java/reflection-testing/target/surefire/surefirebooter14077774838729856584.jar /Users/hhimanshu/code/java/reflection-testing/target/surefire 2020-05-04T15-50-24_446-jvmRun1 surefire18146927270810499563tmp surefire_03196121267245760589tmp
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR] Crashed tests:
[ERROR] com.bl.h2.CommandLineAppTest
[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was /bin/sh -c cd /Users/hhimanshu/code/java/reflection-testing && /usr/local/Cellar/openjdk/13.0.2+8_2/libexec/openjdk.jdk/Contents/Home/bin/java -jar /Users/hhimanshu/code/java/reflection-testing/target/surefire/surefirebooter14077774838729856584.jar /Users/hhimanshu/code/java/reflection-testing/target/surefire 2020-05-04T15-50-24_446-jvmRun1 surefire18146927270810499563tmp surefire_03196121267245760589tmp
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR] Crashed tests:
[ERROR] com.bl.h2.CommandLineAppTest
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:669)
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:282)
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:245)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1183)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:1011)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:857)
[ERROR] 	at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:210)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:156)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:148)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
[ERROR] 	at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:56)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
[ERROR] 	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:305)
[ERROR] 	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:192)
[ERROR] 	at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:105)
[ERROR] 	at org.apache.maven.cli.MavenCli.execute(MavenCli.java:957)
[ERROR] 	at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:289)
[ERROR] 	at org.apache.maven.cli.MavenCli.main(MavenCli.java:193)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR] 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] 	at java.base/java.lang.reflect.Method.invoke(Method.java:567)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:282)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:225)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:406)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launch

<details>
<summary>英文:</summary>

I am writing a simple program using `System.console` and I am writing a test for that. I am using `maven` to test it and the test fails. 

The complete code is available at https://github.com/hhimanshu/reflection-testing/tree/console-test

Following are some more details

**CommandLineApp.java**
```java
package com.bl.h2;

import java.io.Console;

public class CommandLineApp {
    public static void main(String[] args) {
        final Console c = System.console();
        if (c == null) {
            System.err.println(&quot;console not available&quot;);
            System.exit(1);
        }

        String userName = c.readLine(&quot;Enter your name: &quot;);
        System.out.println(&quot;Hello &quot; + userName);
    }
}

CommandLineTest.java

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CommandLineAppTest {
    private final ByteArrayOutputStream out = new ByteArrayOutputStream();
    private final PrintStream originalOut = System.out;

    @BeforeEach
    public void setStreams() {
        System.setOut(new PrintStream(out));
    }

    @AfterEach
    public void restoreInitialStreams() {
        System.setOut(originalOut);
    }

    @Test
    public void testInputOutput() {
        CommandLineApp.main(new String[]{});
        assertEquals(&quot;Enter your name: &quot;, out.toString());
    }

I run mvn clean test and the test fails

[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 s - in com.bl.h2.AppTest
[INFO] Running com.bl.h2.CommandLineAppTest
console not available
[INFO] 

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.1:test (default-test) on project reflection-testing: There are test failures.
[ERROR] 
[ERROR] Please refer to /Users/hhimanshu/code/java/reflection-testing/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was /bin/sh -c cd /Users/hhimanshu/code/java/reflection-testing &amp;&amp; /usr/local/Cellar/openjdk/13.0.2+8_2/libexec/openjdk.jdk/Contents/Home/bin/java -jar /Users/hhimanshu/code/java/reflection-testing/target/surefire/surefirebooter14077774838729856584.jar /Users/hhimanshu/code/java/reflection-testing/target/surefire 2020-05-04T15-50-24_446-jvmRun1 surefire18146927270810499563tmp surefire_03196121267245760589tmp
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR] Crashed tests:
[ERROR] com.bl.h2.CommandLineAppTest
[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was /bin/sh -c cd /Users/hhimanshu/code/java/reflection-testing &amp;&amp; /usr/local/Cellar/openjdk/13.0.2+8_2/libexec/openjdk.jdk/Contents/Home/bin/java -jar /Users/hhimanshu/code/java/reflection-testing/target/surefire/surefirebooter14077774838729856584.jar /Users/hhimanshu/code/java/reflection-testing/target/surefire 2020-05-04T15-50-24_446-jvmRun1 surefire18146927270810499563tmp surefire_03196121267245760589tmp
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR] Crashed tests:
[ERROR] com.bl.h2.CommandLineAppTest
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:669)
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:282)
[ERROR] 	at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:245)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1183)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:1011)
[ERROR] 	at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:857)
[ERROR] 	at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:210)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:156)
[ERROR] 	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:148)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
[ERROR] 	at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:56)
[ERROR] 	at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
[ERROR] 	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:305)
[ERROR] 	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:192)
[ERROR] 	at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:105)
[ERROR] 	at org.apache.maven.cli.MavenCli.execute(MavenCli.java:957)
[ERROR] 	at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:289)
[ERROR] 	at org.apache.maven.cli.MavenCli.main(MavenCli.java:193)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR] 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] 	at java.base/java.lang.reflect.Method.invoke(Method.java:567)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:282)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:225)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:406)
[ERROR] 	at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:347)
[ERROR] 
[ERROR] -&gt; [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

How can I test this piece of code using JUnit5?

答案1

得分: 0

你需要将 `Console` 注入到你的 `App`以将其与不可模拟的 `System` 解耦
```java
import java.io.Console;
final class App implements Runnable {
    private final Console console;

    @Override
    public void run() {
        final String pass = this.console.readLine("Your name: ");
        this.console.printf(
            "Hello %s",
            pass
        );
    }
    
    public static void main(final String... args) {
        new App(System.console()).run();
    }

}
然后你可以使用 [jmockit](https://jmockit.github.io/) 来模拟最终类 `Console`:
```java
import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import org.junit.jupiter.api.Test;

final class AppTest {

    @Test
    void printsName(@Mocked final Console console) {
        new Expectations() {{
            console.readLine(withInstanceOf(String.class));
            result = "Jeff";
        }};
        new App(console).run();
        new Verifications() {{
            console.printf(anyString, "Jeff");
        }};
    }
}

注意:你需要为 jmockit 添加 javaagent 才能使其工作。


<details>
<summary>英文:</summary>

You need to inject `Console` to your `App` in order to decouple it from the `System`, which is unmockable. 

import java.io.Console;
final class App implements Runnable {
private final Console console;

@Override
public void run() {
    final String pass = this.console.readLine(&quot;Your name: &quot;);
    this.console.printf(
        &quot;Hello %s&quot;,
        pass
    );
}

public static void main(final String... args) {
    new App(System.console()).run();
}

}


Than you can use [jmockit](https://jmockit.github.io/) to mock final class `Console`:
```java
import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import org.junit.jupiter.api.Test;

final class AppTest {

    @Test
    void printsName(@Mocked final Console console) {
        new Expectations() {{
            console.readLine(withInstanceOf(String.class));
            result = &quot;Jeff&quot;;
        }};
        new App(console).run();
        new Verifications() {{
            console.printf(anyString, &quot;Jeff&quot;);
        }};
    }
}

Note: you need javaagent for jmockit to work

huangapple
  • 本文由 发表于 2020年5月5日 07:07:33
  • 转载请务必保留本文链接:https://java.coder-hub.com/61603105.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定