不要在单元测试中使用单例模式
什么是单例模式?
单例模式是一种将类的实例化限制为单个实例的设计模式。当需要一个对象来协调整个系统中的操作时,例如自定义的加载指示器和服务,单例模式非常有用。通过这种设计模式,单例类确保它只被实例化一次,并且可以轻松访问该实例。
单元测试
单元测试是一种软件测试方法,它针对软件的各个单元或组件进行测试。单元测试会隔离一段代码并验证其正确性。单元测试可以针对单个函数、方法、过程、模块或对象。
为什么有些程序员在测试中使用单例模式?
有些程序员在测试中使用单例模式,目的是遵循DRY(不要重复自己)原则,即只实例化测试中将要使用的类一次。
那么,为什么不在单元测试中使用单例设计模式呢?
考虑到单元测试的最佳实践之一是测试应该是独立的,并且如果一个测试发生任何变化,其他测试不应该受到影响,单例模式阻碍了可测试性。
单例模式会在概念上独立的代码单元之间引入隐式依赖关系。这既会造成隐式依赖,又会引入不必要的单元耦合,因此存在诸多问题。
由于无法控制对象的创建,因此不能为每个测试使用干净的对象实例。
例子
在这个例子中,我将使用TypeScript和Jasmine测试框架。
State.ts 模型
export class State {
id: number;
status: string;
static instance:State;
constructor(args: {id:number,status: string}) {
this.id = args.id;
this.status = args.status;
}
static getInstance():State{
if(!State.instance){
State.instance = new State({id:1995,status:'LOADING'});
}
return State.instance;
}
}
state.spec.ts
import { State } from .....;
describe('State', () => {
let state:State = State.getInstance();
it('UT1 - should state id and status be correct', () => {
expect(state.id).toEqual(1995); // Will always pass
expect(state.status).toEqual('LOADING'); // Will always pass
state.status = 'STOP';
});
it('UT2 - should state status be LOADING', () => {
expect(state.status).toEqual('LOADING'); // It will fail whenever UT1 is run first because the state will always change to STOP
});
});
UT1(单元测试 1)和 UT2 共享同一个类实例的状态,导致单元测试之间不必要的耦合。
为防止这种情况发生,我们需要进行一些设置。
这些活动被称为设置和拆卸(清理),Jasmine 具有我们可以用来简化此过程的功能。
首先,我们需要将这段代码添加到State.ts 文件中。
static getCleanInstance():State{
return new State({id:1995,status:'LOADING'});
}
现在我们将这段代码添加到state.spec.ts
文件中 。在每个测试中,我们都会获得一个新的 state 实例,从而消除测试之间的依赖关系。
let state:State;
beforeEach(() => {
state = State.getCleanInstance();
});
结论
单例类不允许测试驱动开发(TDD)。TDD的法则。
单元测试依赖于测试之间的独立性,这样测试就可以按任意顺序运行,并且程序可以在每次执行单元测试之前设置为已知状态。一旦引入了具有可变状态的单例模式,这一点就很难实现。此外,这种全局可访问的持久状态会使代码更难理解,尤其是在多线程环境下。
单例模式可以解决你的很多问题。你知道你只需要一个实例。你可以保证这个实例在使用前已经初始化。它通过提供一个全局访问点来简化你的设计,但不要在你的单元测试中使用它。
干杯✌️,
Bc。