Java 覆盖测试在使用扫描仪的方法上失败,但如果仅执行特定测试方法,则同一测试成功通过

Java Test with coverage fails on methods that use Scanner, but the same test passess successfully if only the particlar test method is executed

我是 Java 的新手,我一直在努力解决这个问题,即覆盖率测试在接受输入(通过扫描仪)的方法上失败。奇怪的是,如果我只 运行 来自 intellij 的特定测试方法,完全相同的测试通过。

我已经能够在下面这个简单的例子中在一定程度上复制它 -
InputName.java

import java.util.Scanner;

public class InputName{
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        String name = inputName();
        System.out.println("You entered - " + name);
    }

    public static String inputName() throws IllegalArgumentException {
        System.out.println("Please enter your name with up to 8 characters: ");
        String name = scanner.nextLine();
        Integer nameLength = name.length();
        if (nameLength > 8){
            throw new IllegalArgumentException("Name length is more than 8 characters long");
        }
        else {
            return name;
        }

    }
}

InputNameTest.java

import org.junit.Test;
import java.io.ByteArrayInputStream;
import static org.junit.Assert.assertEquals;

public class InputNameTest{
    @Test
    public void testInputName() {
//        Scanner scan = new Scanner(System.in);
//        InputStream sysInBackup = System.in;
        System.setIn(new ByteArrayInputStream("JohnDoe\n".getBytes()));
        assertEquals("JohnDoe", InputName.inputName());
//        System.setIn(sysInBackup);
    }

    @Test (expected = IllegalArgumentException.class)
    public void testIllegalArgumentException() {
//        InputStream sysInBackup = System.in;
        System.setIn(new ByteArrayInputStream("JohnnyDoe\n".getBytes()));
        InputName.inputName();
//        System.setIn(sysInBackup);
    }
}

目录结构 -

Check
|-- src
     |- main
     |   |- InputName.java
     |- tests
         |- InputNameTest.java

测试使用 Junit4。

在这里,只有第二个测试可能会失败。但是在我的实际项目中,当 运行 作为覆盖范围时,两个测试都失败了(可能是因为测试测试了在此方法之前使用 Scanner 的其他方法)。但是当 运行 单独时,两者都通过了。

整个测试失败 运行 覆盖 - 当 运行ning 只有那个测试方法时通过 -

你的 UnitTest 很脆弱,因为设计不好。 你的 class 正在测试 S.T.U.P.I.D. code.

“Utility classes”中的方法应该是 static,这是一种常见的误解。事实上,它们不应该,特别是如果这些方法不是 纯函数 但需要一些 依赖项 就像您的代码那样。

该问题的正确解决方案是从方法中删除 static 关键字并将扫描仪 class 的实例作为 构造函数参数传递 而不是在 class InputName 本身内部实例化它:

import java.util.Scanner;

public class InputName{
    private final Scanner scanner;

    InputName(Scanner scanner){
      this.scanner = scanner;
    }

    public static void main(String[] args) {
        String name = new InputName(new Scanner(System.in)).inputName();
        System.out.println("You entered - " + name);
    }

    public  String inputName() throws IllegalArgumentException {
        System.out.println("Please enter your name with up to 8 characters: ");
        String name = scanner.nextLine();
        Integer nameLength = name.length();
        if (nameLength > 8){
            throw new IllegalArgumentException("Name length is more than 8 characters long");
        }
        else {
            return name;
        }

    }
}

你的测试会变成这样:

import org.junit.Test;
import java.io.ByteArrayInputStream;
import static org.junit.Assert.assertEquals;

public class InputNameTest{
    @Test
    public void testInputName() {
        assertEquals("JohnDoe", 
          new InputName(new Scanner(new ByteArrayInputStream(
                     "JohnDoe\n".getBytes()))).inputName());
    }

    @Test (expected = IllegalArgumentException.class)
    public void testIllegalArgumentException() {
        assertEquals("JohnnyDoe", 
          new InputName(new Scanner(new ByteArrayInputStream(
                     "JohnnyDoe\n".getBytes()))).inputName());
    }
}

不用纠结System.in

当然,使用 mocking 框架(如 Mockito)来创建 Scanner class 的测试替身会更好。