[iOS] TDD

한상진·2022년 1월 19일
0

TDD

목록 보기
1/1
post-thumbnail



📕TDD란?

TDD는 Test-Driven-Development의 약자로, '테스트 주도 개발' 이라고 합니다.
반복적인 테스트를 이용한 소프트웨어 방법론으로, 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 사이클을 반복하여 구현합니다.

📌TDD 사이클

TDD는 아래와 같은 사이클을 반복합니다.

  1. 테스트에 실패하는 코드를 작성한다.
  2. 테스트를 통과하는 실제 코드를 작성한다.
  3. 코드를 리팩토링 한다.
  4. 이를 반복한다.

위와 같은 사이클을 'TDD 사이클' 이라고 부르고, 이를 통해 개발이 테스트에 의해 주도되기 때문에 철저하고 정확하게 코드를 테스트할 수 있으며, 불필요한 설계를 피할 수 있고 요구사항에 충족하는지 제대로 확인할 수 있습니다.

📌TDD를 하는 이유

앱이 예상대로 잘 작동이 되는지 테스트 하는 것은 좋지만, 모든 테스트가 좋은 것은 아닙니다.
좋은 테스트는 테스트가 실패할 수 있어야하며, 반복할 수 있어야하며, 빠르게 작동하고 유지보수가 가능해야합니다.
좋은 테스트가 아닌 경우, 오히려 테스트를 위해 준비하는 시간이 낭비일 수 있습니다.

TDD는 좋은 테스트를 위한 방법들을 제공합니다.
물론 좋은 테스트를 하기 위해 반드시 TDD를 해야하는 것은 아니지만, TDD는 확실히 좋은 테스트를 제공할 수 있는 개발 방식입니다.

  • 실패하는 테스트를 작성합니다. 실패하지 않는 테스트는 유용하지 않습니다. 심지어 실패하지 않는 테스트는 CPU시간을 낭비합니다.
  • 새 테스트를 실행하려면 이전의 다른 모든 테스트가 통과되어야 합니다. 이 작업은 테스트가 반복 가능하도록 합니다. 작업 중인 단일 테스트를 실행하는 것이 아니라 모든 테스트를 지속적으로 실행합니다.
  • 모든 테스트를 자주 실행하면 테스트를 빠르게 실행할 수 있습니다. 모든 테스트는 몇 초 내로 빠르게 실행되어야합니다.
  • 리팩토링할 때는 본 코드와 테스트 코드를 모두 업데이트합니다. 이를 통해 테스트가 지속적으로 최신 상태로 유지됩니다.

📌무엇을 테스트 해야하는가?

반드시 더 광범위하게 테스트를 했다고 해서 앱이 더 잘 테스트되었다고 할 순 없습니다.
테스트는 해야할 것과 하지 말아야할 것이 구분되어 있습니다.

하지 말아야할 것 이미 생성된 코드에 대해서는 테스트하지 않습니다.
예를 들어, getter와 setter에 대해선 테스트할 필요 없습니다.
Swift에서 이미 잘 구성되어있기 때문에 믿고 넘어가도 됩니다.

컴파일러에 의해 잡힐 수 있는 문제들에 대해서는 테스트하지 않습니다.
테스트된 문제가 에러나 경고를 일으키는 경우, Xcode가 잡아줄 것입니다.

서드파티 프레임워크 등과 같이 의존성이 있는 코드는 테스트하지 않습니다.
이는 프레임워크 작성자에게 테스트에 대한 책임이 있기 때문입니다.
예를 들어, UIKit클래스에 대해 테스트할 필요 없습니다. UIKit에 대한 문제는 UIKit 개발자에게 책임이 있습니다.
다만, 여러분이 작성한 커스텀 서브클래스들에 대해서는 여러분에게 책임이 있기 때문에 테스트를 해야합니다.
해야 할 것 자동화된 방식으로 잡아낼 수 없는 코드의 경우 테스트를 진행합니다.

예외적으로, 프레임워크가 작동하는 방식을 결정하기 위해 테스트를 작성하는 것은 괜찮지만 그 테스트를 유지할 필요는 없고, 오히려 나중엔 삭제해야 합니다. 추가적으로, 서드파티 코드가 안정적으로 잘 작동되는지에 대한 테스트를 작성하는 것도 괜찮습니다.
하지만 위의 예외적인 테스트를 작성할 때는 해당 라이브러리/프레임워크를 정말로 사용할 것인지 여부를 면밀히 조사하고, 더 나은 방법은 없을지 생각해봐야합니다.

📌TDD는 너무 오래 걸린다?

TDD에 대한 가장 일반적인 불만은 시간이 너무 오래 걸린다는 것입니다.
다행히 TDD는 익숙해지면 더욱 빨라지지만, 사실 테스트를 전혀 작성하지 않았을 경우와 비교해본다면 당연히 궁극적으로 더 많은 코드를 작성하게 되고 개발 초창기에 더 많은 시간이 걸리게 될 것입니다.

하지만 개발은 첫 번째 버전의 코드만 작성을 하는 것이 아니고 시간이 지남에 따라 새로운 기능 추가, 기존 코드 수정, 버그 수정 등의 개발 이후의 유지보수 작업, 업데이트 작업이 있기 때문에 장기적으론 TDD를 따르는 것이 오히려 시간이 훨씬 적게 소모됩니다.
버그가 적고 유지보수가 더 쉽도록 코드를 작성하기 때문입니다.
또한 비용적으로 고려했을 때도 코드에 문제가 발견되지 않을 수록 훨씬 더 이익을 볼 수 있습니다.

📌언제 TDD를 해야하는가?

TDD는 모든 시점에서 사용할 수 있습니다. 하지만 TDD를 시작하는 방법과 위치는 프로젝트의 상태에 따라 다릅니다.
일반적으로 앱이 몇 개월 이상 지속되고, 여러 릴리즈가 있거나 복잡한 로직이 필요한 경우 TDD를 사용하지 않는 것보다 사용하는 것이 좋습니다.

해커톤, 테스트 프로젝트 또는 일시적으로 다른 것을 위한 앱을 만드는 경우엔 TDD가 의미가 있는지 생각해봐야합니다.


📕TDD 사이클

TDD는 아래의 그림과 같이 간단한 프로세스로 요약됩니다.

Red - 앱 코드를 작성하기 전에 실패하는 테스트를 작성한다
Green - 테스트를 통과하기 위한 최소한의 코드를 작성한다.
Refactor - 앱 코드와 테스트 코드를 모두 정리한다.
Repeat - 모든 기능이 구현될 때까지 이 사이클을 다시 수행한다.

이 사이클은 "Red-Green-Refactor Cycle" 이라고 불리기도 합니다.

이처럼 사이클이 색으로 구분되어있는 이유는 Xcode를 포함한 대부분의 코드 편집기에 표시되는 색상이기 때문입니다. (빨강 -> 에러, 녹색 -> 성공)

📌TDD 사이클 간단 예제

Playground에서 TDD 사이클을 이해해보는 간단한 예제를 해보겠습니다.

우선 TDD를 하기 위해서는 XCTest를 import 해주고, 테스트 클래스를 XCTestCase의 하위 클래스로 지정해주어야합니다.

import XCTest

class CashRegisterTests: XCTestCase {

}

그리고 이 테스트 클래스를 실행하기 위해선 코드를 한 줄 추가해야합니다.

CashRegisterTests.defaultTestSuite.run()

1. Red

Red 단계에서는 테스트에 실패하는 코드를 작성해야합니다.

class CashRegisterTests: XCTestCase {
    func testInit_createsCashRegister() {
    	XCTAssertNotNil(CashRegister())
    }
}

이전의 테스트 클래스에 함수 하나를 추가했습니다.
XCTAssertNotNil은 전달된 것이 무엇인지 나타내주는 함수이고, Nil값이 전달된다면 테스트가 실패한 것으로 표시됩니다.
이 함수에 전달된 CashRegister는 존재하지 않는 클래스이기 때문에, 실패하게 됩니다.


2. Green

Red 단계에서 테스트가 실패된 것이 확인되었으므로, Green 단계에서는 테스트가 성공하도록 합니다.

class CashRegister {
}

존재하지 않았던 CashRegister 클래스를 생성합니다.
이로써 앞서 Red단계에서는 실패했던 테스트가 Green단계에서는 통과가 되고, 코드를 실행해보면 테스트가 통과되었다는 로그가 나타나게 됩니다.

Test Suite 'CashRegisterTests' started at 2022-01-19 17:24:21.270
Test Case '-[__lldb_expr_1.CashRegisterTests testInit_createsCashRegister]' started.
Test Case '-[__lldb_expr_1.CashRegisterTests testInit_createsCashRegister]' passed (0.009 seconds).
Test Suite 'CashRegisterTests' passed at 2022-01-19 17:24:21.283.
	 Executed 1 test, with 0 failures (0 unexpected) in 0.009 (0.013) seconds

3. Refactor

Refactor단계에서는 앱 코드와 테스트 코드를 모두 정리합니다. 이를 통해 코드를 유지보수할 수 있습니다.
리팩토링에 유용한 몇 가지 항목이 있습니다.

중복로직 : 제거할 중복 프로퍼티, 메소드, 클래스가 있는지?

설명 : 주석은 코드가 "어떻게 작동되는지"가 아니라 "왜 작동됐는지"를 작성해야합니다. 작동 방식을 설명하는 주석은 제거합니다. 광범위한 작업을 하는, 길게 네이밍 된 메소드를 세부작업을 하는 메소드로 분리하고 네이밍을 더 명확하게 해야합니다. 또한 코드를 더 잘 구조화해야합니다.

코드스멜 : 하드코딩된 문자열이나 문제를 일으킬 수 있는 로직을 수정해야합니다.

본 예제의 CashRegister와 CashRegisterTests는 로직이 별로 없고 리팩토링할 것도 없기 때문에 넘어가도 괜찮습니다.


4. Repeat

Repeat단계에서는 다시 위의 3가지 단계를 반복합니다.

이렇게 TDD의 첫 번째 사이클을 돌았으며, 추가적인 기능구현을 해나가며 TDD사이클을 적용하면 됩니다.

profile
공부방

0개의 댓글