Java - How to test exception which never will occur?
我有Utils类method,当给定数据不正确时抛出异常。
我还有Service使用此method,但数据始终以在调用期间正确的方式生成。 数据由另一个utils类生成。
我明白我应该从Utils类中抛出这个异常 - 但是我不能从Service抛出它 - 所以我必须抓住它。
我该如何测试,模拟这个例外?
对此数据的所有操作都是私有方法。
我想避开PowerMock,因为我听说它是设计糟糕的标志。
那么问题是,如何在良好的设计中实现这一点?
-
我不清楚你想要测试什么。 Utils,Service或两者相互结合?
-
只是嘲笑你的Utils课程。你应该能够在没有Powermock的情况下进行模拟。
-
您可以注入Utils.method,即使使用FunctionalInterface然后模拟它并让模拟抛出异常
-
使用PowerMock也不错,需要它可能是一个指示你的设计是坏的(如果你听到的是真的)。不使用它并不会突然使您的设计变得更好。
-
@JoeC,我想测试服务。
-
@GuillaumeF。,Utils方法是静态的。
-
@JornVernee好的,但这个问题可以表明我的设计有些不好 - 我想知道它是什么,我该如何解决这个问题。
-
@Novaterata你的意思是我应该为Utils类创建一些接口?对我来说根本不清楚。
-
您还可以使用反射来访问私有方法
-
啊,静态方法。我想我们刚刚找到了你的潜力"指标你的设计不好"。
-
@JoeC,utils类用静态方法都不好?例如,如果我需要XML编组和解组,这将由多个类使用,我应该创建常规的XmlUtils类,每次创建实例来调用某些与特定实例没有任何共同点的方法?这种感觉在哪里?
-
@GarethJordan我认为这和使用PowerMock一样糟糕。
-
不是真的,我在手机上,所以输入一个例子很难,但请看en.m.wikipedia.org/wiki/Reflection_(computer_programming)
-
@Line Joe指的是静态方法的可测试性。您不能使用DI,因此您无法通过策略轻松地与模拟版本进行交互。 OT:你可以使用Mockito的when(Utils.someMethod()).doThrow(new Exception())
-
@GarethJordan我想我有一些线索是什么反思。但是在快速我在Google上输入"反射单元测试"并且对这个问题的最高投票答案(stackoverflow.com/questions/2811141/&)你可以找到与他们对PowerMock的说法非常类似的东西:"如果你正在测试你的自己的代码,你需要使用Reflection意味着你的设计是不可测试的,所以你应该修复它而不是诉诸于Reflection。"
-
@VinceEmigh,什么是DI,什么是OT,什么是策略?据我所知,我不能把静态方法放到Mockito"when"。
-
@Line你可以对使用反射实例化对象的持久框架说同样的话。当存在特殊要求时,反射很有用。在这种情况下,如果方法更适合作为实用方法,则在测试中使用反射可能更有利于使生产代码与要求保持一致。您如何看待像Mockito这样的框架?
-
@Line您可以使用PowerMock。对于错误的方向感到抱歉,我有一段时间没有使用过Mockito或PowerMock,现在它们有点混合了。非常确定PowerMock有一个mockStatic。 DI是依赖注入,OT是On Topic(对你帖子的建议)
-
我不同意,如果你想在一个类上独立单元测试一个私有方法。但是在您的情况下,服务类调用Utilities类总线的方法不是私有的,因此您应该能够将"无效"输入传递给该方法,而该方法又会使用无效输入和触发器调用私有方法例外
-
@GarethJordan,我听说私人方法应该只由公众测试:)不幸的是我不能这样做。问题是我将对象传递给此方法,并且此对象的每个实例对此方法都有效。
-
@VinceEmigh,谢谢你的解释。我知道我可以用PowerMock做到这一点,我提到我正在寻找另一种方法。
-
@Line它的设计并不差,也没有暗示设计糟糕的代码。是的,它可以被滥用并允许对设计不良的代码进行测试,但这并不意味着应该完全避免它。 default方法可能被滥用,但这并不意味着它们应该被避免 - 它们有目的。我不知道Utils#method的完整用法细节,但是可以通过从static切换到DI来遇到其他设计问题,这对于测试来说不值得。如果您想测试依赖私有类成员的静态工厂怎么办?
-
@Line我的意思是你替换我认为是一些静态实用方法与可以注入的东西或者在Java 8中你可以使用FunctionalInterface注入任何单个方法,就像在java.utils.function中那样。因为您需要抛出一个已检查的异常。您可以创建自己的单一方法接口,该接口与静态方法具有完全相同的签名,并在其上放置@FunctionalInterface。然后你可以使用任何匹配该签名的lambda。
从您的描述看起来像这样:
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()); |
-
谢谢。但是,如果没有其他类的问题,它是否只是为了测试生产代码中不会被使用的几行来编写代码? :/
-
@Line您不仅可以获得可测试性,还可以获得可维护性。例如,如果您想在不同情况下使用不同的数据提供程序,则不必重构Service类。当然,另一种选择是不测试该分支。
-
SOLID中的D实际上是依赖性反转,它指的是通过抽象解耦模块。依赖注入往往依赖于依赖性反转(就像在这种情况下),但它不是一个要求(例如,注入String)。 SOLID不考虑依赖注入
-
@VinceEmigh谢谢,我把两者弄糊涂了。我一直记得依赖倒置作为控制的反转,它不是以D开头的。
-
如果使DataConsumer成为FunctionalInterface而不是Utils.getConsumer()则不需要实现。可以使用使用Utils.method的lambda而不修改该代码。
这是一种您想要引入依赖注入的方法,但无论出于何种原因您都不想更改遗留代码。
假设您有一些静态实用方法,如下所示:
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); |