我遇到过一些java代码,其中公共构造函数使用一堆new
运算符调用package-private构造函数来创建新对象。
public class Thing {
//public
public Thing(String param1, int paramm2) {
this(param1, param2, new Dependency1(), new Dependency2());
}
//package-private for the sake of testing
Thing(String param1, int param2, Dependency1 param3, Dependency2 param4) {
this.memberVar1 = param1;
this.memberVar2 = param2;
this.memberVar3 = param3;
this.memberVar4 = param4;
}
//...rest of class...
}
在我看来,这是错误的,因为您正在编写代码来测试它而不是编写正确的代码。我认为其他两个选项(我能想到的)是创建工厂或使用PowerMockito
在适用的情况下注入新对象。就个人而言,我会写如下所示。
public class Thing {
//public
public Thing(String param1, int paramm2) {
this.memberVar1 = param1;
this.memberVar2 = param2;
this.memberVar3 = new Dependency1();
this.memberVar4 = new Dependency2();
}
//...rest of class...
}
实施此方法的最佳做法/正确方法是什么?
答案 0 :(得分:3)
在任何已发布的内容中包含特定于测试的代码通常是不好的形式(但也有例外情况,所以不要过于仔细地阅读)。这有几个原因。
<强> 1。外部人员可能会以一种你从未想过的方式使用测试构造函数,因为他们要么没有读取文档,指出它用于测试,要么开发人员忘记记录它。
我们假设您想要写一些有用的Thing
扩展名,但您无法在Thing
的公共/受保护API中找到任何可以执行此操作的内容想要它。然后你会发现这个包私有构造函数似乎允许你想要的东西,但后来才发现它破坏了你的代码。你仍然无法做你想要的和你浪费时间去探索那些没有成功的API。任何执行此操作的人都会对API持否定态度,并且不会将其推荐给其他任何人。
<强> 2。重构包名称会破坏内容。
由于Java默认可见性的工作方式,此测试代码对生产代码中发生的重构没有很大的弹性。如果测试代码位于同一个包中,则它只能调用该构造函数。如果重命名包,则调用它的测试代码将无法访问它,从而导致编译错误。当然,对于开发代码和测试的人来说,它是一个简单的解决方案,但即使没有添加这个小麻烦,重构已经不是很有趣。如果一群人以前能够成功地使用包私有东西来满足他们的需求,那么这就成了一个主要的问题 - 现在他们所有的代码都被破坏了。
在某些情况下,可以在测试和生产环境中运行难以编写的代码(例如,仅在应用程序联网时运行的功能)。在这些情况下,依赖注入可以成为你的朋友,但是如果可以避免更复杂的测试方案而不牺牲功能覆盖或在API中添加钩子而不想让其他开发人员看到它,那么更简单的测试总是最好的。
答案 1 :(得分:0)
我知道在StackExchange的其他地方对这个确切的话题进行了很好的讨论,但我现在对谷歌的好运并不强烈。我发现的副本不太有启发性。
就个人而言,我已经在生产中看到过三种类型的测试代码。
if (isTesting) foo() else bar();
Object foo();
通过测试代码调用,但从未投入生产。Foo(Object dependency1, Object dependency2)
通过测试代码调用,但从未投入生产。前两种类型在几个方面都有害。
第三种类型当然可能以上述相同的方式有害;但是,我相信所谓的测试构造函数可以减轻或消除生产中其他类型测试代码固有的问题。
在构造函数中调用new
通常是不好的,因为依赖注入通常是好的。但是,在某些情况下,类的依赖关系的合理默认实现是显而易见的,并且在构造函数中实例化这些依赖关系会为客户端提供最简单的可能接口。在这些场景中,我发现链接第二个构造函数是合适的,它不会不强制任何默认值,无论第二个构造函数是否有助于测试。
作为奖励,链式构造函数实践鼓励编程到接口,因为第二个构造函数会让您考虑除默认值之外的依赖项实现。