테스트 작성 - ChoDragon9/posts GitHub Wiki

테스팅이 필요한 이유는 결함이 해결되지 않은 상태에서 시스템이 운영 단계 또는 사용자 사용 단계로 넘어간다면 장애가 발생하여 사용자들이 손실을 입고 나아가 회사전체 비즈니스에 영향을 줄 수 있기 때문이다.

위험 기반 테스트

테스팅의 경제성을 설명하는 격언 중 Too little testing is a crime, but too mush testing is a sin(테스팅을 적게하는 것은 범죄자지만 그렇다고 무조건 테스팅을 많이 하는 것이 반드시 미덕은 아니다.)라는 말이 있다. 완벽한 테스트는 불가능하다. 테스트를 수행할 자원은 제한적이다. 그렇다면 위험이 높은 기능 및 비기능 요구사항 테스트에 집중해야 한다.

테스트 케이스에 작성해야 하는 것

  • 가장 중요한 것
  • 실패 가능성이 있는 것
  • 위험 요소가 있는 것
  • 결함 발생 시 파급효과가 심각하고 이로 인한 막대한 손실이 발생하는 기능

위험 요소

  • 장애발생 가능성
    • 소스코드의 복잡도
    • 구현 난이도
    • 구현 크기(Line of Code)
    • 개발자 등급
  • 장애발생 시 비즈니스 영향도
    • 기능 요구사항을 구현한 후 장애가 발생했을 때, 비즈니스적으로 미치는 정도

명세 기반 테스트 기법

요구사항 정의서, 설계서 등 명세를 바탕으로 테스트 케이스를 도출, 실행하여 중대한 결함을 없음을 보장하는 기법이다.

명세 기반 테스트 기법 종류

동등분할

  • 테스트 대상 데이터의 구간을 일정 간격으로 분할하여 케이스를 구성한다.
  • 보통 데이터의 구간에 중간값을 대표값으로 사용한다.
const convertToGrade = score => {
  switch (true) {
    case score <= 100 && score > 80 :
      return 'A'
    case score <= 80 && score > 60 :
      return 'B'
    case score <= 60 && score > 40 :
      return 'C'
    case score <= 40 && score > 20 :
      return 'D'
    case score <= 20 && score > 0 :
      return 'E'
    default :
      return null
  } 
}
describe('convertToGrade', () => {
  it('A 학점', () => {
    // Given
    const score = 90
    // When
    const grade = convertToGrade(score)
    // Then
    expect(grade).toEqual('A')
  })
  it('B 학점', () => {
    // Given
    const score = 70
    // When
    const grade = convertToGrade(score)
    // Then
    expect(grade).toEqual('B')
  })
})

경계값 분석

  • 분기 또는 반복 구문의 경계 값을 기준으로 케이스를 구성한다.(min, min+, normal, max-, max)
const convertToGrade = score => {
  switch (true) {
    case score <= 100 && score > 80 :
      return 'A'
    case score <= 80 && score > 60 :
      return 'B'
    case score <= 60 && score > 40 :
      return 'C'
    case score <= 40 && score > 20 :
      return 'D'
    case score <= 20 && score > 0 :
      return 'E'
    default :
      return null
  } 
}
describe('convertToGrade', () => {
  it('A 학점 - min', () => {
    // Given
    const score = 81
    // When
    const grade = convertToGrade(score)
    // Then
    expect(grade).toEqual('A')
  })
  it('A 학점 - min+', () => {
    // Given
    const score = 82
    // When
    const grade = convertToGrade(score)
    // Then
    expect(grade).toEqual('A')
  })
  it('A 학점 - normal', () => {
    // Given
    const score = 90
    // When
    const grade = convertToGrade(score)
    // Then
    expect(grade).toEqual('A')
  })
  it('A 학점 - max-', () => {
    // Given
    const score = 99
    // When
    const grade = convertToGrade(score)
    // Then
    expect(grade).toEqual('A')
  })
  it('A 학점 - max', () => {
    // Given
    const score = 100
    // When
    const grade = convertToGrade(score)
    // Then
    expect(grade).toEqual('A')
  })
})

결정 테이블

  • 조건(입력 값)과 행위(결과 값)를 테이블로 구성하여 케이스 및 절차를 구성한다.
  • 프로세스 수행 중 요구되는 결정 또는 조건과 프로세스와 관련된 모든 동작을 기술한다.
const login = (email, password) => {
  if (!email) {
    return INVALID_EMAIL
  }
  if (!password) {
    return INVALID_PASSWORD
  }
  return LOGIN
}
describe('login', () => {
  const EMAIL = 'EMAIL'
  const PASSWORD = 'PASSWORD'
  it('이메일/비밀번호 유효하지 않을 때', () => {
    // Given
    const email = ''
    const password = ''
    // When
    const status = login(email, password)
    // Then
    expect(status).toEqual(INVALID_EMAIL)
  })
  it('이메일 유효하고, 비밀번호 유효하지 않을 때', () => {
    // Given
    const email = EMAIL
    const password = ''
    // When
    const status = login(email, password)
    // Then
    expect(status).toEqual(INVALID_PASSWORD)
  })
  it('이메일 유효하지 않고, 비밀번호 유효할 때', () => {
    // Given
    const email = ''
    const password = PASSWORD
    // When
    const status = login(email, password)
    // Then
    expect(status).toEqual(INVALID_EMAIL)
  })
  it('이메일/비밀번호 유효할 때', () => {
    // Given
    const email = EMAIL
    const password = PASSWORD
    // When
    const status = login(email, password)
    // Then
    expect(status).toEqual(LOGIN)
  })
})

조합

  • 테스트하는 데 필요한 값이 다른 파라미터의 값과 최소한 한 번씩은 조합을하여 케이스를 구성한다.
  • 대부분의 결함이 두 개 요소의 상호작용에 기인한다는 것에 착안하여, 두 개 요소의 모든 조합을 다룬다.
const convertToTitle = ({repeat: boolean, base: boolean, eq: boolean}) => {
  return {
    repeat: repeat ? '전체반복' : '한곡반복',
    base: base ? '설정' : '해제',
    eq: eq ? '설정' : '해제',
  }
}
describe('convertToTitle', () => {
  it('repeat: 전체반복, base: 설정, EQ: 설정', () => {
    // Given
    const repeat = true
    const base = true
    const eq = true
    // When
    const result = convertToTitle({repeat, base, eq})
    // Then
    expect(result.repeat).toEqual('전체반복')
    expect(result.base).toEqual('설정')
    expect(result.eq).toEqual('설정')
  })
  it('repeat: 전체반복, base: 해제, EQ: 해제', () => {
    // Given
    const repeat = true
    const base = false
    const eq = false
    // When
    const result = convertToTitle({repeat, base, eq})
    // Then
    expect(result.repeat).toEqual('전체반복')
    expect(result.base).toEqual('해제')
    expect(result.eq).toEqual('해제')
  })
  it('repeat: 한곡반복, base: 설정, EQ: 해제', () => {
    // Given
    const repeat = false
    const base = true
    const eq = false
    // When
    const result = convertToTitle({repeat, base, eq})
    // Then
    expect(result.repeat).toEqual('한곡반복')
    expect(result.base).toEqual('설정')
    expect(result.eq).toEqual('해제')
  })
  it('repeat: 한곡반복, base: 해제, EQ: 설정', () => {
    // Given
    const repeat = false
    const base = false
    const eq = true
    // When
    const result = convertToTitle({repeat, base, eq})
    // Then
    expect(result.repeat).toEqual('한곡반복')
    expect(result.base).toEqual('해제')
    expect(result.eq).toEqual('설정')
  })
})

상태전이

  • 시스템의 각 상태를 중심으로 케이스를 도출하고 전이 상태를 절차로 구성한다.
  • 객체의 상태를 구분하고 이벤트에 의해 어느 한 상태에서 다른 상태로 전이되는 경우의 수를 테스트 케이스로 구성
  • 상태 머신을 사용하는 UI 컴포넌트 테스트 시 유용
describe('MovieComponent', () => {
  it('영화관을 선택할 때', () => {
    // Given
    const component = mount(MovieComponent)
    // When
    component.trigger('click')
    // Then
    expect(component.hasClass('active')).toBe(true)
  })
})