Aad*_*mia 52 java oop unit-testing
我最近读到,制作类单例使得无法模拟类的对象,这使得测试其客户端变得困难.我无法立即理解其根本原因.有人可以解释为什么不可能嘲笑单身人士课程?此外,还有更多的问题与制作班级单身人士有关吗?
Pas*_*ent 57
当然,我可以写一些像不使用单身,它们是邪恶的,使用Guice/Spring /除了第一个,这不会回答你的问题,其次,你有时必须处理单身,当使用遗留代码时例.
所以,我们不讨论单身人士的好坏(还有另外一个问题),但让我们看看如何在测试过程中处理它们.首先,让我们看一下单例的常见实现:
public class Singleton {
private Singleton() { }
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public String getFoo() {
return "bar";
}
}
Run Code Online (Sandbox Code Playgroud)
这里有两个测试问题:
构造函数是私有的,所以我们不能扩展它(我们无法在测试中控制实例的创建,但是,这就是单例的重点).
这getInstance是静态的,因此很难使用单例在代码中注入伪造而不是单例对象.
对于基于继承和多态的模拟框架,这两点显然都是大问题.如果您拥有对代码的控制权,一个选项是通过添加一个允许调整内部字段的setter来使您的单身"更易于测试",如学习停止担心和爱单身一样(您甚至不需要嘲笑那种情况下的框架).如果不这样做,基于拦截和AOP概念的现代模拟框架可以克服前面提到的问题.
例如,Mocking Static Method Calls显示了如何使用JMockit Expectations模拟Singleton .
另一种选择是使用PowerMock,它是Mockito或JMock的扩展,它允许模拟通常不可模仿的东西,如静态,最终,私有或构造方法.您还可以访问类的内部.
bri*_*ndy 14
模拟单身人士的最好方法是根本不使用它们,或者至少不使用传统意义.您可能想要查看的一些实践是:
所以,而不是像你这样访问一个:
Singleton.getInstance().doSometing();
Run Code Online (Sandbox Code Playgroud)
...将您的"单身"定义为一个界面,并让其他东西管理它的生命周期并将其注入您需要的地方,例如作为私有实例变量:
@Inject private Singleton mySingleton;
Run Code Online (Sandbox Code Playgroud)
然后,当您单元测试依赖于单例的类/组件/等时,您可以轻松地注入它的模拟版本.
大多数依赖注入容器都允许您将组件标记为"单例",但是由容器来管理它.
使用上述实践可以更轻松地对代码进行单元测试,并让您专注于功能逻辑而不是布线逻辑.它还意味着您的代码真正开始成为真正的面向对象,因为任何静态方法(包括构造函数)的使用都是程序性的.因此,您的组件也开始变得真正可重用.
查看Google Guice作为10的入门者:
http://code.google.com/p/google-guice/
您还可以查看可以执行此类操作的Spring和/或OSGi.那里有很多IOC/DI东西.:)
Pét*_*rök 13
根据定义,Singleton只有一个实例.因此,它的创造受到阶级本身的严格控制.通常它是一个具体的类,而不是一个接口,由于它的私有构造函数,它不是子类.此外,它由客户端(通过调用Singleton.getInstance()或等效)主动发现,因此您不能轻易使用例如依赖注入将模拟实例替换为"真实"实例:
class Singleton {
private static final myInstance = new Singleton();
public static Singleton getInstance () { return myInstance; }
private Singleton() { ... }
// public methods
}
class Client {
public doSomething() {
Singleton singleton = Singleton.getInstance();
// use the singleton
}
}
Run Code Online (Sandbox Code Playgroud)
对于模拟,理想情况下,您需要一个可以自由子类化的接口,并通过依赖注入将其具体实现提供给其客户端.
您可以放松Singleton实现以使其可测试
setInstance方法以允许在单元测试中替换实例例:
interface Singleton {
private static final myInstance;
public static Singleton getInstance() { return myInstance; }
public static void setInstance(Singleton newInstance) { myInstance = newInstance; }
// public method declarations
}
// Used in production
class RealSingleton implements Singleton {
// public methods
}
// Used in unit tests
class FakeSingleton implements Singleton {
// public methods
}
class ClientTest {
private Singleton testSingleton = new FakeSingleton();
@Test
public void test() {
Singleton.setSingleton(testSingleton);
client.doSomething();
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,您只能通过破坏Singleton的"清洁度"来使您的Singleton使用代码单元可测试.最后,如果你能避免它,最好不要使用它.
更新:以下是Michael Feathers 对"有效使用遗留代码"的强制性参考.
并不是说单例模式本身就是纯粹的邪恶,而是即使在不适当的情况下也被大量过度使用。许多开发人员认为“哦,我可能只需要其中一个,所以让我们将其设为单例”。事实上,您应该考虑“我可能只需要其中一个,所以让我们在程序开始时构建一个,并在需要的地方传递引用。”
单身和测试的第一个问题与其说是因为单身,不如说是因为懒惰。由于获取单例的便利性,对单例对象的依赖通常直接嵌入到方法中,这使得将单例更改为具有相同接口但具有不同实现的另一个对象(例如,模拟对象)变得非常困难)。
代替:
void foo() {
Bar bar = Bar.getInstance();
// etc...
}
Run Code Online (Sandbox Code Playgroud)
更喜欢:
void foo(IBar bar) {
// etc...
}
Run Code Online (Sandbox Code Playgroud)
现在您可以foo使用bar您可以控制的模拟对象测试功能。您已删除依赖项,以便foo无需测试即可进行测试bar。
单例和测试的另一个问题是在测试单例本身时。单例(按设计)很难重建,因此例如您只能测试单例构造函数一次。单个实例也可能Bar在测试之间保留状态,导致成功或失败取决于测试运行的顺序。