关于单元测试:Java – 如何测试永远不会发生的异常?

Java - How to test exception which never will occur?

我有Utilsmethod,当给定数据不正确时抛出异常。

我还有Service使用此method,但数据始终以在调用期间正确的方式生成。 数据由另一个utils类生成。

我明白我应该从Utils类中抛出这个异常 - 但是我不能从Service抛出它 - 所以我必须抓住它。

我该如何测试,模拟这个例外?
对此数据的所有操作都是私有方法。

我想避开PowerMock,因为我听说它是设计糟糕的标志。
那么问题是,如何在良好的设计中实现这一点?


从您的描述看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Service {

    public void someMethod() {
        Data data = AnotherUtils.getData();

        try {
            Utils.method(data); // exception never thrown
        } catch(Exception e) {
            // how to test this branch?
        }
    }

}

目标是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface DataProvider {
    Data getData();
}

interface DataConsumer {
    void method(Data data);
}

class Service {
    private final DataProvider dataProvider;
    private final DataConsumer dataConsumer;

    public Service(DataProvider dataProvider, DataConsumer dataConsumer) {...}

    public void someMethod() {
        Data d = dataProvider.getData();

        try {
            dataConsumer.method(data);
        } catch(Exception e) {

        }
    }
}

这种技术称为依赖注入。

然后,在测试时,您可以简单地为此DataProvider接口提供模拟实现,该接口确实返回错误数据:

1
2
3
4
5
6
7
@Test(expected=Exception.class)
public void myTest() {
    DataProvider badDataProvider = () -> new BadData(); // Returns faulty data

    Service service = new Service(badDataProvider, Utils.getConsumer());
    service.someMethod(); // boom!
}

对于非测试代码,您可以简单地将您已有的utils类包装在这些接口中:

1
2
3
4
5
6
7
class AnotherUtils {
    public static Data getData() {...}

    public static DataProvider getProvider() {
        return AnotherUtils::getData;
    }
}

...

1
Service service = new Service(AnotherUtils.getProvider(), Utils.getConsumer());


这是一种您想要引入依赖注入的方法,但无论出于何种原因您都不想更改遗留代码。

假设您有一些静态实用方法,如下所示:

1
2
3
4
5
6
7
class Utils{
    public static Something aMethod(SomethingElse input) throws AnException{
          if(input.isValid())
              return input.toSomething();
          throw new AnException("yadda yadda");
    }
}

你有一个使用该实用方法的类。 您仍然可以使用FunctionalInterface注入它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@FunctionalInterface
interface FunctionThrowsAnException<K,V> {
    V apply(K input) throws AnException;
}

class Service {
    private final FunctionThrowsAnException<SomethingElse,Something> func;

    Service(FunctionThrowsAnException<SomethingElse,Something> func){
        this.func = func;
    }

    Something aMethod(SomethingElse input){
         try{
            return func.apply(input);
         }catch(AnException ex){
            LOGGER.error(ex);
         }
    }
}

然后像这样使用它:

1
new Service(Utils::aMethod).aMethod(input);

测试它:

1
new Service(x -> { throw new AnException("HA HA"); }).aMethod(input);