리팩토링에 있어 테스트 코드는 왜 중요할까?
모든 테스트를 완전히 자동화하고 그 결과까지 스스로 검사하게 만들자.
function sampleProvinceData() {
return {
name: "Asia",
producers: [
{name: "Byzantium", cost: 10, production: 9},
{name: "Attalia", cost: 12, production: 10},
{name: "Sinope", cost: 10, production: 6},
],
demand: 30,
price: 20
};
}
class Province {
constructor(doc) { // json을 인자로 받음
this._name = doc.name;
this._producers = [];
this._totalProduction = 0;
this._demand = doc.demand;
this._price = doc.price;
doc.producers.forEach(d => this.addProducer(new Producer(this, d)));
}
addProducer(arg) {
this._producers.push(arg);
this._totalProduction += arg.production;
}
get name() {
return this._name;
}
get producers() {
return this._producers.slice();
}
get totalProduction() {
return this._totalProduction;
}
set totalProduction(arg) {
this._totalProduction = arg;
}
get demand() {
return this._demand;
}
set demand(arg) {
this._demand = parseInt(arg);
}
// 숫자로 변환
get price() {
return this._price;
}
set price(arg) {
this._price = parseInt(arg);
}
// 숫자로 변환
get shortfall() {
return this._demand - this.totalProduction;
}
get profit() {
return this.demandValue - this.demandCost;
}
get demandValue() {
return this.satisfiedDemand * this.price;
}
get satisfiedDemand() {
return Math.min(this._demand, this.totalProduction);
}
get demandCost() {
let remainingDemand = this.demand;
let result = 0;
this.producers
.sort((a, b) => a.cost - b.cost)
.forEach(p => {
const contribution = Math.min(remainingDemand, p.production);
remainingDemand -= contribution;
result += contribution * p.cost;
});
return result;
}
}
class Producer {
constructor(aProvince, data) {
this._province = aProvince;
this._cost = data.cost;
this._name = data.name;
this._production = data.production || 0;
}
get name() {
return this._name;
}
get cost() {
return this._cost;
}
set cost(arg) {
this._cost = parseInt(arg);
}
get production() {
return this._production;
}
set production(amountStr) {
const amount = parseInt(amountStr);
const newProduction = Number.isNaN(amount) ? 0 : amount;
this._province.totalProduction += newProduction - this._production;
this._production = newProduction;
}
}
describe('province', function () {
it('shortfall', function () {
const asia = new Province(sampleProvinceData()); // 픽스쳐 설정
expect(asia.shortfall).equal(5); // 검증
});
});
완벽하게 만드느라 테스트를 수행하지 못하느니, 불완전한 테스트라도 작성해 실행하는게 낫다.
describe('province', function () {
it('shortfall', function () {
const asia = new Province(sampleProvinceData());
expect(asia.shortfall).equal(5);
});
it('profit', function () {
const asia = new Province(sampleProvinceData());
expect(asia.profit).equal(230);
});
});
const asia = new Province(sampleProvinceData());
이 부분이 중복되니까 말이다.describe('province', function () {
let asia;
beforeEach(function () {
asia = new Province(sampleProvinceData());
});
it('shortfall', function () {
expect(asia.shortfall).equal(5);
});
it('profit', function () {
expect(asia.profit).equal(230);
});
});
beforeEach
를 사용하면 된다.setUpWithError()
가 그역할을 한다.describe('province', function () {
let asia;
beforeEach(function () {
asia = new Province(sampleProvinceData());
});
it('shortfall', function () {
expect(asia.shortfall).equal(5);
});
it('profit', function () {
expect(asia.profit).equal(230);
});
it('change production', function () {
asia.producers[0].production = 20;
expect(asia.shortfall).equal(-6);
expect(asia.profit).equal(292);
});
});
describe('province', function () {
let asia;
beforeEach(function () {
asia = new Province(sampleProvinceData());
});
it('shortfall', function () {
expect(asia.shortfall).equal(5);
});
it('profit', function () {
expect(asia.profit).equal(230);
});
it('change production', function () {
asia.producers[0].production = 20;
expect(asia.shortfall).equal(-6);
expect(asia.profit).equal(292);
});
it('zero demand', function () { // 비었을 때를 확인한다.
asia.demand = 0;
expect(asia.shortfall).equal(-25);
expect(asia.profit).equal(0);
});
it('negative demand', function () { // 수요가 음수라면?
asia.demand = -1;
expect(asia.shortfall).equal(-26);
expect(asia.profit).equal(-10);
});
});
문제가 생길 가능성이 있는 경계 조건을 생각해보고 그 부분을 집중적으로 테스트하자.
describe('province', function () {
let asia;
beforeEach(function () {
asia = new Province(sampleProvinceData());
});
it('shortfall', function () {
expect(asia.shortfall).equal(5);
});
it('profit', function () {
expect(asia.profit).equal(230);
});
it('change production', function () {
asia.producers[0].production = 20;
expect(asia.shortfall).equal(-6);
expect(asia.profit).equal(292);
});
it('zero demand', function () { // 비었을 때를 확인한다.
asia.demand = 0;
expect(asia.shortfall).equal(-25);
expect(asia.profit).equal(0);
});
it('negative demand', function () { // 수요가 음수라면?
asia.demand = -1;
expect(asia.shortfall).equal(-26);
expect(asia.profit).equal(-10);
});
it('empty string demand', function () { // 수요가 비어있다면?
asia.demand = "";
expect(asia.shortfall).NaN;
expect(asia.profit).NaN;
});
});
describe('string for producers', function () { // 생산자 수 필드에 문자열을 대입
it('', function () {
const data = {
name: "String producers",
producers: "",
demand: 30,
price: 20
};
const prov = new Province(data);
expect(prov.shortfall).equal(0);
});
});
forEach
를 통해 결과값을 만들었었다.forEach
가 동작하지 않아 에러를 띄운다.9 passing (12ms)
1 failing
1) string for producers :
TypeError: doc.producers.forEach is not a function
어차피 모든 버그를 잡아낼 수는 없다 생각하여 테스트를 작성하지 않으면, 대다수의 버그를 잡을 기회를 놓치게 된다.
버그 리포트를 받으면 그 버그를 드러내는 단위 테스트 부터 작성하자.
이런 유용한 정보를 나눠주셔서 감사합니다.