#frontend

2021.Sep.30

프론트엔드 코드 커버리지


개발자는 숫자를 좋아합니다. 꼭 컴퓨터와 수학의 역사가 밀접한 관련이 있기 때문만은 아닙니다. 코드는 계측과 수량화가 매우 어렵기 때문입니다. 테스트코드(이하 TC)도 크게 다르지 않습니다. 어디서 어디까지를 테스트 범위로 잡아야할지도 문제고, 테스트 통과만으로도 소프트웨어의 무결성을 증명하기는 무척 어렵습니다.

코드 커버리지가 이런 난관에 큰 도움이 됩니다. 코드 커버리지는 테스트코드에 수량화된 척도를 제시하는 도구입니다.

원리

원리는 간단합니다. 모든 코드에 번호를 부여하고, 테스트가 해당 코드를 지나갈 때마다 번호를 지워나갑니다.

1 if (a) {
2 do something
3 }
4 print('finish')

만약 a == false 라면 1번과 4번이 실행과 동시에 커버된 코드로 인정되면서 지워지고, 2번과 3번이 남습니다.

// a == false
1 if (a) { // 커버 됨
2 do something // 커버 안됨
3 } // 커버 안됨
4 print('finish') // 커버 됨

총 4 줄의 코드중에 2줄의 코드가 커버되었기 때문에, 코드 커버리지 완성도는 2/4=50%가 됩니다. 그렇기 때문에 테스트 코드에서 a == true 인 경우를 제시하면, 코드 커버리지의 완성도는 100%가 됩니다.

잠깐 생각해보면 알 수 있지만 코드 커버리지의 100%가 코드의 안전성이 100%라는 뜻은 절대 아닙니다. 다만 현재 작성한 TC가 이전에 작성한 TC에 비해서 얼마나 넓은 범위를 커버하는지를 비교할 수는 있습니다. 코드 커버리지 50%일 때보다, 코드 커버리지 60% 일 때가 상대적으로 TC의 방어범위가 높아져 더욱 안전해 졌다고 말할 수 있게 됩니다.

프론트엔드 코드 커버리지

위에서 본 것과 유사한 형태의 유닛 테스트는 텍스트 자료 기반의 백엔드에 더욱 알맞습니다. 그러나 프론트엔드도 테스트에 있어서 정량적인 지표가 있다면 작업이 좀 더 유리해지는 것은 자명합니다. 그래서 불완전하고 불편한 환경에도 불구하고 코드 커버리지를 어떤 식으로든 사용할 수 있게 하는 각종 도구들이 있습니다. 현재 유명한 개발스택 위주로 정리하면 이와 같은 도구들이 있습니다.

프론트엔드 코드 커버리지 통합하기

문제는 코드 커버리지 환경이 이와 같이 제각각이기 때문에, 별도의 통합 과정을 거쳐야한다는 것입니다.

그나마 다행이라고 한다면, 프론트엔드-자바스크립트(이하 JS)환경에서는 대부분의 코드 커버리지가 노드용 코드 커버리지 툴인 istanbul 을 기반으로 하고 있어서, 하나로 내용을 머지하기 용이합니다.

istanbul(이하 이스탄불)의 CLI라고할 수 있는 nyc를 이용하여 서로 다른 코드 커버리지를 머지할 수 있습니다.

nyc merge $TARGET_FOLDER $OUTPUT

필요하지 않은 코드 커버리지 생략하기

프론트엔드의 경우 유닛테스트를 통해 확인하기 어려운 코드들이 있습니다. 노드와 브라우저의 환경이 완벽하게 일치하지 않기 때문입니다. 그 뿐만 아니라 너무나 내용이 명확하여 별도의 테스트가 필요하지 않은 경우도 있습니다. 이 경우 eslint에서 린팅을 생략하듯이 특정 코드에만 커버리지를 생략할 수 있습니다.

// 내용이 너무 명확하여, 커버리지 생략
/* istanbul ignore next */
const makeResult: makeResultArg = (result) => {
return { errorCode: '', error: null, result };
};

// 파일 blob을 다운로드하도록 해야하는데,
// 해당 내용을 jest 내부의 node환경에서 100% 재현하기 어렵기 때문에 커버리지 생략
/* istanbul ignore next */
saveJson(exportObj, 'settings.json');

// env 파일의 경우도 내용이 너무 명확하여, 커버리지 생략
/* istanbul ignore file */
// tiny wrapper with default env vars
const NODE_ENV = process.env.NODE_ENV || 'development';
module.exports = {
NODE_ENV,
PORT: process.env.PORT || 3000,
isBuildPerformanceLog: process.env.BUILD_PERFORMANCE_LOG === 'true',
isCypress: process.env.CYPRESS_MODE === 'true',
isDevelop: NODE_ENV === 'development',
};

보고서

프론트엔드 코드 커버리지는 도구에 관계없이 대체로 .json 또는 lcov.info 파일로 생성됩니다. lcov란 GNU 컴파일러(GCC)용 코드 커버리지 도구인 gcov(Gnu code COVerage)의 리눅스(Linux code COVerage) 버전입니다. 아래는 샘플 프로젝트에서 작성한 lcov 파일입니다.

# lcov.info
TN:
SF:src/constants/blockMode.js
FNF:0
FNH:0
DA:1,4
DA:2,4
LF:2
LH:2
BRF:0
BRH:0
# ...

lcov 내부에는 알 수 없는 문자열과 숫자들 밖에 없습니다. 일반적인 코드 커버리지 리포트는 코드 커버리지를 사용하는 각종 도구간의 호환성을 보장하기 위해, 기계적인 포맷으로 제공됩니다. 사람이 읽기 어려울 수밖에 없습니다. 그래서 보통은 html이나 웹페이지 형태의 리포트를 별도로 제공합니다.

코드 커버리지 보고서 둘러보기

Jest로 코드 커버리지 리포트를 작성할 경우, 내부적으로 이스탄불의 코드 커버리지 도구를 사용하여 html 보고서를 작성해 줍니다. 보고서 내용은 아래와 같습니다.

어떤 파일이 얼마나 많은 구문에 대하여 TC가 작성되었고, 놓친 분기와 함수가 몇 개인지 등의 자료를 상세히 설명해 줍니다. 각 파일을 클릭하면 코드 커버리지를 실제 코드와 1:1 대응하여 보여줍니다.

빨간색은 커버되지 않은 내용입니다. 오른쪽에 표시된 숫자는 해당 코드를 실행한 숫자입니다.

CCaaS: Code Coverage as a Service

지금까지 많은 서비스들이 클라우드화 된 것을 관찰했습니다. Visual Regression Test를 클라우드화한 것(Percy)을 보기도 했고, 현지화를 서비스화한 LaaS 도 있었습니다. 잠깐 살펴보면 코드 커버리지도 클라우드 서비스화할 수 있는 여지가 매우 많습니다.

이를 해결하기 위해 통합 코드 커버리지 저장소 - 뷰어를 제공하는 서비스를 CCaaS(Code Coverage as a Service)라고 합니다.

2021년 기준으로 아래와 같은 CCaaS들이 있습니다.

관련하여 블로그 내용을 참조하여 비교적 가성비라고 할 수 있는 Coveralls(이하 커버올즈)를 선택했습니다.

CICD + CCaaS

CCaaS를 찾는 사용자들이 많아지면서 CICD 서비스에도 CCaaS 도입을 도와주는 다양한 도구들이 있습니다. 진행하고 있는 사이드프로젝트인 Fokus 는 CICD 서비스로 Drone(이하 드론) 을 사용하고 있는데, 드론에도 커버올즈 세팅을 단번에 도와줄 수 있는 플러그인이 있었습니다. CICD용 yaml에 도커 이미지와 환경변수를 전달하여 커버올즈까지 업로드하는 파이프라인을 금방 완성할 수 있습니다.

# http://plugins.drone.io/drone-plugins/drone-coveralls/
kind: pipeline
type: docker
name: "unit-test"

steps:
# 우선 유닛 테스트를 실시한다
- name: "jest unit test"
image: node:12.20.2
commands:
- yarn install
- yarn test-unit-ci
# 유닛 테스트가 통과할 경우, 작성된 lcov.info파일을 coveralls용 토큰을 이용하여
# coveralls에 업로드한다
- name: "coveralls"
image: lizheming/drone-coveralls
environment:
COVERALLS_REPO_TOKEN:
from_secret: coveralls_token
settings:
files:
- ./coverage/lcov.info
token:
from_secret: coveralls_token

예제

커버올즈의 장점

커버올즈의 단점

마무리

사이드 프로젝트에 코드 커버리지를 구성하며 느낀 문제점은 이러했습니다

사이드 프로젝트에 코드 커버리지를 세팅하며 느낀 장점들은 아래와 같습니다.

결론적으로는 초기 세팅을 위해 다소 자원이 소모되지만, TC의 질을 개선하기 위해서 코드 커버리지를 앞으로 새로 시작할 프로젝트에도 자주 도입할 것 같습니다. 다만 커버올즈는 여러가지 미비한 점들이 확인되어, 추후에는 Codecov를 사용할 것 같습니다.