Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TDD - Test Expresions #42

Open
samsung-ga opened this issue Sep 12, 2022 · 0 comments
Open

TDD - Test Expresions #42

samsung-ga opened this issue Sep 12, 2022 · 0 comments
Labels

Comments

@samsung-ga
Copy link
Owner

TDD - Test Expresions

레이먼드 아저씨 책 - iOS Test-Driven Development by Tutorials 의 네번째 챕터 Test Expressions에서 배운 내용

배운 내용

  • XCTAssert functions
  • UIViewController testing
  • Code Coverage
  • Test deubgging

XCTAssert

종류

  • Equailty : XCTAssertEqual XCTAssertNotEqual
  • Truthiness : XCTAssertTrue XCTAssertFalse
  • Nullability : XCTAssertNil XCTAssertNotNil
  • Comparison : XCTAssertLessThan, XCTAssertGreaterThan, XCTAssertLessThanOrEqual, XCTAssertGreaterThanOrEqual
  • Erroring: XCTAssertThrowsError, XCTAssertNoThrow

  • XCTest는 실패가 없는한 통과로 간주한다. 따라서, asserts가 없다면(아무것도 테스트하지 않았어도), test는 성공으로 마크된당
  • 모든 XCTAssert는 description을 쓸 수 있다. 테스트 실패시 실패처리에 함께 나온다.

XCTAssertTrue, XCTAssertFalse

  func testModel_whenStarted_goalIsNotReached() {
    XCTAssertFalse(sut.goalReached, "goalReached should be false when the model is created")
  }

  func testModel_whenStopsReachGoal_goalIsReached() {
    // given
    sut.goal = 1000

    // when
    sut.steps = 1000

    // then
    XCTAssertTrue(sut.goalReached)
  }

Testing Errors

  • XCTAssertThrowsError, XCTAssertNoThrow
  • 메소드에 throws 키워드를 붙인 후, 마찬가지로 위 테스트 메소드를 사용한다.

View controller testing

  • ViewController를 테스팅하기 위해서는 view와 conrol을 테스트하는 것이 아닌, viewcontroller의 상태로직을 검토하는 목적이다.
  • 기능 테스트는 UI와 로직 메소드를 분리하는 것이 핵심이다.
  • ViewModel을 controller으로부터 분리하는 것이 로직을 unit-testable하게 만들 수 있다. (Like, VIPER, MVVM)

viewcontroller에서 테스트하는 것들

  • Action에 의한 로직(State) 변경 로직 테스트
  • 이 때, Action은 View가 처음 로드되었을 때도 해당 (초기 State를 잘 가지고 있는 지 테스트)

viewcontroller를 호출하는 방법

  • 앱이 실행될때, viewcontroller의 view를 호출하면 이미 view가 로드되었기 때문에, view를 이용한 코드되어도 문제가 없다.
  • 하지만 테스트를 할 경우, viewcontroller의 view를 load해주어야한다. (viewcontroller의 생명주기를 테스트할 때는 수동으로 만들어주어야함)
  • 3가지 방법이 있다.
    • Host App 사용하기
    • viewDidLoad 호출하기 (viewcontroller 초기화함수에 UI 초기화하기)
    • 스토리보드를 통해 가져오기
  1. Host App 사용하기

스크린샷 2022-09-12 오전 11 37 05

  • Allow testing Host Application APIs 를 체크하면 테스트 작업을 실행할 때, 시뮬레이터나 디바이스에서 호스트앱이 실행된다.
  • 테스트를 시작하기 전에 앱이 로드될 때까지 대기하며, 테스트는 앱의 컨텍스트에서 실행된다.
  • 따라서, 테스트에서 UIApplication 객체와 전체 View 계층에 접근할 수 있게 된다.
// ViewControllers.swift
import UIKit
@testable import FitNess

func getRootViewController() -> RootViewController {
  guard let controller =
    (UIApplication.shared.connectedScenes.first as? UIWindowScene)?
    .windows
    .first?
    .rootViewController as? RootViewController else {
    assert(false, "Did not a get RootViewController")
  }
  return controller
}
  • app의 window에서 rootViewController를 얻는다.
  • rootViewController를 통해 다른 viewcontrollers도 얻기 위한 목적이다.
import UIKit
@testable import FitNess

extension RootViewController {
  var stepController: StepCountController {
    return children.first { $0 is StepCountController }
      as! StepCountController
  }
}
  • rootViewController에서 stepController를 얻는다.
override func setUpWithError() throws {
  try super.setUpWithError()
  let rootController = getRootViewController() // ✅ 
  sut = rootController.stepController // ✅
}
  • viewcontroller가 UIApplication에서 온 것으로, view는 이미 로드된 상태이다.
  1. viewDidLoad() 호출
override func setUpWithError() throws {
  try super.setUpWithError()
  sut = StepCountController() // ✅
}


func testController_whenCreated_buttonLabelIsStart() {
  // given
  sut.viewDidLoad() // ✅
  
  // then
  let text = sut.startButton.title(for: .normal)
  XCTAssertEqual(text, AppState.notStarted.nextStateButtonLabel)
}
  • viewDidLoad 메소드를 호출하여 viewcontroller의 생명주기를 강제로 실행한다.
  1. 스토리보드를 통해 가져오기
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let stepController = storyboard.instantiateViewcontroller(withIdentifier: "stepController") as! StepCountController
stepController.loadViewIfNeeded()  // ✅
  • loadViewIfNeeded() 를 통해 view를 로드함 (이 메소드는 더 알아봐야할듯)

Test ordering matters (테스트 케이스 순서 중요함)

  • viewcontroller의 생명주기 안에 메소드가 있는 경우 등 테스트 순서도 테스트 성공에 영향을 미친다. (중요함)
  • 만일, viewcontroller를 다시 세팅해주지 않는다면, 테스트 각각은 성공할지라도 전체 테스트를 돌릴 때 테스트가 실패할 수 있다.
  • viewcontroller의 상태와 UI 재세팅을 해주어야한다.

Randomized order

original-2

  • Test action에서 테스트 순서를 랜덤하게 만들어 발견하지 못한 테스트 의존성을 찾을 수 있다.

Code coverage

original

  • Test action에서 Options 탭을 보면, Code Coverage 체크 박스를 선택할 수 있다.
  • 테스트를 실행한 후, Report Navigator를 보면 Build, Coverage, Log 3가지를 확인할 수 있다.

스크린샷 2022-09-12 오후 12 37 04

  • Test Coverage의 기준은 테스트 실행 시, 실행되는 코드줄 수를 의미한다.
  • TDD를 제대로 따랐다면 100%를 차지한다.

original-3

  • 위에서 파일을 선택하여 들어가면 swift파일별로 커버러지가 표시된 코드가 나온다.
  • 숫자는 줄이 실행된 횟수를 의미한다.
  • 빨간색과 숫자 0은 테스트를 해야한다는 것을 의미한다.
  • 흰색으로 빗금 친 부분은 해당 줄은 코드가 일부만 실행되었다는 것을 의미한다. 커서를 올려다보면, 테스트가 된 부분은 초록색, 안 된 부분은 빨간색으로 나타난다.

Debugging tests

  • 테스트를 디버깅할 때는 "내가 테스트 조건과 테스트 결과를 제대로 주었나"를 생각해야한다.
    • 즉, given과 then에 초점을 맞추어야한다.
  • 위 사항을 체크해도 무언가 이상하다면, test case가 실행되는 순서를 확인한다.
  • test case가 실행되는 순서에 따라 이상해진다면 state의 초기화가 제대로 이루어지지 않는 것이다.

test breakpoints

original-4

  • 테스트가 실패일 경우 Breakpoint를 찍어 프로퍼티들을 확인할 수 있다. (오.. 신기)

커버리지 높이기

var caught: Bool {
  return distance > 0 && nessie.distance >= distance
}

// ✅ 테스트 코드
func testModel_whenStarted_userIsNotCaught() {
  XCTAssertFalse(sut.caught)
}
  • distance > 0 만 테스트가 되고 뒤 코드는 돌아가지 않는 경우가 있음
func testModel_whenUserAheadOfNessie_isNotCaught() {
    // when
    sut.distance = 10000
    sut.nessie.distance = 100

    // then
    XCTAssertFalse(sut.caught)
}

func testModel_whenNessieAheadofUser_isCaught() {
    // given
    sut.nessie.distance = 1000
    sut.distance = 100

    XCTAssertTrue(sut.caught)
}
  • 모든 케이스들을 테스트해주어야 테스트 커버러지가 커버됨

정리

  • XCTAssert 메소드 다양함.
  • viewcontroller 호출하는 방법
    • Host App 이용하기 - UIApplication에서 rootViewController로 접근
    • viewDidLoad 호출
    • 스토리보드를 통해 불러온 후 loadViewIfNeeded 호출
  • 테스트 케이스 실행 순서가 테스트 성공여부에 양향을 끼침 -> Randomize 체크
  • 테스트 커버리지 기능
  • 테스트 실패 시 BreakPoint 찍어서 확인하기
@samsung-ga samsung-ga added the TDD label Sep 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant