티스토리 뷰

iOS

[iOS] Unit Test

Peppo 2022. 7. 10. 19:44
728x90

Unit Test  (Method Test)

 

하나의 기능을 테스트 하는것

작성한 코드가 의도한대로 동작하는지 확인하는 가장 작은 단위의 테스트 입니다.

 

Unit Test 를 왜 하나요? 

  • 기능을 세세하게 나누어 테스트 함으로 문제되는 부분을 쉽게 파악할 수 있습니다.
  • 전체 프로그램을 빌드하는 대신 작은 단위로 빌드하기 때문에 시간절약도 할 수 있습니다.

테스트를 하기위해 몇가지 원칙이 있지만, 여기서는 FIRST 원칙에대해 정리해 보겠습니다.

 

FIRST원칙

 

Fast: 빠르게 

  - 테스트가 빠르게 실행되어야 합니다.

 

Independent / Isolated: 독립적으로

  - 서로에게 영향을 주면 안됨.

 

Repeatable: 반복 가능하게

  - 언제든 같은결과가 나와야 함.

 

Self-Validating: 스스로 검증하는

  - 테스트는 자동화가 되어야 합니다. 결과는 '성공' , '실패' 로만 나타냅니다.

 

Timely: 적시에

  - 테스트를 언제 작성해야 하는지 (기능을 구현 -> 테스트 코드 작성)

or

   (기능구현과 동시에 테스트코드 작성 -> TDD 라고 합니다. )

 


Unit Test 시작

 

 

신규프로젝트를 만들때 include Test 부분을 체크하면 아래와같이 Test 파일이 생깁니다.

 

 

Tests 파일로 이동하면 아래의 코드가 보일 겁니다.

 

이미 프로젝트를 진행중이라면

 

새 파일 만들기 (Cmd + N) 로 Unit Test Case Class 를 만들어줍니다.

 


Unit Test 동작방식

 

기본적으로 XCTestCase를 상속받고, 

아래의 메소드들을 갖고 있습니다.

// 1
setUpWithError()  
// 초기화 코드를 작성하는 함수 → 각 테스트 함수의 호출전에 호출되는 함수 입니다.

// 3
*tearDownWithError()
// 해제 코드를 작성하는 함수 → 테스트가 다 끝나면 이곳에 해제를 해주면 됩니다. 

  // *tearDown: 파괴하다, 해제하다.

// 2
testExample()
// 테스트 케이스를 작성하는 함수 → 테스트하려는 함수가(기능) 올바른 결과를 내는지 확인하는 함수 입니다.

// 다음에 공부하고 정리해보겠습니다. 여기서는 개념만 !
testPerformanceExample()
// 성능 테스트 케이스를 작성하는 함수 → 시간을 측정하는 코드를 작성하는 함수 입니다.

 

 순서

 

1. setUpWithError() 에서 테스트 하고자 할 Class를 초기화 해주고

2. testExample() 에서 테스트 코드가 실행 되며

3. 테스트가 완료 되면 tearDownWithError() 에서 해제 시켜줍니다.

 

 

+ 추가 (테스트 메서드 동작순서)

아래는 테스트 메서드가 동작하는 순서 입니다.

공식문서

 

해당 메서드가 어떻게 호출되는지는 맨 하단에 코드 및 출력 예시가 있으니 참고하면 좋을것 같아요~!


Unit Test 작성해보기

 

먼저 struct 또는 class에 테스트 해볼 기능(func)을 먼저 만들어 보겠습니다.

2글자 이상 검색어를 입력해야 true를 반환해주는 검색어 유효성검사를 하는 기능이에요.

 

// SearchValidator.swift

import Foundation

class SearchValidator {
    
    func isValidSearch(input: String) -> Bool {
        if input.count > 1 {
            return true
        } else {
            return false
        }
    }
}

 

여기서 isValidSearch() 메소드를 테스트 해보려고 합니다.

UnitTests 파일로 가서 순서대로 봅시다

 

먼저 초기화를 해줘야하니까 setUpWithError() 부분에서 초기화를 시켜줍니다. 

(부가 설명은 주석처리 해놓았어요)

 

// UnitTestsTests.swift

import XCTest
@testable import UnitTests  //  ← @testable import [프로젝트 이름]

class SearchValidatorTests: XCTestCase {

    // sut - systemUnderTest 의 줄임말
    var sut: SearchValidator!
    
    override func setUpWithError() throws {
        // 테스트를 하기 위해 초기화 시켜주기.
        
        // 여러 테스트코드가 있을때 변수의 값이 변해 테스트가 겹치지 않게 하기위함.
        try super.setUpWithError()
        sut = SearchValidator()
    }

 

다음 테스트코드를 작성해야죠.

testExample() 이 부분인데 이름을 정하는 규칙은 팀, 회사 마다 다 다르다고 해요 

 

저는  test_[class,struct이름]_[테스트내용]_[결과 또는 test_테스트메소드이름] 규칙으로 써보겠습니다. 

 

    func testExample() throws {
       // 테스트 코드 작성
        
        /*
        테스트 메소드 이름 작성 방법
        test_class,struct이름_테스트내용_결과 또는 test_테스트메소드이름 (※ 팀에 따라 다름 주의)
         
        ex)
          1. func test_SearchValidator_WhenValidStringProvided_ShouldReturnTrue() { }
          2. func test_isValidSearch() { }
        */
    }
    
    func test_SearchValidator_WhenValidStringProvided_ShouldReturnTrue () {
        // 1. given - 테스트에 필요한 값들 생성
        let input = "밥"
        
        // 2. when - 테스트 할 코드 실행
        let result = sut.isValidSearch(input: input)
        
        // 3. then - 테스트의 결과 출력
        XCTAssertFalse(result)
    }

 

테스트 형식은 아래와 같이 주석으로 구분해놓고 작성해 주면 좋습니다.

given - 테스트에 필요한 값 

when - 테스트 할 코드

then  - 테스트의 결과 값

 

여기서 XCTAssert = 결과는? 이라고 이해해 주시면 될거 같아요.

아래처럼 자동완성으로 괄호안에 들어갈 값이 XCTAssert~~에 적합하면 테스트 성공, 아니면 실패로 나오게 됩니다.

 

XCTAssertFalse(_ expression:) 을 예시로 들어볼게요

 

given

input ("밥")으로 들어가는 input.count가 2 이하이기 때문에 return false를 반환하겠죠?

(왼쪽 isValidSearch 메소드 참고)

 

when

반환값을 변수 result에 넣어준 후 .

 

then

XCTAssertFalse(result)의 뜻은 → 결과는 False 가 맞니? 

 

후 아래 재생버튼(거터)을 눌러주거나 (Cmd + U) 를 눌러주면 테스트가 시작됩니다.

 

 

결과는 성공!!

 

 

만약 틀리면 아래와 같이 떠요. (XCTAssertTrue( ) 를 사용했습니다.)

 

 

마지막으로 테스트가 끝났으니 해제 시켜줘야겠죠.

 

override func tearDownWithError() throws {
        // 테스트가 끝났으니 해제 시켜주기.
        sut = nil
    }

 

이렇게하면 테스트가 종료됩니다.

 


오늘은 Unit Test에 대해 공부해 봤는데요.

 

물론 모든 기능에 대해 UnitTest를 하는게 좋겠지만,

 

시간이 많이 소요된다는 점이 있어 최소한 '핵심기능'을 담당하는 부분만이라도 UnitTest를 만드는 습관을 들여야 겠다는 생각이 들었습니다. 

 

 


UnitTest 종합 코드

 

gitHub 링크

 

// UnitTestsTests.swift

import XCTest
@testable import UnitTests  //  ← @testable import [프로젝트 이름]

class SearchValidatorTests: XCTestCase {

    // sut - systemUnderTest 의 줄임말
    var sut: SearchValidator!
    
    override func setUpWithError() throws {
        // 테스트를 하기 위해 초기화 시켜주기.
        
        // 여러 테스트코드가 있을때 변수의 값이 변해 테스트가 겹치지 않게 하기위함.
        try super.setUpWithError()
        sut = SearchValidator()
    }

    override func tearDownWithError() throws {
        // 테스트가 끝났으니 해제 시켜주기.
        sut = nil
    }

    func testExample() throws {
       // 테스트 코드 작성
        
        /*
        테스트 메소드 이름 작성 방법
        test_class,struct이름_테스트내용_결과 또는 test_테스트메소드이름 (※ 팀에 따라 다름 주의)
         
        ex)
          1. func test_SearchValidator_WhenValidStringProvided_ShouldReturnTrue() { }
          2. func test_isValidSearch() { }
        */
//        test_SearchValidator_WhenValidStringProvided_ShouldReturnTrue()
        
    }
    
    func test_SearchValidator_WhenValidStringProvided_ShouldReturnTrue () {
        // 1. given - 테스트에 필요한 값들 생성
        let input = "밥"
        
        // 2. when - 테스트 할 코드 실행
        let result = sut.isValidSearch(input: input)
        
        // 3. then - 테스트의 결과 출력
        XCTAssertFalse(result)
    }

//    func testPerformanceExample() throws {
//        // This is an example of a performance test case.
//        self.measure {
//            // Put the code you want to measure the time of here.
//        }
//    }

}

 

 


UnitTest 실행순서

 

override class func setUp() {
            // This is the setUp() class method.
            // It is called before the first test method begins.
            // Set up any overall initial state here.
            super.setUp()
            print("*** called: override class func setUp()")
        }

        override func setUpWithError() throws {
            // Put setup code here. This method is called before the invocation of each test method in the class.
            // This is the setUpWithError() instance method.
            // It is called before each test method begins.
            // Set up any per-test state here.
            try super.setUpWithError()
            print("*** called: override func setUpWithError()")
        }
        
        override func setUp() {
            // This is the setUp() instance method.
            // It is called before each test method begins.
            // Use setUpWithError() to setu up any per-test state,
            // unless you have legacy tests using setUp().
            super.setUp()
            print("*** called: overried func setUp()")
        }
        
        func testMethod1() throws {
            // This is an example of a functional test case.
            // Use XCTAssert and related functions to verify your tests produce the correct results.
            // This is the first test method.
            // Your testing code goes here.
            
            print("*** called: func testMethod1()")
            
            addTeardownBlock {
                // Called when testMethods1() end.
                print("*** called: addTeardownBlock in testMethod1")
            }
            
            print("*** called: func testMethod1() after")
        }
        
        func testMethod2() throws {
            // This is the second test method.
            // Your testing code goes here.
            
            print("*** called: func testMethod2()")
            
            addTeardownBlock {
                // Called when testMethod2() ends.
                
                print("*** called: addTeardownBlock1 in testMethod2")
            }
            
            addTeardownBlock {
                // Called when testMethod2() ends.
                
                print("*** called: addTeardownBlock2 in testMethod2")
            }
        }
        
        override func tearDown() {
            // This is the tearDown() instance method.
            // It is called after each test method completes.
            // Use tearDownWithError() for any per-test cleanup,
            // unless you have legacy tests using tearDown().
            print("*** called: overried func tearDown()")
            super.tearDown()
        }

        override func tearDownWithError() throws {
            // Put teardown code here. This method is called after the invocation of each test method in the class.
            // This is the tearDownWithError() instance method.
            // Is it called after each test method completes.
            // Perforn any per-test cleanup here.
            try super.tearDownWithError()
            print("*** called: override func tearDownWithError()")
        }
        
        override class func tearDown() {
            // This is the tearDown() class method.
            // It is called after all test methods complete.
            // Perform any overall cleanup here.
            print("*** called: override class func tearDown()")
            super.tearDown()
        }
        
        
        
        /*
*** called: override class func setUp()
Test Case '-{프로젝트} testMethod1]' started.
*** called: override func setUpWithError()
*** called: overried func setUp()
*** called: func testMethod1()
*** called: func testMethod1() after
*** called: addTeardownBlock in testMethod1
*** called: overried func tearDown()
*** called: override func tearDownWithError()
Test Case '-{프로젝트} testMethod1]' passed (0.002 seconds).
Test Case '-{프로젝트} testMethod2]' started.
*** called: override func setUpWithError()
*** called: overried func setUp()
*** called: func testMethod2()
*** called: addTeardownBlock2 in testMethod2
*** called: addTeardownBlock1 in testMethod2
*** called: overried func tearDown()
*** called: override func tearDownWithError()
Test Case '-{프로젝트} testMethod2]' passed (0.001 seconds).
*** called: override class func tearDown()
        */

 

출처

https://zdodev.github.io/xctest/Xcode-XCTest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%8B%A4%ED%96%89-%EC%88%9C%EC%84%9C-%EC%A0%95%EB%A6%AC/

 

XCode XCTest 테스트 메서드 실행 순서 정리

XCTest 프레임워크

zdodev.github.io

 

728x90

'iOS' 카테고리의 다른 글

[iOS] Responder Chain, FirstResponder  (0) 2022.07.17
[Swift] ~= 연산자  (0) 2022.07.13
[Swift] 익스텐션 (Extensions)  (0) 2022.07.01
[Swift] 중첩 타입 (Nested Types)  (0) 2022.06.24
[iOS] indexPath.row , IndexPath.item 의 차이점  (0) 2022.06.22