티스토리 뷰
해당 글은 github goldbergyoni 님의 javascript-testing-best-practices 를 번역하여 작성한 글 입니다. 번역 과정중 오류가 있을 수 있으니 댓글을 통해 피드백 주시면 감사하겠습니다.
들어가며
우리는 평소에 소프트웨어 구현만을 위한 코드(Production code)만을 신경쓰고 있습니다. 따라서 기존에 익숙하지 않은 테스트 코드를 작성하는 대에 어려움과 거부감을 느끼게 되고 많은 분들이 포기하게 됩니다. 하지만 익숙하지 않음에도 불구하고 테스트 코드에 대한 작은 투자는 큰 가치를 얻을 수 있습니다.
하지만 과한 테스트보다는 필요한 만큼만 테스트하고, 빠른 테스트를 유지하기 위해 노력해야 합니다. 때로는 민첩성과 단순성을 위해 테스트를 포기하고 신뢰성을 낮추는게 더 가치 있을 때도 있습니다. 해당 포스팅에서는 테스트를 잘 작성하는 방법과 잘 작성된 테스트 코드들을 소개합니다.
테스트의 3 요건
테스트 수행 결과는 코드가 요구사항을 만족하는지 알려주어야 합니다. 이는 테스트를 3가지 구성요소로 작성한다면 가장 잘 표현할 수 있습니다.
1. 무엇을 테스트 하는가?
2. 어떤 상황과 어떤 시나리오에서?
3.예상되는 결과는?
//1. unit under test
describe('Products Service', function() {
describe('Add new product', function() {
//2. scenario and 3. expectation
it('When no price is specified, then the product status is pending approval', ()=> {
const newProduct = new ProductService().add(...);
expect(newProduct.status).to.equal('pendingApproval');
});
});
});
테스트 구조 디자인 : AAA 패턴
테스트를 작성할 때 Arrange, Act, Assert (AAA) 3가지 섹션으로 나누어 디자인할 수 있습니다.
Arrange |
테스트를 위해 필요한 객체들을 생성, 설정 하는 등 테스트를 위한 조건을 예열하는 코드를 말합니다. 여기에는 테스트 대상의 인스턴스 생성, DB 레코드 추가, mocking / stubbing 등의 준비 코드가 포함됩니다. |
Act |
테스트중인 대상을 실행합니다. 일반적으로 한 줄의 코드로 이루어집니다. |
Assert |
Act 를 통해 수행하여 반환 받은 값이 기대치를 충족하는지 확인하는 섹션입니다. 이 역시 일반적으로 한 줄의 코드로 이루어집니다. |
describe('Customer classifier', () => {
test('When customer spent more than 500$, should be classified as premium', () => {
//Arrange
const customerToClassify = {spent:505, joined: new Date(), id:1}
const DBStub = sinon.stub(dataAccess, "getCustomer")
.reply({id:1, classification: 'regular'});
//Act
const receivedClassification = customerClassifier.classifyCustomer(customerToClassify);
//Assert
expect(receivedClassification).toMatch('premium');
});
});
BDD style assertions
테스트 코드를 선언적 스타일로 코딩하면 해당 코드를 읽는 사람이 의도를 파악하기 매우 쉽습니다. 테스트 코드의 요구사항을 조건문으로 분기하여 작성하면 이를 이해하기 위해 비교적 더 많은 시간이 필요합니다. 따라서 요구사항을 인간의 언어에 친화적인 선언형 BDD style 로 작성하는것이 좋습니다.
Javascript Test Framework인 Jest의 경우 BDD style로 작성할 수 있도록 expect API 를 제공하고 있습니다.
권장하지 않는 예제
test("When asking for an admin, ensure only ordered admins in results" , () => {
//assuming we've added here two admins "admin1", "admin2" and "user1"
const allAdmins = getUsers({adminOnly:true});
const admin1Found, adming2Found = false;
allAdmins.forEach(aSingleUser => {
if(aSingleUser === "user1"){
assert.notEqual(aSingleUser, "user1", "A user was found and not admin");
}
if(aSingleUser==="admin1"){
admin1Found = true;
}
if(aSingleUser==="admin2"){
admin2Found = true;
}
});
if(!admin1Found || !admin2Found ){
throw new Error("Not all admins were returned");
}
});
권장하는 예제
it("When asking for an admin, ensure only ordered admins in results" , () => {
//assuming we've added here two admins
const allAdmins = getUsers({adminOnly:true});
expect(allAdmins).to.include.ordered.members(["admin1" , "admin2"])
.but.not.include.ordered.members(["user1"]);
});
블랙박스 테스트 : public 메소드만 테스트 하라
코드의 내부를 테스트하는 것은 큰 오버헤드를 가져옵니다. 내부 동작까지 테스트하는 코드는 깨지기 쉽고 올바르게 동작하고 올바른 결과를 반환함에도 불구하고 내부적으로 어떻게 동작하는지를 테스트해야 합니다. 따라서 테스트를 작성하고 유지하는 대에 많은 시간을 소비하게 됩니다.
따라서 외부에 공개되는 public 메소드들만 테스트 하는 것을 권장합니다. public 메소드를 테스트할 때 마다 암묵적으로 private 구현체들까지 테스트되기 때문에 크리티컬한 문제가 있을 때에만 테스트가 실패하게 됩니다. 이러한 접근법은 행위, 행동 테스트라고도 합니다.
반대로 white box 접근법(테스트)의 경우에는 반드시 내부를 테스트해야 합니다. 이는 컴포넌트의 결과를 계획하는 것이 아닌 세부적인 동작 과정에 초점을 맞춰 테스트코드를 작성하게 됩니다.
권장하지 않는 예제
아래 코드는 이유없이 내부 함수를 테스트하고 있습니다.
class ProductService{
//this method is only used internally
//Change this name will make the tests fail
calculateVAT(priceWithoutVAT){
return {finalPrice: priceWithoutVAT * 1.2};
//Change the result format or key name above will make the tests fail
}
//public method
getPrice(productId){
const desiredProduct= DB.getProduct(productId);
finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice;
}
}
it("White-box test: When the internal methods get 0 vat, it return 0 response", async () => {
//There's no requirement to allow users to calculate the VAT, only show the final price. Nevertheless we falsely insist here to test the class internals
expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0);
});
원문 출처
'프로그래밍' 카테고리의 다른 글
웹사이트 스크래핑 라이브러리 Puppeteer 소개 (390) | 2019.08.15 |
---|---|
rxjs map operator 총 정리 (5384) | 2019.08.13 |
Ubuntu 에서 NodeJS 설치 및 HTTP 서버 구동 (416) | 2017.02.10 |
패키징 구조의 장단점 (403) | 2017.02.09 |
DataBase 회복기법 (376) | 2016.11.16 |