티스토리 뷰

 

 

이전에 xcode에서 XCTest를 활용해 unit test 하는 법을 간략하게 포스팅한 적이 있다

 

 

[iOS] XCode에서 Unit Test 사용해보기

오늘은 간단한 예제를 통해서 test code를 작성해볼 것이다!UITest도 있지만 오늘은 Unit test를 한번 해보았다.   기본 UI는 저번에 구현했었던 프로필과 이름 바꾸는 예제를 활용했다 XCode에서 unit t

yanni13.tistory.com

 

 

거의 XCTest 사용법 정도 익혀본 수준이라, unit test에 필요한 테스트 코드 작성 하는 법과

해당코드를 토대로 Quick과 Nimble로도 같이 테스트를 해보려고 한다!

 

 

 

📍테스트 코드 작성하기

 

사전 조건

    • UserManager 인스턴스가 생성되어 있음
    • "testuser"라는 이름의 사용자가 이미 추가되어 있음

 

인수 조건

  • username: "testuser"

 

예상 결과

  • "testuser" 해당하는 User 객체가 반환됨
  • 반환된 User 객체의 username "testuser" 일치함

 

 

위에 사용자 조회라는 하나의 기능을 토대로 사용자 정보 조회에 관한 단위 테스트 코드를 작성해보고자 한다.

 

지금이야 간단한 사용자 정보 조회에 관한 테스트 코드를 작성했을 뿐이지만 나중에 실제 프로젝트에서 테스트 코드를 작성한다 했을 때 하나의 기능에 대해서 테스트코드가 엄청 길어질 것이다. 

 

 따라서 Given - When - Then 구조로 작성하여 테스트 코드의 흐름과 가독성을 높이고자 하였다.

구조를 잡아놓고 테스트 코드를 작성하는 것이 테스트 코드의 품질과 추후 유지보수하기에도 도움이 되기 때문이다!

 

 

Given 

  • 테스트를 하기 위한 초기 값을 정의한다.

When 

  • 테스트를 하려는 실제 동작이나 이벤트

Then

  • 예상 되는 결과나 행동의 결과를 검증한다

 

func testGetUser() throws {
        // Given 
        let username = "testuser"
        let email = "testuser@naver.com"
        
        let newUser = try user.addUser(username: username, email: email)

        // When 
        let foundUser = user.getUser(byUsername: username)
        
        // Then 
        XCTAssertNotNil(foundUser, "사용자가 목록에 존재함")
        XCTAssertEqual(foundUser?.username, username)
        XCTAssertEqual(foundUser?.email, email)
        XCTAssertEqual(foundUser?.id, newUser.id)
}

 

위에 작성된 코드로 구조를 살펴보자

 

Given 절에는 username과 email에 대한 초기 값을 정의해 두고 사용자를 추가해주고 있다.

지금 테스트 하려는 단위가 존재하는 사용자 조회에 해당하고, 사전 조건으로 "testUser"라는 이름을 가진 사용자가 존재해야 하므로 사용자를 추가해주는 것 까지가 테스트에 필요한 초기 설정이게 된다.

 

When 절에는 테스트를 하려는 실제 동작이 들어가야 한다.

우리가 지금 테스트 하고자하는 것은 존재하는 사용자 조회이기 때문에 getUser로 User정보를 가지고 온다.

 

Then 절에는 예상 되는 결과나 행동의 결과를 검증해야 하므로 사용자가 목록에 존재하면 (nil값이 아니면) true값이 나오는 XCTAssertNotNil, 조회된 사용자의 데이터를 비교해보는 XCTAssertEqual를 통해 비교해보았다.

 

 

💡Quick & Nimble

 

Quick과 Nimble은 iOS에서 사용하는 TDD를 지원하는 BDD(Behavior-Driven Development) 스타일의 테스트를 작성하는 테스트 도구이다.

 

모두 Unit Test에서 사용하며 둘은 세트로 쓰인다.  이 둘의 차이점이 무엇인지 간략하게 알아보자

 

 

Quick

  • BDD 스타일의 테스트 프레임워크
  • 테스트 구조 정의(describe-context-it)
  • beforeEach와 AfterEach를 통해 테스트 전후 작업을 관리한다.

 

Quick은 Describe, Context, It을 통해 Given - When - Then 구조에 거의 대응된다.

 

 

Describe는 테스트 대상이나 기능에서 설명하려는 동작이 포함되어야 한다.

 

Context 특정 상황이나 조건을 정의한다.

 

It는 특정 상황에서 기대되는 결과를 서술하고 그 결과를 테스트한다.

 

따라서 It 에는 테스트 결과를 검증하는 데 사용하는 Nimble을 사용해서 기대하는 결과값과 실제 값을 비교하는 코드가 작성된다.

 

final class QuickNimbleExample: QuickSpec {

    override class func spec() {
        describe("존재하는 사용자를 정보를 조회하고자 한다.") { // Given
            context("사용자 조회") { // When
                it("존재하는 사용자가 조회되어야 한다") { // Then
                }
            }
        }
    }
}

 

최종적으로 위에 코드처럼 구조에 맞춰 테스트 코드를 작성할 수 있고 desribe, context, it에 해당하는 테스트 코드를 작성함으로써 테스트 케이스의 구조를 읽기 쉽게 만들어주게 되는 것이다.

 

 

Nimble

  • XCTest에서 Assertion을 더욱 편리하게 쓸 수 있도록 하는 프레임워크
  • 테스트 결과를 검증하는데 사용된다.
expect(1 + 1).to(equal(2))
expect(1.2).to(beCloseTo(1.1, within: 0.1))
expect(3) > 2
expect("seahorse").to(contain("sea"))
expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
expect(ocean.isClean).toEventually(beTruthy())

 

공식문서에서 확인해보면 Nimble은 expect을 사용해서 예상결과를 검증한다.

Assertion보다 훨씬 더 직관적이며 다양하게 사용할 수 있는 것을 확인할 수 있다!

 

 

일반 XCTest에서 제공해주는 메서드들로 테스트를 해도 되긴 하지만, Quick과 Nimble은 BDD 스타일의 문법을 제공하기 때문에 XCTest를 사용할 때 주석으로 Given-When-Then 구조를 나눌 필요 없이 제공된 문법을 통해 코드를 더 간략하게 이해할 수 있다.

 

그리고 XCTest에서 제공하는 XCTAssert에도 많은 메서드들이 존재하지만 실제 사용되는 것들은 제한적이었기 때문에 Nimble에서 제공하는 expect 메서드가 더 직관적이고 복잡한 조건도 쉽게 이해할 수 있었기에 이를 사용해 테스트코드를 개선해보고자 하였다.

 

 

 

 


Quick & Nimble 사용해보기 

 

 

1. cocoaPods 설치 후 Pod 깔아주기

target 'UnitTestExample' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for UnitTestExample
  target 'UnitTestExampleTests' do
    inherit! :search_paths
	pod 'Quick'
	pod 'Nimble'
    # Pods for testing
  end

  target 'UnitTestExampleUITests' do
    # Pods for testing
  end

end

 

코코아팟 설치 후 Quick과 Nimble을 테스트에 해당하는 폴더 타겟 내부에 작성해 주자!

UnitTest에만 테스트 코드를 작성하고 싶었기에 UnitTestExampleTests 폴더 내부에 Quick과 Nimble을 설치해 주었다.

 

 

그리고 pod install 하면 정상적으로 팟 설치 완료!!

 

 

2.  테스트하고자 하는 파일 생성 및 import 

 

이제 Unit Test에 필요한 테스트 코드를 작성하기 위해 새 파일을 생성해주어야 한다.

 

 

이때 Swift 파일이 아닌 Unit Test Case Class로 파일을 생성해주어야 한다.

 

Unit Test Case Class는 자동으로 XCTestCase를 상속한 클래스가 생성되기 때문이다

 

 

import Quick
import Nimble
@testable import UnitTestExample

final class QuickNimbleExample: QuickSpec {

    override class func spec() {

    }
}

 

테스트하는데 필요한 프레임워크인 Quick과 Nimble을 import 해주고 UnitTestExample이라는 모듈을 테스트할 때 모듈에서 접근 가능하게 만들기 때문에 테스트하려는 대상 모듈의 내부 구현을 위해 @testable import [프로젝트 이름]을 해준 것이다

 

쉽게 말하자면 User라는 Model에 새로운 사용자를 생성한 후 사용자 조회가 정상적으로 완료되었는지를 테스트해보려면, User 구조체에 접근이 가능해야 한다.

 

User구조체에 접근하기 위해 프로젝트 전체 파일을 testable로 import 해준 것!

 

XCTest와는 다르게 Quick은 QuickSpec을 상속받으며 spec이라는 메서드 자체가 테스트 케이스가 된다.

QuickSpec 메서드를 확인해보면 XCTestCase를 상속받고 있기 때문에 override로 spec메서드를 재정의하면 테스트 실행시 spec이 자동으로 호출되어 테스트 케이스 자체가 되게 되는 것이다.

 

 

3. 테스트 코드 작성

테스트 코드를 작성하기 전에 beforeEach와 AfterEach에 대해 간략하게 짚고 넘어가자

 

BeforeEach / AfterEach

앞서 Quick의 특징 중에 beforeEach와 AfterEach를 통해 테스트 전후 작업을 관리한다고 설명했었는데 여기서 나온다.

Quick에서는 beforeEach와 AfterEach를 통해 테스트케이스가 실행되기 전과 후에 실행되는 코드를 정의한다고 생각하면 된다.

 

XCTest에서는 SetUpWithError메서드와 tearDownWithError 메서드를 통해 테스트가 실행되기 전 필요한 객체를 생성해 두고, 테스트가 끝나면 메모리 누수를 방지하기 위해 nil값을 줘서 메모리를 해제시키는 역할을 했었다 -> 이전 블로그 참고!!

 

그 역할을 Quick에서는 beforeEach와 AfterEach를 통해 구현할 수 있고 테스트 설명 블록 내에서 조건부로 사용할 수 있게 되어서 더 세밀한 테스트가 가능해지게 된 것이다.

 

 

 

코드

Quick과 Nimble에 대해 간단하게 살펴보았으니 이제 테스트 코드를 작성해 보자.

 

위에 예시와 동일하게 사용자 조회에 대한 테스트 코드에 대한 예시이다.

import Quick
import Nimble
@testable import UnitTestExample

final class QuickNimbleExample: QuickSpec {

    override class func spec() {
        var userManager: UserManager!
        beforeEach {
            userManager = UserManager()
        }
        describe("존재하는 사용자가 조회되어야 한다.") {
            // Given: 사용자 조회를 하기 위한 사용자 추가는 테스트 준비 과정
            let username = "testuser"
            let email = "testuser@naver.com"
            var newUser: User?
            
            beforeEach {
                newUser = try? userManager.addUser(username: username, email: email)
            }
            
            // When: 사용자를 조회할 때
            context("사용자 이름으로 조회할 때") {
                var foundUser: User?
                
                beforeEach {
                    foundUser = userManager.getUser(byUsername: username)
                }
                
                // Then: 조회된 유저가 존재하고 정보가 일치해야 함
                it("존재하는 사용자 객체를 반환해야 한다.") {
                    expect(foundUser).toNot(beNil(), description: "사용자가 목록에 존재함")
                    expect(foundUser?.username).to(equal(username))
                    expect(foundUser?.email).to(equal("testuser@naver.com"))
                }
            }
        }
    }
}

 

위에서 사용자 조회에 관한 테스트 코드를 Quick과 Nimble을 사용해서 변환했을 때는 위와 같다.

 

describe

사용자 조회를 하기 위한 테스트를 검증하기 전에, "testuser"라는 사용자가 존재하려면 User객체에 추가가 되어있어야 한다. 따라서 "존재하는 사용자가 조회되어야 한다."는 descibe블록 안에 초기 값들을 정의해 두고 beforeEach문 안에서 사용자를 추가해주고 있다. 

 

우리가 테스트하고자 하는 것은 사용자 추가가 아닌 조회이기 때문에 테스트 케이스가 실행되기 전에 사용자 추가가 완료되어야 하기 때문이다.

 

따라서 beforeEach를 사용해서 '사용자 조회'라는 테스트 케이스가 실행되기 전에 사용자 추가가 호출되기 때문에 사용자를 조회할 수 있는 상태가 보장된다.

 

 

Context

Context 블록은 특정 상황이나 조건을 정의하기 때문에 "사용자 이름으로 조회할 때"라는 특정 상황이 담긴 로직이 실행되어야 한다.

 

Context에서는 When 절에 해당하는 사용자 조회의 테스트 동작이 들어가면 되는데 왜 beforeEach로 감싸주고 있는지 의문이 들 수도 있다!

 

Quick의 구조는 describe 블록이 실행 -> Context 블록 실행 -> it 블록 실행 순으로 실행이 된다. 여기서 it블록에서 예상되는 테스트의 값들을 비교해 보는 과정이 들어가야 하고, it 블록이 실행되기 전에 테스트 동작인 context에서 사용자 이름으로 사용자 정보를 조회한 테스트가 항상 실행되어야 하기 때문에 보장된 상태여야 한다.

그래서 beforeEach를 사용해서 테스트를 실행한 것이다!

 

 

it

it 블록에서는 존재하는 사용자 객체를 반환해야 하기 때문에, 조회하는 사용자의 이름과 이메일이 describe 블록에서 추가한 사용자와 동일한지를 체크해줘야 한다.

 

조회한 사용자(foundUser)를 조회해서 Nil값이 아닌지 확인함으로써 사용자의 존재여부를 확인해 보았고, 추가한 사용자의 이름과 이메일이 동일한지를 foundUser 값에 접근해서 비교해 보았다.

 

 

 

테스트를 실행해 보면 위와 같이 출력되면서 테스트 성공이라고 뜬다.

 

확실히 콘솔 창에서 quick의 descibe-context-it의 구조가 XCTest보다 어떤 동작을 테스트 하고 어떤 값이 반환되어야 하는지를 명확하게 알 수 있었다.

 

 

🛠️ 마주친 오류들 

 

처음에 import까지 해주고 테스트 코드를 일부 작성한 후 테스트를 돌리니 아래와 같은 오류가 나왔다

접근권한에 대한 오류가 발생한 것이다!

 

이때 프로젝트 타겟에서 Build Setting에 들어가 보자.

 

그럼 위와 같이 User Script Sandboxing을 No로 변경해 주면 위에 오류는 사라진다~

 

 

 


참고

https://github.com/Quick/Quick/blob/main/Documentation/ko-kr/QuickExamplesAndGroups.md

https://github.com/Quick/Nimble

https://zeddios.tistory.com/1245

https://silver-g-0114.tistory.com/142

 

'iOS' 카테고리의 다른 글

[iOS] RxSwift 예제로 알아보기 (2)  (3) 2024.11.07
[iOS] RxSwift 알아보기 (1)  (2) 2024.10.13
[iOS] 퀵헬프 주석  (1) 2024.09.09
[iOS] testflight 사용해보기  (0) 2024.08.18
[xcode] pod update를 하면서 마주친 오류들  (2) 2024.08.04
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함