테스트 커버리지란 시스템이나 소프트웨어의 테스트를 논할 때 얼마나 많은 라인(브랜치, 구문(statements) 등)이 테스트에 의해 커버되었는지에 대한 지표입니다.
즉, 수행한 테스트가 테스트의 대상을 얼마나 커버했는지를 나타냅니다.
테스트의 충분함을 측정하는데, 테스트 대상의 전체 범위에서 테스트를 수행한 범위로 측정하게 됩니다.
10–30%는 빌드 정확성에 대해 판단하기에는 분명히 너무 낮습니다. 그렇다고 100%가 달성되면 완벽한 소프트웨어는 아닙니다. 대부분은 대다수의 어플리케이션을 만족하기 위해서 80%면 적절하다고 합니다.
코드 커버리지는 구문, 조건, 결정 구조로 이루어져 있으며 코드의 구조를 얼마나 커버했는지에 따라 측정 기준을 나누게됩니다.
구문 커버리지(%) = (수행된 구문의 수 / 전체 구문의 수) x 100
테스트에 의해 모든 문장들이 최소 한 번은 실행될 수 있는 입력 데이터를 선정하는 기준입니다. 특정 구문을 테스트하는 테스트 케이스를 도출하는 과정으로 Line 커버리지라고도 부르며 코드가 한 줄 이상 실행됐을 경우 조건을 충족하게 됩니다.
결정 커버리지(%) = (수행된 분기의 수 / 전체 분기의 수) x 100
조건문이 True/False가 되는 조건이 실행되는 것을 측정하게 됩니다. Branch 커버리지라고 부릅니다. 테스트 케이스의 최소 개수는 2개로 조건이나 조건/결정 커버리지에 비해 약한편입니다. 또한 테스트 깊이가 깊을 수록 제품의 커버리지는 높아지나 테스트 케이스 수가 기하급수적으로 많아져 비용과 시간, 리소스 등이 많이 소요됩니다.
조건 커버리지(%) = (수행된 조건의 수 / 전체 조건의 수) x 100
전체 조건의 결과와 관계없이 각 개별 조건이 True/False 모두 갖도록 조합하는 것 입니다. 결정 커버리지 보다 강력한 형태의 커버리지입니다. 조건 커버리지는 모든 결정 커버리지에 대한 보장을 주지는 않습니다.
조건/결정 커버리지(%) = {(수행된 분기의 수 + 수행된 조건의 수) / (전체 분기의 수 + 전체 조건의 수)} x 100
결정 커버리지와 조건 커버리지를 포함하는 커버리지로 전체 조건의 결과가 True/False 한번을 갖도록 각 개별 조건을 조합합니다. 이때 각 개별 조건식도 True/False 한번을 모두 갖도록 개별 조건식을 조합하게됩니다.
변경 조건/결정 커버리지(%) = (수행된 MC/DC 조건의 수 / 전체 조건의 수) x 100
각 개별 조건식이 다른 개별 조건식에 무관하게 전체 조건식의 결과에 독립적으로 영향을 주도록합니다. 결정 커버리지와 조건/결정 커버리지를 향상시킨 것으로 조건/결정 커버리지보다 강력합니다.
다중 조건 커버리지(%) = (수행된 조건식 조합의 수 / 전체 조건식 조합의 수) x 100
결정 포인트 내에 있는 모든 개별 조건식의 모든 가능한 논리적 조합을 고려하게 됩니다. 가장 강력한 커버리지이며 논리적 수준이 100% 커버리지를 보장합니다.
단일 기능에서 입력 시점부터 종료시점까지의 여러 분기들 중 하나의 흐름을 의미합니다. 모든 경우의 수를 고려하므로 철저한 테스트가 가능하지만, 테스트 케이스 설계에 엄청난 공수가 필요하게 됩니다. 또한 데이터 간의 상호 배반적 관계로 수행이 불가능한 경로가 다수 존재합니다.
아래 명령어를 입력하면 jest에서 코드 커버리지를 확인할 수 있습니다. 만약 테스트가 되지 않은 코드가 있다면 Uncovered Line에 몇 번째 줄인지 표시해 줍니다.
$ npm jest --coverage
$ yarn test --coverage
"script": {
...
"test": "jest --coverage",
...
},
$ npm test
$ yarn test
적용 범위 임계값을 갖도록 CI 환경을 설정해 커버리지 임계치를 설정하여 그 기준에 미치지 못하면 빌드를 중지할 수 있습니다. 아래 코드와 같이 구성 요소별로 임계값을 구성할 수도 있습니다.
{
"ject": {
"coverageThreshold": {
"global": {
"lines": 80
},
"./src/vomponents/finance": {
"lines": 95
}
}
}
}
이렇게 함으로써 개발자들이 커버리지를 올리거나 유지하도록 압박할 수 있습니다. 말한대로 커버리지는 오직 하나의 양적 지표일 뿐 테스트의 견고성을 나타내기에는 충분하지 않습니다.
테스트 커버리지에서 코드의 80%가 테스트되었다고 하더라도 누락된 부분을 결정하기가 여려울 수 있습니다. 즉, 보고서를 생산해서 응용 프로그램이 생각대로 작동하는지 여부를 확인할 필요가 있습니다.
npm jest folder/name --coverage --coverageDirectory='coverage'
yarn test folder/name --coverage --coverageDirectory='coverage'
위 명령어로 지정폴더에 HTML 보고서를 생성합니다.
브라우저에서 index.html
파일을 열면 단위 테스트가 이루어지지 않은 라인을 확인 할 수 있습니다.
뮤테이션(%; MS) = {죽은 뮤테이션 수 / (총 뮤테이션 수 - 동등 뮤턴트 수)} x 100
소스코드에 하나의 작은 변화를 발생시킨 것을 말합니다. 결함 기반 테스팅 기법 중 하나로 기존 작성된 테스트 케이스들의 테스트 적정성을 평가하는 기법입니다. 즉, 의도적으로 삽입된 오류들을 식별할 수 있는지 확인합니다.
원본 프로그램을 일정한 변경 규칙에 따라 변경하여 여러 변형된 프로그램 버전을 생성합니다. 원본 프로그램의 구문을 변경하는 사전 정의된 변경 규칙은 뮤테이션 오퍼레이터(Mutation operator)라고 하며, 뮤테이션 오퍼레이터를 적용하여 생성된 원본과 약간 달라진 프로그램 버전을 뮤턴트(mutant)라고 합니다. 뮤턴트는 잘못 프로그래밍된 오류 버전을 나타냅니다.
테스트 케이스를 통해 원본 프로그램과 뮤턴트의 실행 결과를 비교합니다.
뮤턴트의 실행 겨로가가 원본 프로그램의 실행 결과와 다르다면 현 테스트 케이스로 뮤턴트 프로그램을 구해낼 수 있습니다. 즉, 오류를 식별해 낼 수 있다는 것이고 이 경우 해당 뮤턴트 프로그램은 죽은(Killed) 것으로 처리되어 관심대상에서 제외됩니다.
만약, 테스트 결과가 원본 프로그램과 뮤턴트가 동일하다면 해당 뮤턴트를 라이브 뮤턴트(live mutant) 라고 부릅니다. 라이트 뮤턴트는 kill하기충분하지 않다는 것으로 샌규 테스트 케이스를 추가로 작성하여 실행을 반복합니다.
뮤테이션 테스팅의 경우 생성된 무수히 많은 수의 뮤턴트를 테스트 해야함으로 높은 컴퓨팅 비용이 발생하게 됩니다. 따라서 많은 테스트 케이스로 실행과 결과를 확인으로 인해 컴퓨팅 리소스가 소요되며 성능 문제가 발생할 수 있습니다.
뿐만 아니라 각 테스트 케이스가 정확한지 확인하기 위해서는 사람의 개입이 필요하므로 높은 인적 비용이 발생하게 됩니다.
뮤테이션 테스팅은 테스트 케이스를 작성하는 것이 지나친 노력이 들어가고, 자동화 도구가 개발되지 않아 현재까지도 실무에서 폭넓게 사용은 못하고 있습니다.
자바스크립트 프로젝트의 경우 ESLint를 사용해 코드에 문제가 없는지 검사를 하고 있습니다.
린트(Lint)는 소스 코드에 문제가 잇는지 탐색하는 작업을 의미하며, 린터(linter)는 이 작업을 도와주는 소프트웨어 도구를 의미합니다.
자바스크립트와 같이 컴파일 과정이 없는 인터프리터 언어의 경우, 린터 작업을 통해 사전에 에러를 최대한 잡아주는 것이 중요합니다.
최근 ESLint라는 린터가 개발자들 사이에서 많은 인기를 끌고 있으며 많은 기업들이 사용하고 있습니다.
$ npm init -y
$ npm i -D eslint
{
"eslintConfig": {
"extends": ["react-app", "react-app/jest"]
}
}
pakage.json으로 ESLint 설정을 하는 것은 소규모 프로젝트의 경우는 괜찮지만 팀 단위의 큰 프로젝트의 경우 프로젝트가 커짐에 따라 유지보수가 힘들어지기 때문에 사용파악이 어렵기 때문에 추천하지 않는 방법입니다.
module.exports = {
"env": {
"es6": true,
"node": true,
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly",
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 11,
},
"rules": {
"no-console": "error",
"import/prefer-default-export": "off"
},
};
env
: ESLint는 기본적으로 미리 선언하지 않고 접근하는 변수에 대해서 오류를 내기 때문에 실행 환경에서 기본적으로 제공되는 전역 객체에 대해서 설정을 통해 알려줘야 합니다.eslint:
: 자바스크립트 코드를 린트할 때 extends
옵션을 통해 기업이 공개해놓은 설정을 기반으로 설정을 활용할 수 있습니다. 보통 airbnb
와 pretter
의 설정을 많이 확장해서 사용합니다.rules
: 옵션으로 명시된 규칙을 extends
옵션을 통해서 가져온 규칙보다 우선으로 처리해줍니다. 직접 관리해야하는 설정이 늘어나는 단점이 있습니다.parserOptions
: ESLint는 기본적으로 순수한 자바스크립트 코드만 이해할 수 있기 때문에 자바스크립트의 확장 문법이나 최신 문법으로 작성한 코드를 린트하기 위해서는 그에 상응하는 파서를 사용하도록 설정해줍니다.참고