안녕하세요, ONDA에서 호텔 운영관리 솔루션을 개발하고 있는 백엔드 개발자 Gunner(거너, 정재훈)입니다. N년간 다양한 분야에서 백엔드 개발을 해오면서 S/W 테스팅 분야의 범위가 넓을뿐더러, 테스팅이 필수인가에 대한 의견이 분분하다는 것을 느꼈습니다.
이에 소프트웨어 테스트의 종류와 개발자가 만들면 좋은 테스트의 기준에 대해 두 편으로 나눠 이야기해 볼까 합니다.
소프트웨어 테스트란 소프트웨어가 올바르게 동작하는지, 버그는 없는지 발견하기 위한 과정을 말합니다.
S/W 테스팅은 크게 블랙박스(Black-Box) 테스트와 화이트박스(White-Box) 테스트로 나눌 수 있습니다.
또한 테스트가 접근하는 레벨의 관점에서는 다음과 같이 분류할 수 있습니다.
◦ Regression(회귀): 기능 추가나 버그 픽스 등 수정을 가했을 때 시스템의 기존 기능에 부정적인 영향을 미치는지 확인하기 위해 실행하는 테스트입니다.
*Integration(통합) 테스트는 Regression(회귀) 테스트가 될 수도 있고, 서로 다를 수도 있습니다.
현재 많은 소프트웨어 시스템은 객체 지향 방법으로 개발되고 있습니다. 객체 지향 패러다임은 1960년대 시뮬레이션 목적으로 만든 Simula 언어를 통해 처음 등장했으나, 현재는 여러 분야에서 사용되고 있죠.
객체 지향 시스템에서 단위 테스트를 할 때 단위의 기준은 무엇일까요?
객체 지향 시스템에서 시스템을 이루는 기본 단위는 객체(object)이므로, 단위 기준은 객체에 대한 정보를 기술하는 클래스(class)로 보는 게 일반적입니다.
클래스는 필드(field)와 메서드(method) 및 상속 등 여러 요소가 얽혀 구성됩니다. 실제 클래스를 단위 테스트할 때, 일반적으로 메서드에 대한 테스트를 작성하기 때문에 메서드를 기본 단위로 생각할 수도 있는데요. 본래 메서드는 클래스와 분리하여 생각할 수 없으므로 메서드를 기본 단위로 보는 것은 적절하지 않습니다.
클래스를 단위 기준으로 봤을 때 단위 테스트는 크게 2가지 형태로 나눌 수 있습니다.
여기서 잠깐! 인터 클래스 형태의 단위 테스트와 통합 테스트의 차이는?
객체 지향 시스템 특성상 한 테스트 케이스에 여러 클래스가 관여하는 경우가 많은데, 이때 단위 테스트와 통합 테스트 간 경계가 모호해집니다. 통합 테스트는 ‘같은 시스템 내 또는 다른 시스템과의 컴포넌트 간 상호 작용을 테스트’하는 것이기 때문이죠.
결국 이는 컴포넌트의 기준을 어떻게 보느냐에 따라 달려있습니다. 만약 컴포넌트의 기준을 하나의 클래스로 본다면 단위 테스트와 통합 테스트간 경계선은 모호해지는 것이고요.
정해진 답은 없지만 일반적으로 다음과 같이 단위 테스트와 통합 테스트의 구분 기준을 세울 수 있습니다. 물론 절대적인 기준이 아니기에 객체 지향 시스템에서 이를 명확히 구분하기란 여전히 어려운 부분으로 남아있지만요.
이런 모호함은 행위 주도 개발(Behaviour-Driven Development, BDD)1 과 테스트 주도 개발(Test-Driven Development, TDD) 같은 테스트 도구에서도 엿볼 수 있습니다. TDD가 단위 테스트에 포커스를 두는 반면, BDD는 TDD에서 파생된 기법으로 시스템의 행위에 포커스를 두고 이를 명확하게 표현하고 테스트하는 데 중점을 둡니다.
BDD에서 테스트 명세를 작성할 때 사용하는 ‘Given/When/Then’ 가이드라인은 이런 BDD의 특성을 잘 나타내는데요.
‘Given/When/Then’ 가이드라인을 사용해 작성된 예제 Scala 테스트 케이스 코드를 살펴보겠습니다.
BDD에서 ‘Given/When/Then’을 강조하는 이유는?
객체 지향 시스템은 객체 간 상호 작용으로 기능이 동작하는 경우가 많아 단위 테스트만으로는 한계가 있고, 그렇기 때문에 테스트 케이스(Test Case, TC) 작성 시 상호 작용에 집중하여 테스트를 만드는 것이 좋습니다. 즉, TC 설계 시 인트라 클래스에 범위를 한정시키지 않고 인터 클래스 범위로 생각하는 편이 좋다는 뜻인데요.
만약 JUnit과 같은 단위 테스트 도구를 쓴다면 1개의 클래스에 대해 하나의 테스트 파일을 작성하는 JUnit의 암묵적인 규칙과 함께 TC 설계도 해당 클래스 한 가지에 한정하여 생각하기 쉽습니다. 하지만 처음부터 ‘Given/When/Then’ 가이드를 고려한다면 자연스럽게 클래스 간 상호 작용을 생각하게 되겠죠?
즉 BDD를 이용할 경우 클래스 간 인터랙션을 고려하여 TC를 작성하게 되고, 이는 자연스럽게 좋은 인터 클래스 테스트를 유도합니다.
물론 ‘Given/When/Then’ 가이드를 따르는 것만으로 상호 작용을 고려한 테스트가 작성되는 것은 아닙니다. ‘Given/When/Then’ 가이드는 단위 테스트와 통합 테스트 모두에 사용할 수 있고, 무엇보다 중요한 건 테스트 작성자가 생각하는 TC의 레벨입니다.
그렇다면 TDD(테스트 주도 개발)는 무엇이고, 이에 대해 어떤 의견들이 있었을까요?