April 10, 2020
테스트 주도 개발하기
테스트는 말 그대로 작성한 코드가 잘 작동하는것을 검증하는 작업이다. 잘 작동하는지 확인하려면 구현한 기능을 직접 사용해보는 방법이 있다. 직접 클릭해보고 키보드로 입력해보고 잘 작동하는지 확인하는것. 그러나 프로젝트 규모가 커진다면 수동으로 일일이 확인하는 것은 번거로운 일이다. 그래서 테스트 자동화라는 작업을 하여 사람이 직접 확인을 하는것이 아니라 테스트 코드를 작성해서 테스트 시스템이 자동으로 확인을 해줄 수 있게 해준다. 이것이 테스트 자동화
다른사람들과 협업을 하게 되는 경우 테스트 코드를 작성하면 큰 도움을 준다 테스트 코드를 사용하면 우리가 프로젝트를 개발하는 과정에서 써내려가는 코드가 기존의 기능들이 실수로 망가뜨리게 되는일을 효과적으로 방지할 수 있다. 또한 리팩토링을 할 때 테스트 코드가 존재한다면 리팩토링 후 코드가 이전과 똑같이 작동하는지 검증하는게 매우 쉬워진다.
아주 조그마한 단위로 작성되는 테스트, 예시
프로젝트의 기능을 잘게 쪼개서 테스트를 하면 각 기능이 모두 잘 작동하는지 확인 할 수 있다.
기능들이 전체적으로 잘 작동하는지 확인하기 위해서 사용하는 것이 통합테스트, 예시
유닛테스트는 하나에 초점을 둔다면, 통합테스트는 여러 요소들을 고려하여 작성한다.
테스트가 개발을 이끌어나가는 형태의 개발론이며 선 테스트코드 작성 후 구현이다.
TDD를 진행하면서 테스트 케이스를 작성할때 주로 작은 단위로 만들어서 코드를 작성할 때 코드가 방대해지지 않고, 코드의 모듈화가 자연스럽게 잘 이루어지면서 개발이 진행된다.
TDD를 하면 테스트 커버리지가 자연스럽게 높아지고 결국 리팩토링도 쉬워지고 유지보수도 쉬워진다. 결국 프로젝트의 퀄리티가 높아짐
배열의 최댓값, 최소값, 평균, 중앙값, 최빈값을 구하는 함수들을 구현해보자
stats.js 와 stats.test.js를 만든다.
테스트케이스를 작성한다.
// stats.test.js
const stats = require('./stats')
describe('stats', () => {
it('gets maximum value', () => {
expect(stats.max([1, 2, 3, 4])).toBe(4)
})
})
max 함수 구현
// stats.js
exports.max = numbers => {
let result = numbers[0]
numbers.forEach(n => {
if (n > result) {
result = n
}
})
return result
}
이렇게 하면 테스트가 통과한다. 그 다음 어떻게 리팩토링을 할지 고민을 해본다 Math.max 함수사용
//stats.js
exports.max = numbers => Math.max(...numbers)
실패 케이스 작성
// stats.test.js
const stats = require('./stats')
describe('stats', () => {
it('gets maximum value', () => {
expect(stats.max([1, 2, 3, 4])).toBe(4)
})
it('gets minimum value', () => {
expect(stats.min([1, 2, 3, 4])).toBe(1)
})
})
테스트 케이스 통과시키기
exports.max = numbers => Math.max(...numbers)
exports.min = numbers => Math.min(...numbers)
평균값 구하는 테스트 케이스 작성
const stats = require('./stats')
describe('stats', () => {
it('gets maximum value', () => {
expect(stats.max([1, 2, 3, 4])).toBe(4)
})
it('gets minimum value', () => {
expect(stats.min([1, 2, 3, 4])).toBe(1)
})
it('gets average vale', () => {
expect(stats.avg([1, 2, 3, 4, 5])).toBe(3)
})
})
함수 구현
exports.max = numbers => Math.max(...numbers)
exports.min = numbers => Math.min(...numbers)
exports.avg = numbers => {
const sum = numbers.reduce((acc, current) => acc + current, 0)
return sum / numbers.length
}
중앙값 구하기 중앙값을 구현하기 전에 우선 배열을 정렬해야한다. 우선 정렬하는 함수를 위한 테스트 케이스 작성.
const stats = require('./stats')
describe('stats', () => {
it('gets maximum value', () => {
expect(stats.max([1, 2, 3, 4])).toBe(4)
})
it('gets minimum value', () => {
expect(stats.min([1, 2, 3, 4])).toBe(1)
})
it('gets average vale', () => {
expect(stats.avg([1, 2, 3, 4, 5])).toBe(3)
})
describe('median', () => {
it('sorts the array', () => {
expect(stats.sort([5, 4, 1, 2, 3])).toEqual([1, 2, 3, 4, 5])
})
})
})
describe 내부에서 또 describe를 쓸 수 있다. 단 it 내부에 또다른 it 이나 describe는 쓸 수 없다. 위 케이스에서 toBe가 아닌 toEqual을 사용했는데 이는 객체 또는 배열을 비교해야하는 상황에서 사용한다.
exports.max = numbers => Math.max(...numbers)
exports.min = numbers => Math.min(...numbers)
exports.avg = numbers => {
const sum = numbers.reduce((acc, current) => acc + current, 0)
return sum / numbers.length
}
exports.sort = numbers => numbers.sort((a, b) => a - b)
exports.median = numbers => {
const middle = Math.floor(numbers.length / 2)
if (numbers.length % 2) {
// 홀수
return numbers[middle]
}
return (numbers[middle - 1] + numbers[middle]) / 2
}
작성 후 리팩토링
exports.median = numbers => {
const { length } = numbers
const middle = Math.floor(length / 2)
return length % 2
? numbers[middle]
: numbers[middle - 1] + numbers[middle] / 2
}
최빈값은 배열에서 빈도가 가장 높은값. 이 값은 배열 안에 어떤 숫자들이 있느냐에 따라 형태가 다르다.
테스트 케이스 작성
describe('mode', () => {
it('has one mode', () => {
expect(stats.mode([1, 2, 2, 2, 3])).toBe(2)
})
it('has no mode', () => {
expect(stats.mode([1, 2, 3])).tobe(null)
})
it('has multiple mode', () => {
expect(stats.mode([1, 2, 2, 3, 3, 4])).toEqual([2, 3])
})
})
기능 구현
exports.mode = numbers => {
const counts = new Map()
numbers.forEach(n => {
const count = counts.get(n) || 0
counts.set(n, count + 1)
})
const maxCount = Math.max(...count.values())
const result = [...counts.keys()].find(
number => counts.get(number) === maxCount
)
return result
}
첫번째 테스트 케이스 통과
exports.mode = numbers => {
const counts = new Map()
numbers.forEach(n => {
const count = counts.get(n) || 0
counts.set(n, count + 1)
})
const maxCount = Math.max(...counts.values())
const modes = [...counts.keys()].filter(
number => counts.get(number) === maxCount
)
if (modes.length === numbers.length) {
// 최빈값이 없다
return null
}
if (modes.length > 1) {
// 최빈값이 여러개
return modes
}
// 최빈값이 하나
return modes[0]
}
모든 케이스 통과 참고 :https://velog.io/@velopert/TDD%EC%9D%98-%EC%86%8C%EA%B0%9C