Unit Test

종류: Test

유닛테스트란?

  • 유닛 테스트는 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차다. 즉, 모든 함수와 메소드에 대한 테스트 케이스(Test case)를 작성하는 절차를 말한다. (위키백과)
  • unit test 는 작성한 프로그램이 의도한 대로 동작하는지 검증하는 가장 작은 단위의 테스트
    • 이를 통해서 각 모듈(클래스, 메소드)들이 잘 동작하는지 확인할 수 있음

필요성

  • 각각의 모듈을 부분적으로 확인할 수 있기 때문에 어떤 모듈에서 문제가 발생하는지 확인 가능
  • 전체 프로그램 빌드가 아닌 유닛 단위로 빌드하기 때문에 시간이 절약됨

테스트할 대상 파악

  • 핵심기능: 모델 클래스, 메소드 및 컨트롤러와의 상호 작용
  • 가장 일반적인 UI workflow
  • 경계 조건(boundary condition)
  • 버그 수정

FIRST 원칙

  • Fast(빠름): 테스트가 빠르게 실행되어야 함
  • Independent/Isolated(고립됨): 테스트는 서로 상태를 공유해서는 안됨
  • Repeatable(반복적): 테스트를 실행할 때마다 동일한 결과를 얻어야 함
    • 외부 데이터 공급자(external data provider) 또는 동시성(concurrency) 문제로 인해 간헐적인 오류가 발생할 수 있음
  • Self-validating(자가 검증): 테스트는 완전히 자동화되어야 함.
    • 출력은 로그 파일에 대한 프로그래머의 해석에 의존하지 않고 통과 또는 실패
  • Timely(적시): 이상적으로는 테스트하는 프로덕션 코드를 작성하기 전에 테스트를 작성해야 함
    • 이를 테스트 주도 개발(TDD)라고 함

→ 이 원칙을 따르게 되면 테스트가 앱의 장애물이 되는 것이 아니라 앱을 명확하고 유용하게 유지할 수 있게 됨

기본 코드

import XCTest
@testable import uniwaffle_Application_iOS

class uniwaffle_Application_iOSTests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
        // Any test you write for XCTest can be annotated as throws and async.
        // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
        // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
    }

    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.
        }
    }

}

XCTest

  • 유닛 테스트, 퍼포먼스 테스트, UI 테스트를 만들고 실행하는 프레임 워크

XCTestCase

추상 클래스인 XCTest의 하위 클래스로, 테스트를 작성하기 위해 상속해야 하는 가장 기본적인 클래스이다. XCTest는 테스트를 위한 프레임워크의 이름이기도 하고, 테스트에서 가장 기본이 되는 추상 클래스의 이름이기도 하다.

해당 클래스를 상속받은 클래스에서는 test에서 사용되는 다양한 프로퍼티와 메서드를 사용할 수 있다.

setUpWithError()

각각의 test case가 실행되기 전마다 호출되어 각 테스트가 모두 같은 상태와 조건에서 실행될 수 있도록 만들어줄 수 있는 메서드다.

tearDownWithError()

각각의 test 실행이 끝난 후마다 호출되는 메서드. 보통 setUpWithError()에서 설정한 값들을 해제할 때 사용된다.

testExample()

test로 시작하는 메서드들은 작성해야 할 test case가 되는 메서드다. 테스트할 내용을 메서드로 작성해 볼 수 있다. 메서드 네이밍은 무조건 test로 시작되어야 한다.

testPerformanceExample()

성능을 테스트해보기 위한 메서드다. XCTestCase의 measure(block:)라는 메서드를 통해 성능을 측정하게 된다.

순서

순서

만드는 방법

  1. 먼저 기존 프로젝트의 코드에 접근할 수 있도록 아래의 코드 추가
@testable import 프로젝트이름
  • @testable: 낮은 접근 권한을 가지는 클래스의 접근 권한을 테스트를 위해 높이는 역할
    1. 자동으로 생성된 코드를 수정
import XCTest
@testable import 프로젝트이름

class NewTests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }
}
  • setUpWithError : 테스트 메소드가 실행되기 전 모든 상태를 reset (초기화 코드)
  • tearDownWithError : 테스트 동작이 끝난 후 모든 상태를 clean up(해체 코드)
    1. 테스트 함수 작성
func testScoreIsComputedWhenGuessIsHigherThanTarget(){
	    // given
	    let guess = sut.targetValue + 5
	    
	    // when
	    sut.check(guess: guess)
	    
	    // then
	    XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
	}
  • given : 필요한 value 들을 셋팅
  • when : 테스트 코드 실행
  • then : 결과 확인(출력)
  • 위의 코드로 작성하면 무조건 실패하는 코드가 작성됨
    • 응답이 오기 전에 XCTestExpectation 코드가 먼저 실행되기 때문
func testApiCallCompletes() throws {
  // given
  let urlString = "http://www.randomnumberapi.com/test"
  let url = URL(string: urlString)!
  let promise = expectation(description: "Completion handler invoked")
  var statusCode: Int?
  var responseError: Error?

  // when
  let dataTask = sut.dataTask(with: url) { _, response, error in
    statusCode = (response as? HTTPURLResponse)?.statusCode
    responseError = error
    promise.fulfill()
  }
  dataTask.resume()
  **wait(for: [promise], timeout: 5)** // 5초 기다리는 로직

  // then
  XCTAssertNil(responseError)
  XCTAssertEqual(statusCode, 200)
}

Q. 만약 no such module XCTesst라고 뜬다면??

A. https://velog.io/@chagmn/No-such-module-XCTest-오류-해결하기 참고 build setting을 바꿔줘야 함


Android, iOS 확인해줘야 할 사항

  1. Unit의 단위를 Controller 단위로 할것인지? 모듈? 클래스? 로 할것인지 결정
    1. viewmodel단위로 테스트 진행하면 될 것으로 보임
  2. AAA 패턴을 사용할 수 있나?
    1. 아마 given, when, then 패턴을 가장 많이 사용하고 있기에 이 패턴을 사용할 예정
  3. Parameterlized Unit test를 사용할 수 있나?
    1. 가능
     import XCTest
        
     class ParameterizedExampleTests: XCTestCase {
            
         //properties to save the test cases
         private var array: [Float]? = nil
         private var expectedResult: Float? = nil
            
         // This makes the magic: defaultTestSuite has the set of all the test methods in the current runtime
         // so here we will create objects of ParameterizedExampleTests to call all the class' tests methodos
         // with differents values to test
         override open class var defaultTestSuite: XCTestSuite {
             let testSuite = XCTestSuite(name: NSStringFromClass(self))
             addTestsWithArray([12, 3], expectedResult: 4, toTestSuite: testSuite)
             addTestsWithArray([12, 2], expectedResult: 6, toTestSuite: testSuite)
             addTestsWithArray([12, 4], expectedResult: 3, toTestSuite: testSuite)
             return testSuite
         }
            
         // This is just to create the new ParameterizedExampleTests instance to add it into testSuite
         private class func addTestsWithArray(_ array: [Float], expectedResult: Float, toTestSuite testSuite: XCTestSuite) {
             testInvocations.forEach { invocation in
                 let testCase = ParameterizedExampleTests(invocation: invocation)
                 testCase.array = array
                 testCase.expectedResult = expectedResult
                 testSuite.addTest(testCase)
             }
         }
        
         // Normally this function is into production code (e.g. class, struct, etc).
         func division(a: Float, b: Float) -> Float {
             return a/b
         }
    
  4. Xcode, Android studio 에서의 사용법
    1. 위의 글 참고
  5. 기타 필요한 데이터 혹은 더 좋은 방법이 있으면

참고

통합 ui 테스트라는 것도 있대

https://blog.banksalad.com/tech/test-in-banksalad-ios-1/