Unit Test
유닛 테스트란 프로그래밍 후 소스 코드의 프로그램의 기본 단위인 모듈이 의도대로 정확히 작동하는지 검증하는 절차이다. 즉, 소스 코드의 개별 단위를 테스트하여 사용할 준비가 되었는지 확인하는 테스트 방법이다.
1) 유닛 테스트를 하는 이유
- 프로그램이 커서 메모리가 많이 들고 다른 리소스(데이터베이스 등)이 막 필요한 경우 로컬 환경에서 코드를 실행시켜보기도 어렵고, 그걸 매번 수동으로 QA해보기도 어렵다. 그래서 유닛 테스트를 만들어서 빠르게 자신의 코드가 정상적으로 작동 하는지 확인 하는 것이 좋다.
- 종속성이 있는 다른 클래스들에서 버그가 나는 것을 방지하기 위해서이다.
- 예를 들어, A Class에서 버그가 나는데, 그것이 A class 내의 Common class 에서 발생하는 에러였다.
- 그래서 Common class의 에러 나는 부분을 수정하고, A class의 버그가 사라졌다. 그러나
- Common class는 C class에서도 사용 중이었다. 그랬더니 Common class를 수정 후 원래는 잘 작동하던 C class에서 버그가 생겨버렸다.
- 그래서 C class에서 발생하는 버그를 잡기 위해서 Common class를 다시 수정 해야 하고... 이러면 A class에서 다시 에러가 발생한 가능성이 높다.
- 하지만 이러한 상황에서 유닛 테스트를 한다면 Common class에 의존하는 다른 클래스드로 확인 가능하기 때문에 해당 에러를 잡기 더 쉬워진다.
하지만 프로그래밍 된 것은 수많은 모듈들이 정보를 주고 받으며 연결되어 있고, 어떤 경우 한 모듈을 테스트하려면 그 모듈과 연관된 상위, 하위 모듈 역시 잘 작동해야 해당 모듈을 정확히 테스트 할 수 있다. 그러나 상, 하위 모듈이 준비가 되어있지 않을 수도 있으므로 실제 상위 모듈 대신 가상의 역할을 하는 테스트 드라이버, 하위 모듈의 역할을 하는 테스트 스텁 을 사용하기도 한다.
2) 유닛 테스트를 하는 것의 장점
- 작은 각 단위가 잘 작동하는지 그때 그때 테스트해봄으로써 문제점을 빨리 발견하고 대응할 수 있으며, 그렇기 때문에 프로그래밍의 안정성이 높아질 뿐만 아니라 길게 보면 오히려 디버깅 시간을 단축시켜 개발 시간도 단축시킬 수 있다.
- 유닛 테스트를 통해서 기능은 바뀌지 않지만 코드를 정리해나가는 등의 리팩토링 작업을 할 때, 기존의 유닛 테스트를 통해 의도한 기능 여부를 알 수 있다. 즉, 지속적인 유닛 테스트 환경을 구축하면 어떠한 변화가 있더라도 코드와 그 실행이 의도대로 잘 되었는지를 확인, 검증 할 수 있다.
- 먼저 프로그램의 각 단위 부분을 검증하였기에 그에 대한 확신을 가질 수 있다. 그래서 그 단위들을 합쳐서 다시 검증하는 통합 테스트에서도 짐을 덜 수 있다.
3) 유닛 테스트의 조건
- 독립적이어야 하며, 어떤 테스트도 다른 테스트에 의존하지 않아야 한다.
- 그래서 Ajax, Axios, LocalStorage등 테스트 대상이 의존하는 것들을 대체하여서 딱 테스트하고 싶은 것만 격리 되어야 한다.
Jest
Jest란
Facebook에서 만든 테스팅 프레임 워크이다. 유닛 테스트를 학 위해 test case를 만들어서 코드가 의도대로 잘 돌아간는지 확읺할 수 있다.
※ 원래 mocha를 배운 적이 있는데, 크게 차이점은 없는 것 같고, mocha, should, supertest에 대한 내용은 해당 블로그에 잘 정리 되어 있는 듯 하다. 모카, 슈드, 슈퍼테스트에 대한 내용을 정리 해 둔 글
1) Jest 사용방법
- Jest 라이브러리 설치 npm install jest --save-dev (개발환경에서 test를 하는 것이므로 --save-dev로 설치해준다.)
- package.json에서 test script 변경 "scripts": {"test": "jest"} 또는 "scripts": {"test": "jest --watchAll"}. 이렇게 script를 작성해주면 npm test 할 때 jest가 알아서 test 파일을 찾아서 test를 진행한다.
- 테스트를 작성할 폴더 및 파일 기본 구조 생성
- test 폴더
- 단위 테스트를 위한 unit 폴더
- 단위 테스트 파일 [대상 이름].test.js
- 통한 테스트를 위한 integration 폴더
- 통합 테스트 파일 [대상 이름].test.init.js
- 단위 테스트를 위한 unit 폴더
- test 폴더
2) Jest 파일 구조
Jest의 파일 구조는 describe안에 test case들을 하나씩 넣는 것이다. describe는 여러 관련 테스트를 그룹화(블록) 하는 것이고, it(test)란 개별 테스트를 수행하는 것으로 각 테스트를 설명하고, 작성하는 것이다.
이 test는 expect와 matcher로 이루어지는데, expect 함수는 값을 테스트할 때 마다 사용되는 주어진 입력값에 대해서 리턴하는 값이 기대한는 값과 같은지를 비교하는데 사용된다. matcher는 여러 방법으로 test하도록 사용되는 것이며 expect에서 기대조건에 해당하는 함수를 말한다.
3) jest.fn()이란
Mock 함수를 생성하는 함수이다. 이 Mock 함수가 하는 일은 단위 테스트를 작성 할 때 해당 코드가 의존하는 부분을 가짜로 대체해준다.
예를 들어서 데이터베이스에 데이터를 저장하는 코드에 대해서 유닛 테스트를 할 경우,
Mock 함수 없이 실제 사용 중인 데이터베이스로 테스트를 진행한다면, 데이터를 전송하는 중에
- 데이터베이스 접속작업이 직접 이루어지므로 테스트 실행 속도 저하
- 테스트 코드보다 데이터베이스와 연결을 맺고 트랜잭션을 생성하고, 쿼리를 전송하는 코드가 더 길어질 수 있다.
- 만약 테스트 실행 순간 일시적으로 데이터베이스가 죽어 있었다면 해당 테스트는 코드 문제가 아니었는데도 실패
- 테스트 종료 후 데이터베이스에서 변경 데이터를 직접 원복하거나 트랜잭션을 rollback해줘야 하는 경우가 있음
등의 고려해야할 것이 너무 많다.
특히 더 문제인 것은 특정 기능만 고립하여서 독립적으로 test 해보아야 하는 유닛 테스트 조건과 맞지 않게 의존적인 부분이 생겨 영향을 받게 된다는 것이다.
그래서 Mock함수인 jest.fn()이 필요하다. 또한 jest.fn()이 생성한 가짜 함수는 이 함수에 어떤 일들이 발생했는지, 다른 코드들에 의해서 어떻게 호출되는지를 기억하기 때문ㅇ에 이 함수가 내부적으로 어떻게 사용되는지 검증하기도 쉽다.
4) jest.fn() 사용법
- Mock 함수 생성 const mockFunction = jest.fn()
- Mock함수 호출 (인자를 넘기는 것도 가능) mockFunction([인자])
- mockReturnValue 메소드를 이용해서 Mock 함수가 어떠한 결과값을 반환 할 지 직접 알려주기 mockFunction.mockReturnValue('가짜 함수 반환')
- toBeCalledTimes를 이용해서 Mock함수가 몇 번 호출되었고, toBeCalledWith를 이용해서 어떤 인자가 넘어왔는지 검증이 가능하다.
TDD
1) TDD 진행 순서
- 해야 할 일이 무엇인지 생각하기
- 유닛 테스트 작성
- 테스트에 대응하는 실제 코드 작성
예를 들어, products 테이블에 product 데이터를 하나 생성하는 create 작업에 대해 TDD를 진행한다고 해보자. 그러면
- 해야 할 일은? 데이터베이스 products 테이블에 product를 저장하는 것이므로 product를 저장하기 위한 함수를 먼저 생성한다.
- 유닛 테스트 작성
// test/unit/product.test.js
describe('Product Controller Create', () => {
it('should have a createProduct function', () => {
//product 데이터 생성을 위한 함수가 있는지 여부를 확인하기 위한 테스트 코드
expect(typeof productController.createProduct).toBe('function');
});
});
3. 테스트에 대응하는 실제 코드 작성
// controller/products.js
const productModel = require('../models/Product');
exports.createProduct = async (req, res, next) => {
};
2) Mock함수를 사용한 TDD 진행 순서
예를 들어, products 테이블에 product 데이터를 하나 생성하는 create 작업에 대해 TDD를 진행한다고 해보자. 그러면
- 해야 할 일은? createProduct 함수를 호출할 때 Product Model의 Create 메소드가 호출이 되는지를 확인
- 유닛 테스트 작성
it('should call Product.create', () => {
//createProduct 함수를 호출할 때 Product Model의 Create 메소드가 호출이 되는지를 확인 해주기 위한 테스트 코드
productController.createProduct(); //productController.createProduct이 호출 될 때,
expect(productModel.create).toBeCalled(); //productModel.create가 호출되는지
//그런데 이 테스트에서는 productModel에 의존적이어서는 안된다. 그래서 Mock함수를 사용한다.
})
여기서 productModel이라는 데이터베이스에는 테스트 코드가 의존적이면 안된다. 그래서 jest.fn()을 이용해서 Mock함수를 생성해야 한다.
//Mock함수 생성
productModel.create = jest.fn();
3. 테스트에 대응하는 실제 코드 작성
const productModel = require('../models/Product');
exports.createProduct = async (req, res, next) => {
productModel.create();
};
reference
따라하며 배우는 TDD 개발
'Dev > SW Engineering' 카테고리의 다른 글
[TDD] 통합 테스트(Integration Test) (0) | 2021.02.26 |
---|---|
[TDD] node-mocks-http 모듈, Jest의 beforeEach 사용 (0) | 2021.02.24 |
40. Authentication - OAuth 2.0 (0) | 2020.09.14 |
38. Authentication - Express cookie-parser (0) | 2020.09.13 |
37. Authentication - Session (0) | 2020.09.06 |