阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 前端测试中的设计模式运用

前端测试中的设计模式运用

作者:陈川 阅读数:63240人阅读 分类: JavaScript

设计模式在前端测试中的价值

设计模式在前端测试中能显著提升代码的可维护性和可扩展性。测试代码与业务代码同样需要良好的架构设计,合理运用设计模式可以让测试用例更清晰、断言更精准、测试套件更易于管理。特别是在UI交互复杂、异步操作频繁的前端场景中,设计模式能有效解决测试代码重复、依赖混乱等问题。

工厂模式生成测试数据

测试数据构造是前端测试的常见痛点,工厂模式通过统一接口创建不同类型的数据对象。以用户信息生成为例:

class UserFactory {
  static createAdmin(overrides = {}) {
    return {
      id: faker.datatype.uuid(),
      name: faker.name.fullName(),
      role: 'admin',
      lastLogin: new Date(),
      ...overrides
    }
  }

  static createGuest(overrides = {}) {
    return {
      id: `guest_${faker.datatype.number()}`,
      name: 'Guest User',
      role: 'guest',
      ...overrides
    }
  }
}

// 测试用例中使用
test('admin should have all permissions', () => {
  const admin = UserFactory.createAdmin({ permissions: ['read', 'write'] })
  expect(validatePermissions(admin)).toBeTruthy()
})

这种模式避免了测试中重复构造相似对象,当数据结构变更时只需修改工厂类。结合faker等假数据库,可以快速生成逼真的测试数据。

策略模式处理多环境断言

不同测试环境可能需要不同的断言策略,策略模式将断言算法封装成独立对象:

const AssertionStrategies = {
  strict: {
    compare(actual, expected) {
      return actual === expected
    }
  },
  loose: {
    compare(actual, expected) {
      return actual == expected  // 双等号宽松比较
    }
  },
  arrayContains: {
    compare(actual, expected) {
      return expected.every(item => actual.includes(item))
    }
  }
}

function assertWithStrategy(strategyName, actual, expected) {
  const strategy = AssertionStrategies[strategyName]
  if (!strategy.compare(actual, expected)) {
    throw new Error(`Assertion failed with ${strategyName} strategy`)
  }
}

// 测试用例
test('API response should contain required fields', () => {
  const response = { id: 1, name: 'Test' }
  assertWithStrategy('arrayContains', Object.keys(response), ['id', 'name'])
})

观察者模式实现测试事件通知

在端到端测试中,观察者模式可以解耦测试动作与结果验证:

class TestEventBus {
  constructor() {
    this.subscribers = []
  }

  subscribe(callback) {
    this.subscribers.push(callback)
  }

  publish(event) {
    this.subscribers.forEach(sub => sub(event))
  }
}

// 在测试框架中
const eventBus = new TestEventBus()

// 页面对象触发事件
class LoginPage {
  constructor() {
    this.eventBus = eventBus
  }

  async login(username, password) {
    // ...执行登录操作
    this.eventBus.publish({
      type: 'LOGIN_ATTEMPT',
      payload: { username }
    })
  }
}

// 测试用例订阅事件
test('should track login attempts', async () => {
  const loginPage = new LoginPage()
  let receivedEvent = null
  
  eventBus.subscribe(event => {
    if (event.type === 'LOGIN_ATTEMPT') {
      receivedEvent = event
    }
  })

  await loginPage.login('test', 'pass123')
  expect(receivedEvent.payload.username).toBe('test')
})

装饰器模式增强测试功能

装饰器模式可以在不修改原有测试代码的情况下添加新功能:

function withRetry(maxAttempts = 3) {
  return function(target, name, descriptor) {
    const original = descriptor.value
    
    descriptor.value = async function(...args) {
      let lastError
      for (let i = 0; i < maxAttempts; i++) {
        try {
          return await original.apply(this, args)
        } catch (error) {
          lastError = error
          await new Promise(resolve => setTimeout(resolve, 1000 * i))
        }
      }
      throw lastError
    }
    
    return descriptor
  }
}

class FlakyTestSuite {
  @withRetry(5)
  async testUnstableAPI() {
    const result = await fetchUnstableAPI()
    expect(result.status).toBe(200)
  }
}

单例模式管理测试状态

跨测试用例共享状态时,单例模式确保状态一致性:

class TestState {
  constructor() {
    this.counter = 0
    this.events = []
  }

  static getInstance() {
    if (!TestState.instance) {
      TestState.instance = new TestState()
    }
    return TestState.instance
  }
}

// 测试用例A
test('should increment counter', () => {
  const state = TestState.getInstance()
  state.counter++
  expect(state.counter).toBe(1)
})

// 测试用例B
test('should maintain counter state', () => {
  const state = TestState.getInstance()
  expect(state.counter).toBe(1)  // 保持上一个测试修改后的状态
})

组合模式构建复杂测试流程

组合模式可以将简单测试组合成复杂测试树:

class TestComponent {
  constructor(name) {
    this.name = name
    this.children = []
  }

  add(component) {
    this.children.push(component)
  }

  async run() {
    console.log(`Running ${this.name}`)
    for (const child of this.children) {
      await child.run()
    }
  }
}

class TestCase extends TestComponent {
  async run() {
    console.log(`Executing test: ${this.name}`)
    // 实际测试逻辑...
  }
}

// 构建测试套件
const suite = new TestComponent('Main Test Suite')
const authSuite = new TestComponent('Authentication')

authSuite.add(new TestCase('Login with valid credentials'))
authSuite.add(new TestCase('Login with invalid password'))

suite.add(authSuite)
suite.add(new TestCase('Guest checkout flow'))

// 执行整个测试树
suite.run()

代理模式控制测试访问

代理模式可以拦截和管理对测试资源的访问:

class RealAPI {
  fetchUser(id) {
    // 实际API调用
  }
}

class APIProxy {
  constructor() {
    this.realAPI = new RealAPI()
    this.cache = new Map()
  }

  fetchUser(id) {
    if (this.cache.has(id)) {
      return this.cache.get(id)
    }
    const user = this.realAPI.fetchUser(id)
    this.cache.set(id, user)
    return user
  }
}

// 测试中使用代理
test('should cache API responses', async () => {
  const api = new APIProxy()
  const user1 = await api.fetchUser(1)  // 实际调用
  const user2 = await api.fetchUser(1)  // 从缓存获取
  expect(user1).toBe(user2)
})

状态模式处理测试生命周期

测试用例常需要根据不同状态执行不同操作:

class TestState {
  constructor(testCase) {
    this.testCase = testCase
  }

  run() {
    throw new Error('Abstract method')
  }
}

class PendingState extends TestState {
  run() {
    console.log(`${this.testCase.name} is pending`)
  }
}

class RunningState extends TestState {
  run() {
    console.log(`Executing ${this.testCase.name}`)
    try {
      this.testCase.execute()
      this.testCase.setState(new PassedState(this.testCase))
    } catch (error) {
      this.testCase.setState(new FailedState(this.testCase, error))
    }
  }
}

class TestCase {
  constructor(name) {
    this.name = name
    this.state = new PendingState(this)
  }

  setState(newState) {
    this.state = newState
  }

  run() {
    this.state.run()
  }
}

模板方法模式定义测试结构

模板方法模式可以规范测试用例的基本结构:

class TestTemplate {
  beforeAll() {}
  beforeEach() {}
  afterEach() {}
  afterAll() {}

  run() {
    this.beforeAll()
    try {
      this.testCases.forEach(test => {
        this.beforeEach()
        test()
        this.afterEach()
      })
    } finally {
      this.afterAll()
    }
  }
}

class UserTests extends TestTemplate {
  beforeAll() {
    this.db = setupTestDatabase()
  }

  testCases = [
    () => {
      const user = createUser()
      expect(user.id).toBeDefined()
    },
    () => {
      const users = listUsers()
      expect(users.length).toBe(0)
    }
  ]
}

new UserTests().run()

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌