2014년 8월 13일 수요일

Xcode 6 유닛테스트(Unit Test) 기초가이드

앞서 Swift 프로젝트에서 유닛 테스트 하기에 관한 글을 썼었는데 이번 글은 Xcode 6 에서 제공하는 유닛 테스트(Unit Test)의 기본 가이드이다. 코드는 스위프트를 기준으로 하겠지만 Objective-C 에서도 구조가 동일하기 때문에 이해하기에 어려울 것은 없을 것이다.

XCTestCase

Xcode의 유닛 테스트는 XCTestCase 라는 클래스에 기반한다. 즉 테스트 케이스를 만드려면 이 XCTestCase 를 상속받은 클래스를 만들면 된다.

Xcode에서 테스트 클래스(Test Class) 파일을 추가하면 기본적으로 XCTestCase 를 상속받은 파일과 함께 예제 코드도 자동으로 입력된 상태로 추가된다.

setUp 과 tearDown

테스트케이스 템플릿에는 setUp과 tearDown 이라는 메소드가 오버라이드 되어있다. 이 두 메소드는 각 테스트 실행 전 그리고 실행 후에 호출된다.
import XCTest

class TestClassName: XCTestCase {
    override func setUp() {
        // ...
    }

    override func tearDown() {
        // ...
    }
}
각 테스트가 실행되기 전에 setUp() 메소드가 호출된다. 그리고 각 테스트 케이스가 실행된 후 tearDown() 메소드가 실행된다. 따라서 각 테스트 케이스가 공통적인 준비와 마무리 과정이 필요하다면 여기에다 필요한 내용을 구현하면 된다.

물론 필요없다면 아예 없애버려도 관계는 없다.

테스트 케이스

XCTestCase 를 상속받은 클래스는 특정 오버라이드 전용 메소드를 제외하고는 전부 자동으로 테스트 케이스 메소드가 된다.
class TestClassName: XCTestCase {

    // ...

    func myTestCase() {
        // ...
    }

    // ...
}
위의 예제코드는 myTestCase 라는 메소드를 만들었는데, 앞서 이야기 한 대로 특정 메소드를 제외하고 정의된 모든 메소드는 테스트케이스가 된다. 이 케이스를 Xcode를 통해 개별로 실행시키거나 혹은 모든 테스트를 실행시킬 수도 있다.

테스트의 구현 - XCTAssert

테스트 자동화는 컴퓨터가 테스트를 하도록 만드는 것이다. 그래서 테스트 케이스에서 구현해야 할 것은 유닛(소프트웨어의 가장 최소화된 단위. 일반적으로 함수나 메소드 정도면 충분하다)을 어떻게 테스트 하느냐이다.

예를 들자면 특정 클래스의 특정 메소드에 어떤 입력을 넣으면 어떤 값이 출력되느냐를 정의해야 한다. 이럴때 우리는 XCTAssert 류 함수들을 이용 할 수 있다.
class TestClassName: XCTestCase {

    // ...

    func myTestCase() {
        let myObject = MyGoodClass()
        XCTAssert(myObject.run(100) == 5050)
    }

    // ...
}
위 코드에서 XCTAssert() 라는 함수 내부의 표현을 보자. 여기서는 MyGoodClass의 오브젝트인 myObject의 run() 메소드를 호출하고 있다. 이 메소드 파라미터로 100을 넣었고 이 메소드의 리턴 값이 5050 인지 비교하는 코드를 입력해놨다.

이 XCTAssert가 사용된 코드 라인의 의미는 이렇다: "run() 메소드에 100을 넣으면 출력은 반드시 5050 이어야 한다."

즉, 만약 MyGoodClass의 run 메소드가 구현이 잘못되었다면 이 테스트에서 실패가 발생하고 프로그래머는 버그가 있다는 것을 알게 된다.

테스트 함수들

앞에서 XCTAssert를 언급했는데 XCTAssert 함수는 내부 파라미터로 넘겨지는 식(정확히 말해 클로져)이 true이면 성공하는 함수이다. 그런데 이런 함수류는 굉장히 많다.
  • XCTFail(format...)
  • XCTAssertNil(a1, format...)
  • XCTAssertNotNil(a1, format...)
  • XCTAssert(a1, format...)
  • XCTAssertTrue(a1, format...)
  • XCTAssertFalse(a1, format...)
  • XCTAssertEqualObjects(a1, a2, format...)
  • XCTAssertEquals(a1, a2, format...)
  • XCTAssertEqualsWithAccuracy(a1, a2, format...)
  • XCTAssertThrows(expression, format...)
  • XCTAssertThrowsSpecific(expression, specificException, format...)
  • XCTAssertThrowsSpecificNamed(expression, specificException, specificName, format...)
  • XCTAssertNoThrow(expression, format...)
  • XCTAssertNoThrowSpecific(expression, specificException, format...)
  • XCTAssertNoThrowSpecificName(expression, specificException, specificName, format...)

위 함수들 이름을 보면 XCTFail만 제외하곤 전부 XCTAssert 라는 이름으로 시작한다는 공통점이 보인다.

여기서 XCTFail은 Xcode에게 무조건 실패했음을 알리기 위한 용도이다. 복잡한 코드로 직접 검증한 뒤 문제가 있다면 XCTFail을 호출하도록 코딩하면 되는 용도인데 사실 거의 쓸 일은 없다.

XCTAssert로 시작되는 함수들은 모두 테스트의 정의들이다. 예를 들어 XCTAssertNil은 a1의 결과가 nil 이어야 한다는 것을 의미한다. 만약 nil이 아니라면 테스트가 실패한다.

비슷하게 XCTAssertEquals 는 a1과 a2의 결과가 동일해야 테스트가 성공한다는 식이다. 앞서 본 예제에서 XCTAssert가 쓰인 코드를 XCTAssertEquals로 고치면 이렇게도 쓸 수 있다: XCTAssertEquals(myObject.run(100), 5050)

XCTAssertThrows 류의 함수들은 모두 예외(Exception)의 발생 여부를 테스트 하기 위한 것들이다.

이제 이름만 보면 무슨 용도인지 파악은 가능할 것이다.

퍼포먼스 테스트

퍼포먼스 테스트 역시 기본 테스트케이스 템플릿에 예제로 포함되어 있는 내용이다.
class TestClassName: XCTestCase {

    // ...

    func testPerformanceExample() {
        self.measureBlock() {
            // ...
        }
    }

    // ...
}
이 코드는 self.mesureBlock() 이라는 메소드를 통해 클로져 내부를 구현하라고 유도하고 있다. 즉 위의 measureBlock() 의 중괄호 내부에 퍼포먼스 테스트를 하기 위한 코드를 채워넣으면 된다.

엄밀히 말해 퍼포먼스 테스트는 유닛테스트와는 관계가 없다. 그냥 퍼포먼스 테스트를 하고 싶다면 만들어봐도 되겠지만 아마도 별로 쓸 경우는 많지 않을 것 같다.

테스트 해 보기

실컷 코드를 만들었는데 테스트하는 방법을 모르면 말이 안될 것이다.

우선 XCTestCase 를 상속받은 클래스들은 별도의 엔트리포인트가 필요없다. 이 클래스들은 모두 Xcode가 테스트 용이라고 알아채고 알아서 테스트 네비게이터에 표시도 해 준다. 즉 프로그래머가 할 일은 그저 XCTestCase를 상속받은 클래스를 구현해 두는 것 뿐이다. 다른 일은 할 게 거의 없다. 원하는 테스트 케이스 파일을 원하는 만큼 추가해서 마음껏 구현만 해 두면 된다.

테스트케이스의 준비가 되었다면 Xcode에서 테스트 네비게이터(Test Navigator)를 열어보자.
Xcode 6 Beta 5 를 기준으로, 좌측 네비게이터 창 상단의 아이콘들 중 5번째에 있는 것이 Test Navigator 이다. 이 창에서는 모든 XCTestCase 클래스들과 각 테스트 케이스 목록이 표시되고 있다.

테스트를 실행시키기 위해서는 여기서 제일 상단에 커서를 가져가 보면 우측에 삼각형 아이콘이 뜨는 것을 볼 수 있다. 이걸 누르면 모든 테스트가 실행된다. 위 스크린샷은 모든 테스트가 성공했음을 녹색 체크표시로 알려주고 있다.

물론 각 테스트 케이스 메소드들도 마우스 커서를 가져가보면 우측에 삼각형 플레이 아이콘이 뜬다. 위 스크린샷에서 녹색 체크 아이콘이 표시되는 곳에 커서를 올려보면 알 수 있다. 역시 눌러보면 해당 테스트 케이스가 실행된다.

그리고 XCTestCase 상속 클래스 소스 파일을 열어보면 편집 시에도 테스트를 실행시켜 볼 수 있다. 위 스크린샷에서 코드가 표시되는 창 좌측에 역시 녹색 체크표시가 보일 것이다. 테스트를 하지 않았다면 이 부분에 마름모꼴 모양의 빈 모양이 표시되는데 마우스 커서를 가져가보면 역시 삼각형 모양의 플레이 아이콘이 등장한다. 당연히 눌러보고 싶게 생겼다. -_-;; 물론 눌러보면 테스트가 실행된다.

참고로 위에서 testPerformanceExample()의 부분에 회색 음영이 쳐져있고 우측에 실행시간이 표시되는 것도 확인 할 수 있다. 앞서 이야기한 퍼포먼스 테스트의 결과도 이렇게 친절하게 표시해준다.

당연히 테스트가 실패했다면 녹색 아이콘이 아닌 다른 표시가 뜰 거라고 예상 할 수 있다. 실제로 테스트 케이스가 실패하면 빨간색 엑스 아이콘이 뜬다.
이렇게 한눈에 실패한 테스트 파악이 가능하다. 남은 일은 테스트가 실패한 유닛의 버그를 수정하는 것이다.

아 그리고 중요한 말이 빠졌다.
세상에서 가장 치명적인 버그는 테스트 코드에 있는 버그이다.
겪어본 사람은 아마도 알 것이다. -_-;;;

[관련글] Swift 프로젝트의 유닛테스트(Unit Test)
[관련글] [Xcode] 비동기 루틴 유닛 테스트 (Asynchronous Unittest)

댓글 1개 :

No.1 PL K.Bryant :

안녕하세요, 구글링하다가 방문하였는데 좋은글이여서
제 블로그에 링크만 담아갑니다.
문제시 말씀해주시면 삭제하겠습니다 ㅎㅎ
- http://blog.naver.com/jegumhon/220406427674