본문 바로가기

EXPERIENCE/iOS

[Xcode/iOS] SwiftUI XCTest를 이용하여 기능과 UI 유닛/단위테스트(Unit Test) 구현하기 (with. TDD)

728x90
728x90

 

 

 

 

 

728x90

 

 

 

 

 

오늘은 예전부터 해야지해야지 했던 TDD를 아주 간단하게만 사용해보았다.

비동기랑 UI부분도 유닛테스트가 가능하다고 하는데 비동기는 추후 좀 더 큰 프로젝트를 진행하면서 사용해볼예정이고,

이번에는 숫자야구게임 프로젝트를 만들어서 간단히 기능 부분이나 UI부분만 Unit Test를 찍먹 해보려고한다!

코드는 Apple에서 제공하는 유닛 테스트 프레임워크인 XCTest를 이용해 작성하였다 :)

 

 

 

 

 

 

프로젝트 동작 결과화면

 

 

 

 

 

 

 

 

Github
 

GitHub - sohay19/TDD: practice TDD

practice TDD. Contribute to sohay19/TDD development by creating an account on GitHub.

github.com

 

 

 

 

 

 

XCTest, Unit Test 작성 전 알아보기

 

  • TDD
- TDD란 Test Driven Development의 약자로 "테스트 주도 개발"이라는 의미이다
- "테스트 케이스 고려 > 테스트 케이스 작성 > 리팩토링"의 형식으로 진행된다
- 여기서 테스트 케이스는 독립적으로 진행될 수 있는 가장 작은 단위(Unit)로 작성한다

 

  • 테스트 케이스 생각하기
- 가장 큰 테스트의 목표를 설정하고, 이를 기반으로 어떤 케이스들을 테스트 할 것인지 미리 생각한다 
- "버튼을 누르면 출력되는 결과가 맞는지 확인한다"와 같은 독립적인 작은 기능을 하나의 테스트 단위로 설정한다

 

  • Test 메소드 이름은?
항상 test로 시작하며 해당 메소드가 테스트하는 내용을 기반으로 이름을 설정한다

 

 

 

 

 

 

 

 

Test를 포함한 프로젝트 생성

빨간 박스의 "Include Tests"를 체크하면 자동으로 테스트가 포함된 프로젝트를 생성할 수 있다

 

- 상단의 마름모+체크 표시 버튼을 클릭한다
- 각 부분들을 선택하여 기능, UI 등 원하는 Unit Test를 진행할 수 있다

 

 

 

 

 

 

 

 숫자야구 관리 클래스
class BullsandCows {
    private var answer:[String] = []
    private var score:[String] = []
    // 정답 여부
    var isCorrect:Bool {
        return score == ["S", "S", "S"]
    }
    
    init() {
        makeAnswer()
    }
    
    // 정답 입력
    func inputAnswer(_ input:String) {
        // 정답 리셋
        answer.removeAll()
        // 입력 값으로 정답 설정
        _ = input.map{ answer.append(String($0)) }
    }
    
    // 정답 생성기
    func makeAnswer() {
        // 정답 리셋
        answer.removeAll()
        // 랜덤한 정답 생성
        for _ in 0..<3 {
            let randomNum = Int.random(in: 0...9)
            answer.append(String(randomNum))
        }
    }
    
    // 정답 확인
    func checkResult(_ input:[String]) {
        // score 비우기
        score.removeAll()
        // 정답 체크
        for (i, data) in input.enumerated() {
            for (j, result) in answer.enumerated() {
                if data == result {
                    score.append(i == j ? "S" : "B")
                    break
                }
            }
            if score.count-1 != i {
                score.append("O")
            }
        }
    }
    
    // 정답 반환
    func getAnswer() -> [String] {
        return answer
    }
    
    // 스코어 반환
    func getScore() -> [String] {
        return score
    }
    
    // 스코어 출력
    func printScore() -> String {
        let result = score.joined(separator: " ")
        return result
    }
}

 

 

 

 

 

 

기능 테스트

 

  • TDDTests.swift
final class TDDTests: XCTestCase {
	// 숫자야구 게임 작동을 위한 메인 클래스
    private var bullAndCows:BullsandCows!
    
    // 초기 설정 (생성 등을 담당)
    override func setUpWithError() throws {
        try super.setUpWithError()
        bullAndCows = BullsandCows()
    }
	// CleanUp
    override func tearDownWithError() throws {
        bullAndCows = nil
        try super.tearDownWithError()
    }
}
- setUpWithError() : 기능 테스트를 위해 숫자야구 게임을 관리하는 BullandCows 클래스를 생성
- tearDownWithError() : 테스트 종료 후 메모리 해제

 

    // BullsandCows 생성 시 초기 정답 값 여부 확인
    func testAnswerNotNil() {
        // given, when
        let answer = bullAndCows.getAnswer()
        // then
        XCTAssertNotEqual(answer.count, 0)
    }
    
    // SSS일 경우
    func testSSS() throws {
        // given, 값 설정
        bullAndCows.inputAnswer("123")
        bullAndCows.checkResult(["1", "2", "3"])
        // when, 테스트 코드 실행
        let result = bullAndCows.printScore()
        // then, 예상 결과 확인
        XCTAssertEqual(result, "S S S", "정답이 잘못되었습니다")
    }
    
    // OOO일 경우
    func testOOO() throws {
        // given, 값 설정
        bullAndCows.inputAnswer("000")
        bullAndCows.checkResult(["1", "2", "3"])
        // when, 테스트 코드 실행
        let result = bullAndCows.printScore()
        // then, 예상 결과 확인
        XCTAssertEqual(result, "O O O", "정답이 잘못되었습니다")
    }
    
    // BBB일 경우
    func testBBB() throws {
        // given, 값 설정
        bullAndCows.inputAnswer("312")
        bullAndCows.checkResult(["1", "2", "3"])
        // when, 테스트 코드 실행
        let result = bullAndCows.printScore()
        // then, 예상 결과 확인
        XCTAssertEqual(result, "B B B", "정답이 잘못되었습니다")
    }
    
    // SOS일 경우
    func testSOS() throws {
        // given, 값 설정
        bullAndCows.inputAnswer("103")
        bullAndCows.checkResult(["1", "2", "3"])
        // when, 테스트 코드 실행
        let result = bullAndCows.printScore()
        // then, 예상 결과 확인
        XCTAssertEqual(result, "S O S", "정답이 잘못되었습니다")
    }
- testAnswerNotNil() : BullsandCows를 생성할 때 초기 정답값이 생성되도록 되어있다. 이 코드가 제대로 수행되는지 확인
- testXXX() : 정답을 설정한 후 다양한 결과가 나오도록 답을 입력하고 해당 결과가 맞게 출력되는지 테스트를 진행

 

 

 

 

 

UI 테스트

 

  • TDDUITests.swift
final class TDDUITests: XCTestCase {
	// 초기화 및 기본 세팅
    override func setUpWithError() throws {
        try super.setUpWithError()
        continueAfterFailure = false
    }
	// CleanUp
    override func tearDownWithError() throws {
        try super.tearDownWithError()
    }
}
- setUpWithError() : 해당 부분에서 테스트 앱을 실행하는 코드를 추가해도 무방함
- tearDownWithError() : 테스트 종료 후 해제 해야할 것들이 있다면 추가

 

// 숫자 입력을 위해 버튼을 클릭했을때 시트가 등장하여 숫자 버튼이 노출되는가
    func testClickNumButtonAndUpSheet() throws {
        // 테스트 앱 실행
        let app = XCUIApplication()
        app.launch()
        // Num 버튼을 찾고 존재하는지 확인
        let btnInput = app.buttons.firstMatch
        XCTAssertTrue(btnInput.exists)
        btnInput.tap() // 버튼 클릭
        // sheet가 열려서 숫자 버튼들이 나오는지 확인
        let btnNum = app.buttons["0"]
        XCTAssertTrue(btnNum.exists, "숫자 버튼이 존재하지 않습니다")
    }
    
    // 숫자 입력 후 화면이 변경되는가
    func testClickSheetAfterChange() throws {
        // 테스트 앱 실행
        let app = XCUIApplication()
        app.launch()
        // Num 버튼을 찾고 존재하는지 확인
        let btnInput = app.buttons.firstMatch
        XCTAssertTrue(btnInput.exists)
        btnInput.tap() // 버튼 클릭
        // sheet가 열려서 숫자 버튼들이 나오는지 확인
        let btnZero = app.buttons["0"]
        XCTAssertTrue(btnZero.exists, "숫자 버튼이 존재하지 않습니다")
        btnZero.tap() // sheet에서 숫자 클릭
        // 화면이 변경되었는지 확인
        let btnMark = app.buttons["?"]
        XCTAssertNotEqual(btnInput, btnMark, "?가 숫자로 변경되지 않았습니다")
    }
- testClickNumButtonAndUpSheet() : 숫자 입력을 위해 버튼을 클릭하면 숫자 입력 시트가 올라오는지 확인
- testClickSheetAfterChange() : 숫자 입력 시트에서 숫자를 입력하면 버튼이 변경되는지 확인

 

 

 

 

 

 

테스트 실행

해당 클래스 선언부 옆의 마름모 버튼을 클릭하면 테스트를 실행할 수 있다
UITest의 경우 시뮬레이터가 실행되며 직접 UI가 변경되는 모습을 볼 수 있다

 

 

 

 

 

 

 

 

 

 

 

 

참고사이트
 

Swift(스위프트): iOS 단위 테스트(Unit test) 및 UI 테스트 튜토리얼 - BGSMM

원문 iOS Unit Testing and UI Testing Tutorial   버전 Swift 5, iOS 14, Xcode 12   iOS 단위 테스트(Unit test) 및 UI 테스트 튜토리얼 iOS 단위 테스트는 거창하지 않지만 테스트를 통해 앱이

yoonbumtae.com

 

 

TDD와 Xcode에서 XCTest 활용하기

TDD(Test Driven Development) 테스트 주도 개발은(TDD)은 매우 짧은 개발 사이클을 반복하는 software 개발 프로세스 중 하나이다. 개발자는 새로운 함수를 정의하는 자동화된 TestCase를 먼저 작성한다.

velog.io

 

 

[iOS] Xcode를 이용한 UI 테스트 - 2. 테스트 케이스 작성

이번 글에서는 iOS 앱의 UI 테스트 케이스를 어떻게 작성하는지 알아보려 합니다. 이전 글과 이어지는 내용이므로 아직 이전 글을 보지 않으셨다면 아래 링크를 눌러 이전 글을 먼저 보는 것을 추

mildwhale.tistory.com

 

728x90
728x90