테스트를 통한 고품질 확보는 소프트웨어 개발에 있어 매우 중요한 활동입니다. 특히, 다른 테스트에 비해 적은 비용으로 수행할 수 있는 단위 테스트는 소프트웨어의 품질을 향상시키는 데 중요한 역할을 합니다. 개발 초기부터 적용하여 빠르게 수행 결과를 확인함으로써 개발자의 생산성을 높이는 데 효과적이기 때문입니다.
테스트 주도 개발(Test-Driven Development, 이하 TDD) 방식도 단위 테스트를 활용하여 보다 높은 수준의 코드 품질을 확보하려는 개발 방법 중 하나입니다.
TDD는 짧은 개발 주기(보통 몇 분에서 몇 시간)의 반복에 의존하는 개발 프로세스로서, Extreme Programming(XP)의 “Test-First” 개념에 기반을 둔 단순한 설계를 중요시합니다. TDD를 가장 쉽게 표현한 것이 “Red-Green-Refactor” 개발 주기인데, 이는 [그림1]과 같습니다.
그림1 - TDD Cycle
- Red 단계에서는 ‘실패하는 테스트 코드’를 먼저 작성합니다.
- Green 단계에서는 테스트 코드를 성공시키기 위한 실제 코드를 작성합니다.
- Yellow 단계에서는 중복 코드 제거, 일반화 등의 리팩토링을 수행합니다.
중요한 것은 실패하는 테스트 코드를 작성할 때까지 실제 코드를 작성하지 않는 것과, 실패하는 테스트를 통과할 정도의 최소 실제 코드를 작성해야 하는 것입니다. 이를 통해, 실제 코드에 대해 기대되는 바를 보다 명확하게 정의함으로써 불필요한 설계를 피할 수 있고, 정확한 요구 사항에 집중할 수 있습니다.
TDD를 통해 얻을 수 있는 장점은 크게 단위 테스트로부터 얻어지는 것과, 테스트를 먼저 작성(Test-First)하는 방식에 의한 것으로 구분할 수 있습니다.
단위 테스트를 통한 장점은 동작하는 코드에 대해 자신감을 가질 수 있고, 회귀 테스트(regression tests)를 통하여 자유롭게 리팩토링 할 수 있으며, 코드에 대한 지식이 증가하는 점 등입니다. 테스트 코드를 작성함으로써 실제 코드에 대한 이해를 높일 수 있을 뿐만 아니라, 실제 코드에 좋지 않은 설계 구조를 손쉽게 파악할 수 있죠. 개발자의 생산성 향상도 단위 테스트 작성을 통해 얻을 수 있는 이점 중 하나인데, 생산성에 영향을 주는 요소들을 아래 [그림2]와 같이 생각해 볼 수 있습니다.
실제로 단위 테스트 또는 TDD를 사용하면 디버깅하는 시간이 현저히 줄어들 뿐만 아니라, 잘못된 코드에 대해 빠른 오류 확인이 가능합니다. 당장은 단위 테스트 작성에 추가적인 노력이 든다고 생각할 수 있지만, 전체 개발 주기를 생각했을 때 이러한 노력이 담긴 테스트를 통해서 결국 생산성이 향상됩니다.
테스트 코드를 먼저 작성함으로써 얻어지는 장점은 테스트 코드 작성 단계에서 실제 코드에 대한 명확한 처리를 설계함으로써 과도한 설계를 피할 수 있고, 간결한 인터페이스를 갖는 점 등 입니다. 더욱이 사전에 작성된 테스트 코드는 요구 사항을 구체화하는 문서로써 활용될 수 있습니다. 이를 실행 가능한 문서(Executable documentation) 또는 명세(Specification)라고도 표현합니다. 향후 요구 사항이 변경되면 테스트 코드 부분이 먼저 변경되어야 하므로, 항상 최종 정보를 제공하는 문서로 관리될 수 있기 때문입니다.
TDD를 사용하면 소프트웨어의 품질이 높아진다고 볼 수 있습니다. 그런데, 소프트웨어의 품질은 어떻게 측정하고 평가할 수 있을까요?
우선 소프트웨어 품질은 [그림3]과 같이 외적 품질(External Quality)과 내적 품질(Internal Quality)로 구분할 수 있습니다.
외적 품질은 기능적인(functional) 품질로서, 소프트웨어가 기대되는 동작을 하는지 등을 나타냅니다. 내적 품질은 코드 품질, 즉 소스 코드의 구조적인(structural) 품질을 나타냅니다. 일반적으로 기한이 주어진 프로젝트에서는 외적 품질을 중요하게 다룹니다. 요구되는 기능이 제대로 동작하지 않는다면, 소프트웨어로서 가장 중요한 가치를 상실하기 때문입니다. 그러나 외적 품질에 문제가 없더라도, 내적 품질이 확보되지 않으면 시간이 지남에 따라 외적 품질도 낮아질 수밖에 없습니다. 따라서 내적 품질도 외적 품질만큼 중요하게 관리되어야 합니다.
내적 품질을 측정할 수 있는 다양한 방법은 소프트웨어 분야에서 오래전부터 고민해 온 주제였습니다. 이제부터 설명할 Cyclomatic Complexity(코드의 복잡함을 측정하는 방식 중 하나)는 이미 70년대부터 사용되었죠.
코드의 품질을 측정할 수 있는 여러 지표(Metrics) 중 대표적인 것들은 다음과 같습니다.
항목 | 세부항목 | 설명 | 비고 |
Code Size | NCLOC | Non Comment Lines of Code | 유효코드라인 (LoC) |
---|---|---|---|
NOM / NOC | Number of Methods / Classes | ||
Complexity | MCC | McCabe Cyclomatic Complexity | |
MLOC | Method Lines of Code | 메소드당 라인 수 | |
CLOC | Class Lines of Code | ||
Modularity | Martin Metrics | Packaged Based Abstraction level | 결합도 관련 |
OO Metrics | LCOM | Lack of Cohesion Methods | 응집도 관련 |
SI | Specialization Index | LSP 관련 | |
DIT | Depth of Inheritance Tree | 결합도 관련 |
코드 수준에서 어떤 방식으로 품질을 측정하는지 알기 위해, 이 중 몇 가지에 대해서 살펴보겠습니다.
복잡도를 측정하는 대표적인 방식은 Thomas J. McCabe가 만든 Cyclomatic Complexity입니다. 소프트웨어의 실행 경로의 개 수를 측정하는 방식으로, 흐름 제어 그래프(Control flow graph) 기반의 복잡도 M은 다음과 같이 정의 됩니다.
또는 “M = the number of decisions + 1”로 간단히 계산할 수 있습니다. 다만, 어떤 결정(decision)문, 논리적 연산자(And, Or) 또는 예외(throw 등)를 포함할지 등에 따라 다양한 산정 방식이 있습니다. 이에 대해서는 Extended, Strict cyclomatic complexity, Modified cyclomatic complexity 등과 측정 툴의 설명을 참조하기 바랍니다.
Martin Metrics는 Uncle Bob으로 알려진 Rebert C. Martin이 만든 지표로, 패키지 기준으로 추상화 및 안정성을 측정하여 계산합니다. 패키지에 대한 불안정성(Instability) I를 [그림5]와 같이 패키지 기준으로 참조가 들어오는 수(Afferent Coupling 또는 Fan-in)와 나가는 수(Efferent Coupling 또는 Fan-Out)를 가지고 다음과 같이 계산하죠.
이 불안정성은 외부의 변경에 얼마나 영향을 받는지에 대한 지표로, 들어오는 참조보다 나가는 참조가 많은 경우(Ca < Ce) 외부 변경으로부터 많은 영향을 받기 때문에 불안정성(Instability)이 높다고 보는 것입니다.
다음으로 추상화 정도(Abstractness)를 나타내는 지표 A를 전체 클래스 중 interface나 abstract class가 어느 정도 차지하는지를 다음과 같이 구합니다.
불안정성 I와 추상화 정도 A는 개별 값으로는 평가할 수 없습니다. 상황에 따라 안정성이 높아야 하는 경우도 있고, 외부 참조가 많아 어쩔 수 없이 불안정성이 높아진 경우도 있습니다. 추상화 정도도 외부에 참조가 적을 때와 같이 추상화가 높을 필요 없는 경우도 있고, 참조가 많기에 추상화를 높여 내부 변경에 대한 외부로의 영향을 줄여야 하는 경우도 있습니다. 즉, 불안정성과 추상화 정도는 상호보완적으로, 이 둘을 합친 값이 1이 될 때 가장 이상적입니다. 이를 Main Sequence라고 하며, 이 값으로부터 얼마나 떨어져 있는지를 측정하는 거리(Distance) D를 다음과 같이 계산합니다.
거리 D에 대한 측정은 [그림6]을 참고할 수 있으며, 0 에 가까울수록 적절하게 설계되었다고 볼 수 있습니다.
LCOM은 응집도(Cohesion)에 대한 측정 지표로, 기본적인 아이디어는 클래스의 속성(Field 또는 Member 변수라고 표현)과 메소드의 상관관계를 수치적으로 계산하여 측정합니다. 만약 클래스 안의 모든 속성이 전체 메소드에서 사용되고 있다면 응집도가 가장 높다고 말할 수 있고, 일부 속성이 특정 메소드에서만 사용된다면 응집도가 낮다고 말할 수 있습니다. 이와 같이 응집도가 낮은 경우, 일부 속성들과 관련된 메소드만을 분리하여 보다 높은 응집도를 갖도록 개선할 수 있습니다.
이 LCOM도 여러 가지 방식으로 계산될 수 있는데, 그 중 하나는 다음과 같습니다.
이 LCOM 계산 방식은 0과 1 사이의 값을 가지며, 0에 가까울수록 응집도가 높다고 평가됩니다.
지금까지 코드 품질 지표에 대해 간단히 살펴보았습니다. 다시 TDD로 돌아가, 이런 지표들의 측정을 통해 TDD가 실제 코드 품질에 얼마나 영향을 주는지 알아보겠습니다. 이 주제에 대해서는 많은 연구가 있었지만, 대부분 외적 품질의 개선 연구에 초점이 맞추어져 있었고 제한된 환경에서 수행된 것들이 많습니다. (그 중 Microsoft나 IBM의 연구 사례가 참고하기 좋을 듯합니다.)
내적 품질 측면에서 개선을 확인할 수 있는 연구 사례로는, 실제 사용되는 여러 오픈소스 SW를 기반으로 연구한 Rod Hilton의 논문이 있습니다. 이 연구에서는 50개 이상의 오픈소스 SW와 57여 명의 Committer/Contributor 설문조사를 통해 TDD 방식으로 개발된 프로젝트와 TDD 방식이 아닌 프로젝트를 구분하여 여러 가지 지표들을 측정하고 비교하였습니다.
결론적으로 복잡도(McCabe Cyclomatic Complexity) 21.18%, Martin Metrics의 거리(Distance) 18.68%, 응집도(LCOM) 6.97% 등 여러 면에서 우수한 것으로 조사되었습니다. 그 외 다양한 코드 품질 지표들이 비교되어 있으니, 관심 있는 분들은 해당 논문을 참조하시기 바랍니다.
아울러 다양한 코드 품질 지표를 측정할 수 있는 툴을 활용하면, 정량적인 측정을 통해 좋은 구조의 코드를 작성하는 데 도움이 됩니다.
TDD를 효과적으로 적용하기 위해 몇 가지 생각해 볼 주제들이 있습니다. 우선, 테스트 코드와 관련된 다양한 기술의 활용입니다. TDD의 기반이 되는 테스트에 대해 자유롭게 활용할 수 있어야 하죠. 일부는 적절한 Mock 프레임워크를 사용하여 자동화할 수 없는 부분과 테스트하기 어려운 부분을 분리하여 활용할 필요가 있습니다.
또, Red-Green-Refactor cycle 상의 “Refactor” 부분에 대해서 다양한 리팩토링 기법을 사용할 수 있어야 합니다. TDD는 결국 이 Refactor에 의해 완성된다고 볼 수 있습니다. 개별적이고 구체적인 예시로부터 리팩토링을 통해 결국 일반화된 코드가 완성되는 과정인 것이죠.
마지막으로 정확한 요구 사항을 도출하기 위한 BDD(Behavior-Driven Development: 구체적인 테스트 코드를 작성하기 전에 구조화되고 특화된 공통 언어를 통해 요구 사항을 시나리오 형태로 정의하여 관리하는 개발 프로세스로, TDD로부터 파생) 또는 ATDD(Acceptance Test-Driven Development: 실제 코드를 작성하기 전에 인수 테스트 코드를 고객과 함께 정의하는 개발 프로세스로, BDD와 같이 TDD를 기반으로 함) 등을 활용을 고려할 수 있습니다. 이는 요구 사항 또는 기능(feature) 정의 단계부터 테스트를 활용하는 것으로, 고객, 도메인 전문가, 분석가, 테스터 등 다양한 이해관계자의 참여를 유도할 수 있습니다.
* 참고
- Alex Garcia; Viktor Farcic, (Packt Publishing, 2018). Test-Driven Java Development – Second Edition
- Lasse Koskela, (Manning, 2013). Effective Unit Testing: A guide for Java developers
- Microsoft research : Bhat, T., & Nagappan, N. (2006). Evaluating the efficacy of test-driven development: industrial case studies
- Williams, L., Maximilien, E. M., & Vouk, M. (2003). Test-driven development as a defect-reduction practice
- Rod Hilton, (2009). Quantitatively Evaluating Test-Driven Development by Applying Object-Oriented Quality Metrics to Open Source Projects
▶ 해당 콘텐츠는 저작권법에 의하여 보호받는 저작물로 기고자에 저작권이 있습니다.
▶ 해당 콘텐츠는 사전 동의없이 2차 가공 및 영리적인 이용을 금하고 있습니다.
한성곤 프로는 삼성SDS SW엔지니어링팀에서 코드에 대한 품질 지표 수립 및 개선을 위한 다양한 활동(리팩토링/클린코드 교육, 코드리뷰 및 점검 수행 등)을 하고 있습니다.