테스트가 필요한 부분과 명세 기반 테스트 방법
우선 테스트가 필요한 이유는 결함이 해결되지 않은 상태에서 서비스가 Production 단계로 배포된다면 장애가 발생하여 사용자들이 손실을 입고 나아가 회사 전체 비즈니스에 영향을 줄 수 있기 때문이다.
테스트가 필요한 부분
테스트의 경제성을 설명하는 격언 중 "테스트를 적게 하는 것은 죄지만 그렇다고 무조건 테스트를 많이 하는 것이 반드시 미덕은 아니다"라는 말이 있다. 테스트를 수행할 자원은 유한하므로 완벽한 테스트는 현실적으로 불가능하다. 그렇다면 위험이 높은 기능 및 비기능 요구사항을 집중적으로 테스트해야 한다.
테스트 케이스에 작성해야 하는 것
- 가장 중요한 것
- 실패 가능성이 있는 것
- 위험 요소가 있는 것
- 결함 발생 시 파급효과가 심각하고 이로 인한 막대한 손실이 발생하는 기능
위험 요소
- 장애 발생 가능성
- 소스코드의 복잡도
- 구현 난이도
- 구현 크기(Line of Code)
- 개발자 등급
- 장애 발생 시 비즈니스 영향도
- 기능 요구사항을 구현한 후 장애가 발생했을 때, 비즈니스적으로 미치는 정도
명세 기반 테스트 방법
명세 기반 테스트는 요구사항 정의 문서, 설계서 등 명세를 바탕으로 테스트 케이스를 도출, 실행하여 중대한 결함을 없음을 보장하는 기법이다.
명세 기반 테스트 기법 종류
동등분할
- 테스트 대상 데이터의 구간을 일정 간격으로 분할하여 케이스를 구성한다.
- 보통 데이터의 구간에 중간값을 대푯값으로 사용한다.
js
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
}
}
js
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)
js
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
}
}
js
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')
})
})
결정 테이블
- 조건(입력 값)과 행위(결괏값)를 테이블로 구성하여 케이스 및 절차를 구성한다.
- 프로세스 수행 중 요구되는 결정 또는 조건과 프로세스와 관련된 모든 동작을 기술한다.
js
const login = (email, password) => {
if (!email) {
return INVALID_EMAIL
}
if (!password) {
return INVALID_PASSWORD
}
return LOGIN
}
js
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)
})
})
조합
- 테스트하는 데 필요한 값이 다른 파라미터의 값과 최소한 한 번씩은 조합을 하여 케이스를 구성한다.
- 대부분의 결함이 두 개 요소의 상호작용에 기인한다는 것에 착안하여, 두 개 요소의 모든 조합을 다룬다.
js
const convertToTitle = ({repeat: boolean, base: boolean, eq: boolean}) => {
return {
repeat: repeat ? '전체반복' : '한곡반복',
base: base ? '설정' : '해제',
eq: eq ? '설정' : '해제',
}
}
js
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 컴포넌트 테스트 시 유용
js
describe('MovieComponent', () => {
it('영화관을 선택할 때', () => {
// Given
const component = mount(MovieComponent)
// When
component.trigger('click')
// Then
expect(component.hasClass('active')).toBe(true)
})
})