코드에 확신을 주기 위해서 유닛 테스트를 작성한다. 우리가 작성한 코드 안에는 많은 실수와 버그들이 숨겨져 있다. 이런 실수들을 확인하고 고치기 위해 유닛 테스트가 필요하다.
Swift에서 유닛 테스트가 이뤄지는 과정은 다음과 같다.
XCText
를 import한다.XCTestCase
를 상속받는다.test
로 시작하는 함수를 실행한다.XCTAssert
로 테스트하고 싶은 내용을 작성한다.A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace.
모든 iOS 프로젝트가 최소 한 개의 타겟을 갖고 있다. 여기서는 유닛 테스트를 위한 특별한 타겟을 따로 만들 것이다.
프로젝트에서 Add a target - Unit Testing Bundle 을 선택하면 유닛 테스트를 위한 새 타겟을 만들 수 있다.
command + u
를 누르면 유닛 테스트를 실행할 수 있다.CurrencyFormatter를 테스트 하는 CurrencyFormatterTests.swift
파일을 새로 만들고 아래와 같이 코드를 작성하자. (아래 코드는 유닛 테스트를 만들기 위한 Code Snippet이기 때문에 ViewController에 관한 코드는 사용하지 않는다.)
import Foundation
import XCTest
@testable import Bankey
class Test: XCTestCase {
var vc: ViewController!
override func setUp() {
super.setUp()
vc = ViewController()
vc.loadViewIfNeeded()
}
func testShouldBeVisible() throws {
let isViewLoaded = vc.isViewLoaded
XCTAssertTrue(isViewLoaded)
}
}
클래스의 맨 위에 테스트하고자 하는 오브젝트를 정의한다.
var formatter: CurrencyFormatter!
setUp()
메소드 내에서 인스턴스화를 하거나 클래스를 만들어준다.
setUp()
메소드는 유닛 테스트를 실행할 때마다 실행이 된다.테스트를 진행할 메소드를 만든다.
func testBreakDollarsIntoCents() throws {
let result = formatter.breakIntoDollarsAndCents(929466.23)
XCTAssertEqual(result.0, "929,466")
XCTAssertEqual(result.1, "23")
}
func testDollarsFormatted() throws {
let result = formatter.dollarsFormatted(929466.23)
XCTAssertEqual(result, "$929,466.23")
}
// Test for edge case
func testZeroDollarsFormatted() throws {
let result = formatter.dollarsFormatted(0.00)
XCTAssertEqual(result, "$0.00")
}
func testZeroDollarsFormatted() throws {
let locale = Locale.current
let currencySymbol = locale.currencySymbol!
let result = formatter.dollarsFormatted(0.00)
XCTAssertEqual(result, "\(currencySymbol)0.00")
}
유닛 테스트는 Continuous Integration(CI) 내에 포함된다.
Continuous integration (CI) is the practice of automating the integration of code changes from multiple contributors into a single software project.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let loginViewController = LoginViewController()
let onboardingViewController = OnboardingContainerViewController()
let mainViewController = MainViewController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.backgroundColor = .systemBackground
loginViewController.delegate = self
onboardingViewController.delegate = self
displayLogin()
return true
}
private func displayLogin() {
setRootViewController(loginViewController)
}
private func displayNextScreen() {
if LocalState.hasOnboarded {
prepMainView()
setRootViewController(mainViewController)
} else {
setRootViewController(onboardingViewController)
}
}
private func prepMainView() {
mainViewController.setStatusBar()
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().backgroundColor = appColor
}
}
extension AppDelegate {
func setRootViewController(_ vc: UIViewController, animated: Bool = true) {
guard animated, let window = self.window else {
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
return
}
window.rootViewController = vc
window.makeKeyAndVisible()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: nil, completion: nil)
}
}
extension AppDelegate: LoginViewControllerDelegate {
func didLogin() {
displayNextScreen()
}
}
extension AppDelegate: OnboardingContainerViewControllerDelegate {
func didFinishOnboarding() {
LocalState.hasOnboarded = true
prepMainView()
setRootViewController(mainViewController)
}
}
extension AppDelegate: LogoutDeleagate {
func didLogout() {
setRootViewController(loginViewController)
}
}