[swift] Xcode에서 Unit Test 연습하기
진행하고 있는 프로젝트를 리팩토링 하면서, 내가 한 리팩토링이 잘 되었는지는 둘째치고 이전에 기능들은 잘 되고 있는지에 대한 불안감이 생겼다.
- 원래 되고있던 기능이 뭐지
- 제대로 되었다는것이 어떤 것을 의미하지
테스트 코드의 작성을 통해 위 두 문제를 해결할 수 있다고 해서 정리하기 시작했다.
모든 내용을 다 정리하지도 해 보지도 않았다 앞으로 필요한 테스트를 위한 기본 몸풀기 정도만 해 보았다. 추후에 해당 아티클들의 뒷 부분이 필요한 때에 다시 해 보고 정리하기로 한다.
테스트를 위한 네비게이션이 있었다. 뭐하는데 쓰는 것일까 궁금했더 곳이다. Command(⌘)+6
으로 창을 열 수 있다. 왼쪽 아래의 + 버튼을 눌러 새로운 유닛테스트를 위해 New Unit Test Target…
을 눌러 생성한다.
처음에 만들면 4개의 메소드가 있다.
- setUpWithError() : 테스트를 위해 설정
- tearDownWithError() : 테스트가 종료 된 후 삭제
- testExample() -> 삭제
- testPerformanceExample() -> 삭제
BullsEye
우리는 50으로 부터 랜덤으로 값을 생성하고, 슬라이더를 움직여 목표 값과 비슷한 값을 입력하는 게임을 테스트 하려한다.
프로젝트 설정
1 . @testable import BullsEye
를 입력해 테스트 코드 파일의 최상단에 프로젝트를 임포트 해주자.
2 . var sut: BullsEyeGame!
프로퍼티를 class BullsEyeGame
타입으로 만들어준다.
setUpWithError
super.setUp()
sut = BullsEyeGame()
sut.startNewGame()
인스턴스를 생성하고, startNewGame까지 실행한 상태로 설정한다.
tearDownWithError
sut = nil
super.tearDown()
테스트가 끝 났을 때 sut을 해제하고, tearDown 해준다.
testScoreIsComputed
점수를 계산하는 check() 메소드를 테스트 해보자. 테스트의 규칙은
- given - 입력값
- when - 테스트 대상의 메소드 실행
- then - 결과값 비교
위와 같이 정한다.
func testScoreIsComputed() {
// 1. given : targetValue에서 5 차이 난 값 입력
let guess = sut.targetValue + 5
// 2. when : 변수가 입력된 check 메소드가 실행되었을 때
sut.check(guess: guess)
// 3. then : 내가 기대한 값은 95와 scoreRound가 같은 값이어야한다.
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
testScoreIsComputedWhenGuessLTTarget
그렇다면 목표 금액보다 5 작은 값이 들어 갔을 때에도 5점 작은 95점이라는 결과 값이어야한다.
func testScoreIsComputedWhenGuessLTTarget() {
// 1. given
let guess = sut.targetValue - 5
// 2. when
sut.check(guess: guess)
// 3. then
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
실행하면 실패한다. 그 이유는 check()에 버그가 있그 때문이다. 입력받은 값을 빼 주기 때문에, 우리가 예상했던 96가 아닌 105라는 결과값을 반환했던 것이다.
디버그 창으로 가서 Test Failure Breakpoint를 추가하면, 실패한 테스트 케이스에서 디버깅이 시작된다.
잘못 작성된 check() 메소드의 let difference = guess - targetValue
을 let difference = abs(targetValue - guess)
로 바꿔준다. 이제 무조건 빼는 게 아닌 절대값의 차이를 구하게 될 것이다. 테스트 성공.
HalfTunes
검색어를 입력하면 가수를 검색해 주는 반쪽짜리 아이튠즈를 테스트 하자.
요청을 날렸을 때, 응답에 따라 잘 동작하는지에 대한 테스트 이다.
해당 프로젝트를 열고 UnitTest를 추가해 주자.
setting
import XCTest
@testable import HalfTunes
class HalfTunesSlowTests: XCTestCase {
var sut: URLSession!
override func setUp() {
super.setUp()
sut = URLSession(configuration: .default)
}
override func tearDown() {
sut = nil
super.tearDown()
}
URL요청을 날리기 위해 URLSession으로 만들어준다.
testValidCallToiTunesGetsHTTPStatusCode200
// Asynchronous test: success fast, failure slow
func testValidCallToiTunesGetsHTTPStatusCode200() {
// given : abba라고 사용자 입력하면 아래 url이 생성된다.
let url =
URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
// 1 : 비동기 테스트를 위한 expectation을 사용한다.
let promise = expectation(description: "Status code: 200")
// when : 요청을 해서 응답을 받았을 때
let dataTask = sut.dataTask(with: url!) { data, response, error in
// then
if let error = error {
XCTFail("Error: \(error.localizedDescription)")
return
} else if let statusCode = (response as? HTTPURLResponse)?.statusCode {
if statusCode == 200 {
// 2 : 응답 코드가 200 이면
promise.fulfill()
} else {
XCTFail("Status code: \(statusCode)")
}
}
}
dataTask.resume()
// 3
wait(for: [promise], timeout: 5)
}
위 테스트에서는 요청에 바로 응답하는게 아닌 비동기로 짜여진 코드를 테스트 하기 위해 비동기 테스트를 진행한다. 응답 코드가 200이면 실행을 알리는 fulfill()을 실행한다.
하지만 이 테스트는 요청이 정상이 아닐 때에는 5초동안 기다렸다가 실패로 판단한다. 만약 200 말고도 다른 결과(404error)가 있다면 테스트 결과를 받기위해 5초(더 길게 설정한다면, 더 긴시간)를 기다려야 한다.
테스트를 개선해보자.
testCallToiTunesCompletes
func testCallToiTunesCompletes() {
// given : 비동기 테스트를 위한 expectation을 사용한다.
let url =
URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
let promise = expectation(description: "Completion handler invoked")
var statusCode: Int?
var responseError: Error?
// when : 요청을 해서 응답을 받았을 때 바로 실행된다.
let dataTask = sut.dataTask(with: url!) { data, response, error in
statusCode = (response as? HTTPURLResponse)?.statusCode
responseError = error
promise.fulfill()
}
dataTask.resume()
wait(for: [promise], timeout: 5)
// then : 200 이면 테스트 통과 아니면 실패
XCTAssertNil(responseError)
XCTAssertEqual(statusCode, 200)
}
응답 결과를 가지고 통과 실패가 바로 나온다. 실제 타임아웃인 상황에 대해서만 타임아웃 테스트가 실패한다.
Result
TestCode에 대해서 다 알게 되지는 않았다. 하지만 더 공부 하는 것 보다는 내가 지금 짤 수 있는 테스트 케이스를 작성하면서 더 복잡한 테스트 코드를 짜는게 빠르게 공부할 수 있을 것 같아 이 뒷부분은 생략한다. 좀 더 테스트 코드에 익숙해지면 복잡한 부분을 진행해보자. 실제 테스트 코드를 짜면서 생긴 어려움을 추가로 정리해야겠다.
댓글
댓글 쓰기