发布于 2026-01-06 5 阅读
0

不要在单元测试中使用单例模式

不要在单元测试中使用单例模式

什么是单例模式?

单例模式是一种将类的实例化限制为单个实例的设计模式。当需要一个对象来协调整个系统中的操作时,例如自定义的加载指示器和服务,单例模式非常有用。通过这种设计模式,单例类确保它只被实例化一次,并且可以轻松访问该实例。

单元测试

单元测试是一种软件测试方法,它针对软件的各个单元或组件进行测试。单元测试会隔离一段代码并验证其正确性。单元测试可以针对单个函数、方法、过程、模块或对象。

为什么有些程序员在测试中使用单例模式?

有些程序员在测试中使用单例模式,目的是遵循DRY(不要重复自己)原则,即只实例化测试中将要使用的类一次。

那么,为什么不在单元测试中使用单例设计模式呢?

考虑到单元测试的最佳实践之一是测试应该是独立的,并且如果一个测试发生任何变化,其他测试不应该受到影响,单例模式阻碍了可测试性。

单例模式会在概念上独立的代码单元之间引入隐式依赖关系。这既会造成隐式依赖,又会引入不必要的单元耦合,因此存在诸多问题。

由于无法控制对象的创建,因此不能为每个测试使用干净的对象实例。

例子

在这个例子中,我将使用TypeScriptJasmine测试框架。

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  });

});
Enter fullscreen mode Exit fullscreen mode

UT1(单元测试 1)和 UT2 共享同一个类实例的状态,导致单元测试之间不必要的耦合

为防止这种情况发生,我们需要进行一些设置。

这些活动被称为设置和拆卸(清理),Jasmine 具有我们可以用来简化此过程的功能。

首先,我们需要将这段代码添加到State.ts 文件中。

static getCleanInstance():State{
    return new State({id:1995,status:'LOADING'});
 }
Enter fullscreen mode Exit fullscreen mode

现在我们将这段代码添加到state.spec.ts
文件中 。在每个测试中,我们都会获得一个新的 state 实例,从而消除测试之间的依赖关系。

let state:State;
  beforeEach(() => {
     state = State.getCleanInstance();
  });
Enter fullscreen mode Exit fullscreen mode

结论

单例类不允许测试驱动开发(TDD)。TDD的法则

单元测试依赖于测试之间的独立性,这样测试就可以按任意顺序运行,并且程序可以在每次执行单元测试之前设置为已知状态。一旦引入了具有可变状态的单例模式,这一点就很难实现。此外,这种全局可访问的持久状态会使代码更难理解,尤其是在多线程环境下。

单例模式可以解决你的很多问题。你知道你只需要一个实例。你可以保证这个实例在使用前已经初始化。它通过提供一个全局访问点来简化你的设计,但不要在你的单元测试中使用它。

干杯✌️,
Bc。

文章来源:https://dev.to/bacarpereira/don-t-use-singleton-pattern-in-your-unit-tests-8p7