模式组合的测试策略
模式组合的测试策略
设计模式在JavaScript开发中常被组合使用以解决复杂问题,但组合后的测试策略往往比单一模式更复杂。测试组合模式需要关注模式间的交互边界、依赖关系以及整体行为是否符合预期。
组合模式测试的核心挑战
当多个设计模式组合使用时,测试复杂度呈指数级增长。观察者模式与策略模式组合时,需要验证状态变化是否触发正确的策略执行:
// 观察者 + 策略组合示例
class Subject {
constructor() {
this.observers = [];
this.currentStrategy = null;
}
setStrategy(strategy) {
this.currentStrategy = strategy;
this.notify();
}
notify() {
this.observers.forEach(observer =>
observer.update(this.currentStrategy)
);
}
}
class ConcreteObserver {
update(strategy) {
strategy.execute();
}
}
const strategyA = {
execute: () => console.log('执行策略A')
};
const subject = new Subject();
subject.observers.push(new ConcreteObserver());
subject.setStrategy(strategyA); // 应触发策略执行
测试这种组合需要:
- 验证观察者通知机制
- 检查策略是否正确注入
- 确认策略执行时机
分层测试策略
单元测试层
对每个模式进行独立验证,确保基础功能正常。以下测试工厂方法与单例模式的组合:
// 工厂 + 单例组合
class Logger {
constructor() {
if (Logger.instance) return Logger.instance;
this.logs = [];
Logger.instance = this;
}
}
function createLogger(type) {
if (type === 'file') return new FileLogger();
return new Logger(); // 返回单例
}
// 测试用例
describe('Logger组合', () => {
it('应返回相同单例实例', () => {
const logger1 = createLogger();
const logger2 = createLogger();
expect(logger1).toBe(logger2);
});
});
集成测试层
验证模式间的交互,例如装饰器模式与适配器模式的组合:
// 装饰器 + 适配器组合
class OldService {
request() { return "旧数据格式"; }
}
class Adapter {
constructor(oldService) {
this.oldService = oldService;
}
newRequest() {
const data = this.oldService.request();
return { formatted: true, data };
}
}
function loggingDecorator(service) {
const original = service.newRequest;
service.newRequest = function() {
console.log('请求开始');
const result = original.apply(this);
console.log('请求结束');
return result;
};
return service;
}
// 集成测试
test('装饰器应记录适配器调用', () => {
const mockConsole = jest.spyOn(console, 'log');
const adapted = new Adapter(new OldService());
const decorated = loggingDecorator(adapted);
decorated.newRequest();
expect(mockConsole).toHaveBeenCalledWith('请求开始');
expect(mockConsole).toHaveBeenCalledWith('请求结束');
});
契约测试策略
当组合模式涉及多个模块时,需要明确模块间的交互契约。中介者模式与命令模式的组合示例:
// 中介者 + 命令组合
class Mediator {
constructor() {
this.commands = {};
}
register(commandName, command) {
this.commands[commandName] = command;
}
execute(commandName, payload) {
if (this.commands[commandName]) {
return this.commands[commandName].execute(payload);
}
throw new Error(`未注册命令: ${commandName}`);
}
}
class CreateUserCommand {
execute(payload) {
return `创建用户: ${payload.name}`;
}
}
// 契约测试
describe('中介者命令契约', () => {
let mediator;
beforeEach(() => {
mediator = new Mediator();
mediator.register('createUser', new CreateUserCommand());
});
it('应拒绝未注册命令', () => {
expect(() => mediator.execute('unknown')).toThrow();
});
it('应返回命令执行结果', () => {
const result = mediator.execute('createUser', { name: '测试用户' });
expect(result).toMatch(/创建用户/);
});
});
可视化测试技术
对于复杂的模式组合,可以采用快照测试记录组件状态。以下是用在状态模式与策略模式组合的React组件示例:
// 状态 + 策略组合组件
class PaymentState {
constructor(strategy) {
this.strategy = strategy;
}
render() {
return this.strategy.render();
}
}
const CreditCardStrategy = {
render: () => (
<div className="credit-card">
<input type="text" placeholder="卡号" />
</div>
)
};
// 测试用例
test('支付策略渲染快照', () => {
const state = new PaymentState(CreditCardStrategy);
const wrapper = render(state.render());
expect(wrapper.container).toMatchSnapshot();
});
测试金字塔实践
- 基础单元测试:覆盖每个模式的独立功能
// 单例模式基础测试
test('单例实例唯一性', () => {
const instance1 = new Singleton();
const instance2 = new Singleton();
expect(instance1).toBe(instance2);
});
- 交互测试:验证模式组合后的行为
// 观察者+工厂组合测试
test('工厂创建的观察者应接收通知', () => {
const observer = createObserver('email');
const subject = new Subject();
subject.subscribe(observer);
const mockHandler = jest.fn();
observer.handleUpdate = mockHandler;
subject.notify('test');
expect(mockHandler).toHaveBeenCalledWith('test');
});
- 端到端测试:验证完整工作流
// 状态机+策略+命令组合测试
test('完整订单流程', async () => {
const order = new Order();
order.setState(new NewState());
order.applyStrategy(new DiscountStrategy());
await order.process(new ProcessCommand());
expect(order.getState()).toBeInstanceOf(ProcessedState);
expect(order.getTotal()).toBeLessThan(originalTotal);
});
测试数据构造技巧
构建测试数据时可采用建造者模式简化复杂对象的创建:
// 测试数据建造者
class UserBuilder {
constructor() {
this.user = {
name: '默认用户',
roles: ['guest'],
preferences: {}
};
}
withAdminRole() {
this.user.roles.push('admin');
return this;
}
withPreference(key, value) {
this.user.preferences[key] = value;
return this;
}
build() {
return this.user;
}
}
// 在测试中使用
test('应允许管理员访问', () => {
const adminUser = new UserBuilder()
.withAdminRole()
.withPreference('theme', 'dark')
.build();
const result = checkAccess(adminUser);
expect(result).toBeTruthy();
});
异步组合模式测试
当组合模式涉及异步操作时,需要特殊处理。以下是发布/订阅模式与Promise的组合测试:
// 异步发布/订阅实现
class AsyncPubSub {
constructor() {
this.subscribers = [];
}
subscribe(fn) {
this.subscribers.push(fn);
}
async publish(data) {
const results = [];
for (const subscriber of this.subscribers) {
results.push(await subscriber(data));
}
return results;
}
}
// 异步测试
describe('AsyncPubSub', () => {
it('应顺序处理异步订阅者', async () => {
const pubsub = new AsyncPubSub();
const mockSubscriber1 = jest.fn(() =>
new Promise(res => setTimeout(() => res('结果1'), 10))
);
const mockSubscriber2 = jest.fn(() => Promise.resolve('结果2'));
pubsub.subscribe(mockSubscriber1);
pubsub.subscribe(mockSubscriber2);
const results = await pubsub.publish('测试数据');
expect(results).toEqual(['结果1', '结果2']);
expect(mockSubscriber1).toHaveBeenCalledBefore(mockSubscriber2);
});
});
测试覆盖率优化
针对模式组合的特殊情况,需要增加边界测试:
// 代理模式 + 缓存组合的边界测试
class CacheProxy {
constructor(service) {
this.service = service;
this.cache = new Map();
}
async getData(key) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const data = await this.service.fetch(key);
this.cache.set(key, data);
return data;
}
}
// 边界测试用例
describe('CacheProxy边界情况', () => {
it('应处理并发重复请求', async () => {
const mockService = {
fetch: jest.fn(() =>
new Promise(res => setTimeout(() => res('数据'), 100))
};
const proxy = new CacheProxy(mockService);
const [res1, res2] = await Promise.all([
proxy.getData('key'),
proxy.getData('key')
]);
expect(res1).toBe(res2);
expect(mockService.fetch).toHaveBeenCalledTimes(1);
});
it('应处理null缓存值', async () => {
const proxy = new CacheProxy({ fetch: () => null });
const result = await proxy.getData('nullKey');
expect(result).toBeNull();
});
});
模式组合的性能测试
某些模式组合可能引入性能问题,需要专门测试:
// 装饰器链性能测试
function benchmark() {
class CoreService {
heavyOperation() {
let total = 0;
for (let i = 0; i < 1e6; i++) {
total += Math.random();
}
return total;
}
}
function loggingDecorator(service) {
const original = service.heavyOperation;
service.heavyOperation = function() {
console.time('operation');
const result = original.apply(this);
console.timeEnd('operation');
return result;
};
return service;
}
function validationDecorator(service) {
const original = service.heavyOperation;
service.heavyOperation = function() {
if (Math.random() > 0.5) throw new Error('随机验证失败');
return original.apply(this);
};
return service;
}
// 测试装饰器叠加性能影响
test('装饰器叠加性能损耗', () => {
const raw = new CoreService();
const decorated = validationDecorator(loggingDecorator(new CoreService()));
const rawStart = performance.now();
raw.heavyOperation();
const rawDuration = performance.now() - rawStart;
const decoratedStart = performance.now();
try { decorated.heavyOperation(); } catch {}
const decoratedDuration = performance.now() - decoratedStart;
expect(decoratedDuration).toBeLessThan(rawDuration * 1.5);
});
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:重构工具与设计模式转换
下一篇:行为保持的重构方法