[Swift] 코드 커버리지(Code Coverage)

민경준·2023년 3월 8일
0

🌟 Code Coverage란?

Code Coverage는 테스트의 가치를 측정하는 도구라고 할 수 있다. 이를 통해서 테스터가 의도한 대로 테스트가 잘 되었는지 판단할 수 있는 자료 중 하나이다.
측정 기준은 여러가지가 있고 소스 코드를 기반으로 수행하는 화이트 박스1 테스트를 통해 측정한다.

1. 구문(Statement)

라인(Line) 커버리지라고 부르기도 한다. 프로덕션 코드의 전체 구문 중 몇 줄의 구문이 실행되었는지를 기준으로 판단한다.

세 가지 코드 커버리지 중에서 구문 커버리지가 가장 대표적으로 많이 사용 되어지고 있다.
조건 커버리지나 결정 커버리지의 경우는 코드 실행에 대한 테스트 보다는 로직의 시나리오에 대한 테스트에 더 가깝다고 볼 수 있기 때문이다.

조건문이 존재하지 않는 코드는 조건, 결정 커버리지의 대상에서 아예 제외된다. 즉, 아예 테스트를 하지 않는다는 것이다.
그러나 라인 커버리지를 만족한다면, 우선은 모든 코드를 테스트 코드가 커버했다고는 말할 수 있다.
한마디로 라인 커버리지가 모든 시나리오를 테스트 한다는 보장은 할 수 없지만, 어떤 코드가 실행 되더라도 해당 코드는 문제가 없다는 보장은 할 수 있다.

func foo(x: Int) {
	print("Start Line")
    if x > 0 {
    	print("Middle Line")
    }
    print("End Line")
}

위의 코드를 테스트 한다고 가정해보자. x = -1을 테스트 데이터로 사용할 경우, if문의 조건을 통과하지 못하기 때문에 3번 코드는 실행되지 못한다.
총 4개의 라인에서 1,2,4번의 라인만 실행되므로 구문 커버리지3 / 4 * 100 = 75(%)가 된다.

2. 결정(Decision)

브랜치(Branch) 커버리지라고 부르기도 한다. 모든 조건식이 true/false를 가지게 되면 충족한다.
즉, 각 분기의 내부조건 자체가 아닌 전체의 결과가 true/false를 가지면 충족한다.

func foo(x: Int, y: Int) {
	print("Start Line")
    if x > 0 && y < 0 {
    	print("Middle Line")
    }
    print("End Line")
}

위의 코드를 테스트 한다고 가정해보자. if문의 조건에 대해 true/false 모두 가질 수 있는 테스트 케이스로는 x = 1. y = -1, x = -1, y = 1이 있다. 첫 번째 테스트 데이터는 x < 0y < 0 모두 true이기 때문에 if문의 조건에 대해 true를 반환한다. 두 번째 테스트 데이터는 x > 0에서 이미 false이기 때문에 if문의 조건에 대해 false를 반환한다. 모든 조건식에 대해 truefalse를 반환하므로 결정 커버리지를 충족한다.

3. 조건(Condition)

모든 조건식의 내부 조건이 true/false를 가지게 되면 충족한다. 조건 커버리지는 결정 커버리지와 다르게, 전체 조건식이 아니라 개별 조건식을 기준으로 판단한다. 개별 조건식이 모두 true/false를 한번식 갖도록 하면 조건 커버리지의 기준을 충족한다.

내부 조건이라는 것이 헷갈릴 수 있는데 조건식 내부의 각각의 조건이라 생각하면 될 것 같다.
아래 코드를 예시로 보면 모든 조건식으로는 2번째 줄에 if문이 있고, 그 중 내부 조건은 조건식 내부의 x > 0, y < 0을 말한다.

func foo(x: Int, y: Int) {
	print("Start Line")
    if x > 0 && y < 0 {
    	print("Middle Line")
    }
    print("End Line")
}

위의 코드를 테스트한다고 가정해보자. 조건 커버리지를 만족하는 테스트 케이스는 x = 1, y = 1, x = -1, y = -1이 있다. 이는 x > 0 내부 조건에 대해 true/false 모두 만족하고, y < 0 내부 조건 또한 true/false 모두를 만족하므로 조건 커버리지의 기준을 충족하게 된다.

하지만 if 조건문의 관점에서 보면 false에 해당하는 시나리오만 체크됐다. 또, 조건 커버리지는 만족했을지 몰라도 3번째 구문이 실행되지 않았기 때문에 라인 커버리지와 결정 커버리지는 만족시키지 못하게 된다.

조건 커버리지를 만족하도록 테스트를 작성할 경우, 구문 커버리지와 결정 커버리지를 만족하지 못하는 경우가 존재할 수 있다.

🔥 코드 커버리지, 몇 퍼센트를 목표로 해야할까?

일반적으로 80% 커버리지가 좋은 목표라고 알려져있다. 반면, 책 클린 코더에서 로버트 마틴은 테스트 커버리지 100%는 권장이 아니라 강력히 요구되는 사항이라고 한다. 즉, 코드 커버리지에는 정답은 없다.

얼마만큼의 코드를 자동화한 단위 테스트로 계산해야 할까? 대답할 필요조차 없다. 모조리 다 해야 한다. 모.조.리! 100% 테스트 커버리지를 권장하냐고? 권장이 아니라 강력히 요구한다. 작성한 코드는 한 줄도 빠짐없이 전부 테스트해야 한다. 군말은 필요 없다. ― 클린 코더 (로버트 마틴 저)

높은 커버리지의 장점

토스뱅크 서버 개발자 이응준님의 '테스트 커버리지 100%'라는 발표 영상을 봤는데, 이응준님도 위 로버트 마틴의 이야기를 듣고 코드 커버리지 100%에 도전하셨다고 한다. 그리고 실제로 100%를 달성하였고, 그 이후 코드 커버리지가 100% 아래로 떨어지면 배포가 불가능하도록 설정했다고 한다. 이응준님의 발표에 따르면 높은 코드 커버리지는 아래와 같은 이점이 존재한다.

코드 커버리지가 높으면, 이전보다 더 과감하게 코드를 리팩토링할 수 있다. 리팩토링에 문제가 있다면 테스트 코드가 알려줄 것이다. 비슷한 이유로 배포도 더 자신있게 할 수 있게된다.

또한 불필요한 프로덕션 코드가 사라진다. 높은 코드 커버리지를 유지하기 위해서는 불필요한 코드에 대한 테스트 코드도 작성해야하는데, 이럴 바에는 불필요한 코드를 즉시 지워버리는 편이 좋기 때문이다.

뿐만 아니라 높은 코드 커버리지는 프로덕션 코드의 이해도를 높여준다. 이해가 충분하지 못하면 테스트 코드를 작성할 수 없기 때문이다.

100% 커버리지 =/= Bug-Free Software

하지만, 커버리지가 아무리 높다고한들 어플리케이션의 중요한 부분이 테스트되지 않고 있거나, 테스트 코드가 문제를 사전에 제대로 발견할만큼 견고하지 않다면 커버리지는 큰 의미 없는 수치이다.

개발자가 요구사항을 잘못 이해하고 테스트 코드를 작성할수도, 테스트 코드 자체를 잘못 작성했을수도, 테스트 케이스를 빠트릴수도있다.

아래와 같은 덧셈을 수행하는 Calculator 클래스가 있다고 하자.

class Caclulator {
	public func sum(_ a: Int, _ b: Int) -> Int {
    	return a + b
    }
}

위 클래스를 아래와 같은 테스트 코드로 테스트한다면 테스트 커버리지는 100%를 달성할 것이다.

func test0과0을전달하면0을반환한다() throws {
	// given
	let calculator: Calculator = Calculator()
    let a: Int = 0
    let b: Int = 0
    
    // when
    let result: Int = calculator.sum(a, b)
    
    // then
    XCTAssertEqual(result, 0, "0과 0을 전달하면 0을 반환한다")
}

func test1과0을전달하면1을반환한다() throws {
	// given
	let calculator: Calculator = Calculator()
    let a: Int = 1
    let b: Int = 0
    
    // when
    let result: Int = calculator.sum(a, b)
    
    // then
    XCTAssertEqual(result, 1, "1과 0을 전달하면 1을 반환한다")
}

하지만 위 테스트 코드는 완벽하지 않다. 누군가 sum(_:_:) 메소드의 +연산자를 -로 변경했다고 하더라도 테스트는 여전히 통과할 것이다.
즉, 높은 커버리지가 테스트가 좋음을 보장해주지 않는다. 커버리지 100%를 달성한다고 한들, 위의 예시처럼 여전히 버그의 위험은 존재한다. 높은 커버리지는 좋지만, 맹신해서는 안된다.



Reference




1. 화이트박스 테스트와 블랙박스 테스트에 관한 자세한 설명은 이곳을 참고하면 된다.

profile
iOS Developer 💻

0개의 댓글