Unit Testing

Seoyoung Lee·2022년 3월 27일
0

Professional iOS

목록 보기
11/12
post-thumbnail

What are unit tests?

What are Unit Tests?

  • Unit tests are small snippets of code we write to prove our software works as expected.

Why write them?

코드에 확신을 주기 위해서 유닛 테스트를 작성한다. 우리가 작성한 코드 안에는 많은 실수와 버그들이 숨겨져 있다. 이런 실수들을 확인하고 고치기 위해 유닛 테스트가 필요하다.

무엇을 테스트 할 수 있을까?

  • State of ViewControllers
  • Logic in code

How do they work?

Swift에서 유닛 테스트가 이뤄지는 과정은 다음과 같다.

  1. XCText 를 import한다.
  2. 테스트할 타겟에 내 프로젝트를 import한다.
  3. 클래스를 만들고 XCTestCase 를 상속받는다.
  4. 테스트를 시작하기 전 setup을 해둔다.
  5. test 로 시작하는 함수를 실행한다.
    1. 이 함수 안에서 XCTAssert 로 테스트하고 싶은 내용을 작성한다.
  6. 테스트 하려는 조건의 test descriptive name을 작성한다.

How to add unit tests to an existing project

Add a new target

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 를 누르면 유닛 테스트를 실행할 수 있다.
  • 유닛 테스트롤 통과하면 왼쪽에 초록색 체크 표시가 뜬다.
  • 특정 유닛 테스트를 실행하고 싶으면 원하는 유닛 테스트 함수 옆에 있는 마름모 아이콘을 클릭한다. 전체 테스트를 실행하고 싶으면 클래스 옆에 있는 아이콘을 클릭한다.

Testing the CurrencyFormatter

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)
    }
}
  • @testable : 내 프로젝트 타겟 내에 있는 모든 파일들에 접근할 수 있게 해준다.
  1. 클래스의 맨 위에 테스트하고자 하는 오브젝트를 정의한다.

    var formatter: CurrencyFormatter!
  2. setUp() 메소드 내에서 인스턴스화를 하거나 클래스를 만들어준다.

    • setUp() 메소드는 유닛 테스트를 실행할 때마다 실행이 된다.
    • 따라서 객체나 테스트 사이에의 state를 기억할 필요가 없다.
  3. 테스트를 진행할 메소드를 만든다.

    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")
    }

How unit testing affects your code

  • 유닛 테스트는 자동화된 테스트의 한 형태이다. 또한 나의 설계에 큰 영향을 주기도 한다.
  • 유닛 테스트는 documentation의 한 형태이기도 하다.
    • 유닛 테스트를 작성함으로써 독자가 내가 작성한 함수가 어떤 식으로 작동할 것으로 예상하는지 알려줄 수 있다.
    • 테스트 형식으로 된 실행 가능한 문서인 셈이다.
  • 유닛 테스트가 프로젝트 내에서 어떻게 사용되는가
    • 유닛 테스트는 Continuous Integration(CI) 내에 포함된다.

      Continuous integration (CI) is the practice of automating the integration of code changes from multiple contributors into a single software project.

Getting back into our flow

AppDelegate 리팩토링하기

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)
    }
}
profile
나의 내일은 파래 🐳

0개의 댓글

관련 채용 정보