Blog

테스트 주도 iOS 애플리케이션 개발 3장 정리

February 10, 2014

테스트 주도 iOS 애플리케이션 개발 3장 정리

단위 테스트의 작성 방법

지금까지 소프트웨어를 테스트하는 목적과, 테스트 주도 소프트웨어와 단위 테스트가 목적을 이루는 데 어떤 도움을 주는지를 살펴봤다.

이 장에 있는 코드는 특정 프로젝트의 일부가 아니라 요구사항에서 시작해 요구사항이 테스트된 앱 코드가 되기까지의 생각 절차를 보여줄 것이다.

요구사항

단위 테스트 작성의 첫 걸음은 애플리케이션에서 필요로 하는 기능이 무엇인지 확인하는 것임을 상기하자.

온도 변환이 텍스트필드에서 시작될 것이기에 UITextFieldDelegate의 -textFieldShouldReturn: 메소드의 사용이 필요하다는 것을 안다. 이 메소드의 설계는 텍스트필드(이 경우에는 섭씨 온도 값을 포함하는 필드)가 메소드의 매개변수임을 의미한다. 그러므로 사용하려고 하는 메소드는 다음과 같은 형태가 될 것이다.

- (BOOL)textFieldShouldReturn:(id)celsiusField;

알려진 입력값으로 코드 실행

단위 테스트의 중요한 특징 중 하나는 단위 테스트가 반복 가능해야 한다는 것이다.

-40°C와 -40°F가 같음을 알고 있기 때문에 이런 경우를 테스트할 것이다.

보통은 테스트 코드와 애플리케이션 코드를 분리한다.

이보다 더 적절한 구현을 할 필요가 없다. 사실 YES와 NO 중 어떤 값을 반환해야 할지와 같은 아직 정해지지 않은 일을 결정해야 했다.

기대되는 결과

이제 내가 원하는 입력값으로 메소드를 호출할 수 있기 때문에 메소드가 무엇을 하는지 검사하고 앱의 요구사항과 결과가 일치하는지 확실히 할 필요가 있다.

메소드의 입력을 제공하고 출력을 보려고 특별한 객체를 사용하는 패턴은 많은 테스트에 사용된다. 이런 패턴을 모의 객체(Mock Object나 Fake Object)라 한다.

결과 검증

단위 테스트의 필수 요구사항은 자체 테스트가 가능해야 한다는 것이다. 각 테스트는 후행 조건에 도달했는지 알아내야 하며 성공 실패 여부를 알려야 한다.

단위 테스트가 테스트할 수 있는 테스트 수

설계가 잘된 테스트는 하나의 시나리오에 적합한 선행 조건을 설정하고 시나리오대로 앱 코드가 정확히 동작하는지를 평가하기에 충분하다. 각 테스트는 독립적이고 성공이나 실패 이외의 중간 상태는 없어야 한다.

좀 더 보기 쉬운 테스트

테스트 조건과 성공이나 실패를 알리는 행위처럼 테스트를 둘러싼 기구를 한 줄로 압축해서 테스트가 일어난 곳을 명확하게 만들고 테스트한 구문을 찾기 쉽게 만든다면 테스트는 좀 더 명확해질 것이다. 조건문과 테스트를 한 줄에 입력하는 매크로를 다음처럼 정의해서 만들 수 있다.

테스트를 통과하지 못하면 프레임워크는 출력으로 개발자 제공 메시지를 보여준다. 이 메시지는 해당 테스트를 생성했던 이유를 알려주고 해당 정보는 테스트를 통과하지 못한 것을 이해하게 돕는다. 테스트해야 하는 특정 조건을 좀 더 분명하게 만들 수 있다.

다중 테스트 구성

전체 입력값의 범위에 따라 정확한 출력을 제공해야 하므로 다양한 입력 조건을 테스트해야 한다. 온도 변환기에서 허용하는 입력값의 범위를 정의해야 하고 이런 입력값 정의의 범주를 벗어나는 것에 입력이 들어왔을 때 어떻게 동작할 것인지를 정의해야 한다.

사실 단위 테스트를 구성하는 좋은 방식은 앱의 클래스 구성을 반영하는 것이다. 애플리케이션의 각 클래스는 특정 클래스의 메소드를 테스트하는 연관된 단위 테스트 클래스를 가지고 있다.

응용 클래스 간에는 의존성을 의미하는 연결선이 있지만 테스트 클래스 간에는 이러한 의존성이 없음에 주목하자. 단위 테스트로서 각 테스트 클래스는 코드가 테스트하는 특정 단위를 필요로 한다.

다른 클래스와의 의존성을 제거하고자 이전에 사용했던 FakeTextContainer 같은 모의 객체를 사용할 수 있다. 다른 코드와의 의존성을 제거하는 것은 의도치 않은 실패를 줄인다. 테스트 클래스 안에 존재하는 하나의 실패는 테스트하고자 하는 클래스에 문제가 있음을 의미하는 것이기 때문이다. 한 테스트 클래스가 여러 응용 클래스에 의존성을 가지고 있다면 하나의 테스트 실패에 대한 이유를 찾으려고 많은 소스 코드를 검사해야봐야 할 것이다.

하나의 응용 클래스 동작에서 서로 다른 여러 가지 측면을 테스트하는 여러 개의 테스트 메소드를 가진 단일 클래스가 있다면 각 테스트 사이에 중복된 코드가 있을 것이다.

OCUnit과 같은 단위 테스트 프레임워크는 각 클래스를 위한 테스트 픽스처를 생성해서 이런 유사한 테스트를 할 때 중복성을 제거하는 데 도움을 준다. 테스트 픽스처는 각 테스트를 실행하는 공통된 환경을 제공한다. 다중 테스트에서 필요한 환경은 테스트 픽스처에 담고 각 테스트는 자신의 고유한 픽스처 복사본에서 실행된다. 이 방식을 이용해 각 테스트는 다른 테스트에 영향을 받지 않는 자신만의 환경을 갖게 된다.

리팩토링

온도 변환기와 변환기의 동작 관련 테스트는 서로 별개의 것이므로 별도의 클래스에 정의해야 한다. 사실 두 개의 다른 클래스간 역할을 분할한다는 것은 테스트 클래스를 배포할 필요가 없음을 의미한다. 사용자가 테스트 코드를 딱히 필요로 하지 않기 때문이다.

이런 리팩토링 작업을 만족할 때까지 원하는 한 계속할 수 있다. 테스트는 애플리케이션의 로직을 바꿀 부분이 있는지 찾아내기를 기다리며 항상 그 자리에 있을 것이다.

정리

요약 부분은 따로 요약하지 않았다. 이번 장의 핵심은 다중 테스트 구성에 나오는 것 같다. 테스트 구성하는 좋은 방법은 앱의 클래스 구성을 따르는 것이며, 테스트 클래스 간에는 의존성이 없어야 하며 이를 위해 모의 객체를 사용할 수 있다.