Test Code
Test Code 란?
- 테스트 코드란 소프트웨어의 제품 또는 서비스의 품질을 확인하거나 소프트웨어의 버그를 찾을 때 작성하는 코드를 의미
- 다시 말해 제품이 예상하는 대로 동작 하는지 확인하는 것
- Test Code 작성의 장점
- 개발 과정 중 예상치 못한 문제를 미리 발견할 수 있음
- 작성한 코드가 의도한 대로 작동하는지 검증할 수 있음
- 코드 변경에 대한 사이드 이펙트를 줄이는 예방책
- 코드 수정이 필요한 상황에서 유연하고 안정적인 대응을 할 수 있도록 해줌
- 코드의 모듈화를 한번 더 고민하게 해줌
- 코드 변경 시 변경 부분으로 인한 영향도를 쉽게 파악할 수 있음
- 코드 리팩토링 시 기능 구현이 동일하게 되었다는 판단을 내릴 수 있음
- 테스트 코드를 통해 동작하는 방식 및 결과 확인 가능
Tip! Test 의 종류
- 테스트 대상 범위나 성격에 따라 크게 세 가지로 구분
UI Test
, Integration Test
, Unit Test
Unit Test
: 단위 테스트
- 소형 테스트에 속하는 테스트
- 클래스 범주 내에서 작은 단위 (함수) 의 기능에 대한 유효성을 검증하는 테스트
- 단위 테스트는 매우 간단하고 명확하며 빠르게 실행된다는 특징 있음
- 하나의 함수에 대한 하나 이상의 테스트가 존재할 수 있고 각각의 조건에 대한 유효성을 검증할 수 있음
- 이렇게 작성된 테스트가 많을수록 해당 로직에 대한 신뢰도가 높아질 수 있음
- 작게 쪼개진 단위 테스트는 해당 로직이 어떤 역할을 하는지 쉽게 파악할 수 있음
Integration Test
: 통합 테스트
- 중형 테스트에 속하는 테스트
- 서로 다른 모듈 혹은 클래스 간 상호작용의 유효성을 검사하는 테스트
- 각각 단위 테스트가 검증되었다 하더라도 모듈 간 인터페이스 및 데이터 흐름이 의도한 대로 작동하지 않는 경우도 있기 때문에 조금 더 넓은 범위에서 추가적인 테스트가 필요
- 또한 각 모듈에 대한 설정 또는 테스트를 하기 위해 사전 조건이 필요한 경우도 있기 때문
- 단위 테스트보다 테스트 코드를 작성하기가 복잡하지만 단위 테스트 보다 더 넓은 범위의 종속성까지 테스트함으로써 단위 테스트 보다 좀 더 유의미한 테스트가 되는 경우가 많음
UI Test
- 대형 테스트로 분류되는 테스트
- 실제 사용자들이 사용하는 화면에 대한 테스트를 하여 서비스의 기능이 정상적으로 작동하는지 검증하는 테스트
- UI 테스트는 실제 앱을 사용하는 사용자의 흐름에 대해 테스트 함으로써 UI 변경 사항으로 발생할 수 있는 문제를 사전에 차단
- 화면과 직접적으로 연관되어있는 테스트이기 때문에 실행 시간도 오래 걸리고 디자인이 변경될 때마다 테스트 코드의 수정이 필요하여 유지보수 비용이 큼
Test Coverage 란?
- 테스트 커버리지란 시스템 또는 소프트웨어의 테스트를 논할 때 얼마나 테스트가 충분한가를 나타낸 것
- 즉, 수행한 테스트가 테스트의 대상을 얼마나 커버했는지를 나타냄
코드 커버리지
- 코드 커버리지는 소스 코드를 기반으로 수행하는 화이트 박스 테스트를 통해 측정
Tip!
- 블랙 박스 테스트 (Black-box test)
- 소프트웨어의 내부 구조나 작동 원리를 모르는 상태에서 동작을 검사하는 방식
- 올바른 입력과 올바르지 않은 입력을 입력하여 올바른 출력이 나오는지 테스트하는 기법
- 사용자 관점의 테스트 방법이라 볼 수 있음
- 화이트 박스 테스트 (White-box test)
- 응용 프로그램의 내부 구조와 동작을 검사하는 테스트 방식
- 소프트웨어 내부 소스 코드를 테스트하는 기법
- 개발자 관점의 단위 테스트 방법이라 볼 수 있음
커버리지를 측정하는 기준
- 먼저 코드의 구조를 살펴보면 크게
구문 (Statement)
, 조건 (Condition)
, 결정 (Decision)
의 구조로 이루어져 있음
- 코드 커버리지는 이러한 코드의 구조를 얼마나 커버했느냐에 따라 측정 기준이 나뉘게 됨
구문 커버리지(Statement Coverage)
- 라인 (Line) 커버리지라고 부르기도 함
- 코드 한 줄이 한 번 이상 실행된다면 충족
def test(x):
print("start line")
if x > 0:
print("middle line")
print("last line")
- 위의 코드를 테스트한다고 가정했을 때
x = -1
을 테스트 데이터로 사용할 경우
- if 문의 조건을 통과하지 못하기 때문에 3번 라인의 코드는 실행되지 못함
- 총 4개의 라인에서 1 → 2 → 4번의 라인만 실행되므로 구문 커버리지는 3 / 4 * 100 = 75(%) 가 됨
조건 커버리지(Condition Coverage)
- 여기서의 조건은 모든 조건식
- 모든 조건식의 내부 조건이 true 또는 false 의 경우를 충족하는 지 확인
def test(x, y):
print("start line")
if x > 0 and y < 0:
print("middle line")
print("last line")
- 여기서 내부 조건이란 조건 식 내부의 각각의 조건
- 즉,
x > 0
와 y < 0
를 말하며 각각이 true 또는 false의 경우가 있으면 조건 커버리지를 만족
- 위의 코드를 테스트한다고 가정했을 때
x = 1, y = 1
과 x = -1, y = -1
을 테스트 데이터로 사용할 경우
- 내부 조건식
x > 0
와 y < 0
는 모두 양수일 때와 음수일 때 테스트 케이스가 존재하므로 조건 커버리지를 만족
- 하지만
x > 0 and y < 0
는 두 경우 모두 false 이므로 2번 라인의 코드는 테스트가 안되는 결과가 발생
- 이처럼 테스트를 작성했을 때 조건 커버리지를 만족하더라도 구문 커버리지와 이후에 나올 결정 커버리지를 만족하지 못하는 경우가 존재
결정 커버리지(Decision Coverage)
- 브랜치 (Branch) 커버리지라고 부르기도 함
- 모든 조건식이 true 또는 false를 가지게 되면 충족
def test(x, y):
print("start line")
if x > 0 and y < 0:
print("middle line")
print("last line")
- 위의 코드를 테스트한다고 가정했을 때
x = 1, y = 1
과 x = -1, y = -1
을 테스트 데이터로 사용할 경우
- 조건 커버리지가 만족되더라도 2번 라인의 코드가 실행되지 않았던 결과가 있었음
- 결정 커버리지에서 결정은 내부 조건이 아닌 조건식을 말하는데 위의 코드에서는
x > 0 and y < 0
를 말함
- 따라서 결정 커버리지를 만족하는 테스트를 만든다고 한다면
x = 1, y = 1
과 x = -1, y = -1
와 같이 조건식이 false 만 나오는 식이 아닌 x = 1, y = -1
를 넣는 테스트를 만들어야함
- 이 테스트 케이스를 넣게되면 조건 커버리지와 결정 커버리지, 구문 커버리지를 모두 만족할 수 있게 됨
가장 많이 사용되는 코드 커버리지
- 위에 세 가지 코드 커버리지 중에서 구문 커버리지가 가장 대표적으로 많이 사용되고 있음
- 그 이유는 조건 커버리지나 브랜치 커버리지의 경우 코드 실행에 대한 테스트보다는 로직의 시나리오에 대한 테스트에 더 가깝다고 볼 수 있기 때문
- 위에 두 커버리지는 조건문이 존재하지 않는 코드의 경우 그 코드는 커버리지 대상에서 아예 제외를 함
- 즉, 해당 코드들을 테스트를 하지 않음
- 그러나 구문 커버리지를 만족한다면 모든 코드를 테스트 코드가 커버했다고는 말할 수 있는 있게 됨
- 물론 위에 결정 커버리지의 코드 예시에서 조건식이 false 인 시나리오에 대해서 테스트 됐다고 보장할 수 없지만 그래도 조건문 내부의 코드가 실행되었을 때 문제가 없다는 것은 보장할 수 있음
- 정리하면 구문 커버리지를 만족하면 모든 시나리오를 테스트한다는 보장은 할 수 없지만 어떤 코드가 실행되더라도 해당 코드는 문제가 없다는 보장은 할 수 있음
Test Code Tip
Given, When, Then
- 테스트 코드 작성 시 많은 곳에서 추천하는 코딩 스타일
- 어떤 값이 주어지고 (Given), 무엇을 했을 때 (When), 어떤 값을 원한다 (Then) 을 나누어 직관적으로 볼 수 있기 때문에 코드의 가독성이 향상됨
- 테스트 코드의 가독성이 중요한 또 다른 이유는 테스트 코드가 문서로써의 역할을 하기도 하기 때문
- 테스트 코드를 봄으로써 해당 메소드를 작성한 개발자가 어떤 의도로 만들었으며 어떻게 동작하길 원하는지를 알 수 있음
모든 응답에 대한 테스트를 진행
- API가 조금이라도 수정될 경우, 테스트 코드가 실패하게 됨으로써 항상 올바른 테스트 코드를 유지할 수 있도록 도움
- API가 변경되면 테스트 코드 역시 변경되어야 하는 것이 당연
- 테스트 코드 커버리지는 높을수록 좋음
- 테스트 코드는 정상적으로 작동하는 부분만을 테스트하면 안됨
- 테스트 코드는 실수나 오류를 발견하고 이를 줄이고 수정하기 위해 작성하는 것
F.I.R.S.T
- Fast
- 단위 테스트는 가능한 빠르게 실행되어야 함
- 실행함에 있어 너무 느려 테스트를 꺼리게 된다면 잘못된 단위 테스트
- Independent
- 단위 테스트는 객체의 상태, 메소드, 이전 테스트 상태, 다른 메소드의 결과 등에 의존해서는 안됨
- 따라서 단위 테스트는 어떠한 순서로 실행되더라도 성공해야 함
- Repeatable
- 단위 테스트는 반복 가능해야 함
- 데이터베이스에 의존하는 테스트는 테스트 수행 후 자동으로 롤백을 한다는 등의 별도 설정이 필요
- Self-Validating
- 단위 테스트는 자체 검증이 가능해야 함
- 테스트를 개발자가 직접 수동으로 확일할 필요 없이 assert 문 등에 의해 성공 여부가 결과로 나타나야 함
- Timely
- 단위 테스트를 통과하는 제품 코드가 작성되기 바로 전에 단위 테스트를 작성해야 함
- TDD를 하고 있다면 적용이 되지만 그렇지 않을 수도 있음
Tip! Python Coverage 패키지 관련 내용
python coverage with unittest 적용하기
Python test code coverage 방법
테스트 코드 커버리지 측정 - coverage