일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 스위프트
- rxswift
- swift documentation
- collectionview
- 리팩토링
- uitableview
- combine
- uiscrollview
- 애니메이션
- Observable
- Clean Code
- HIG
- Protocol
- MVVM
- swiftUI
- RxCocoa
- 리펙터링
- Human interface guide
- ios
- SWIFT
- 리펙토링
- Refactoring
- tableView
- map
- Xcode
- clean architecture
- UITextView
- 클린 코드
- ribs
- UICollectionView
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - Swift] 2. 유닛 테스트 방법 - Quick과 Nimble을 이용한 테스트 코드 구현 방법 (BDD, sut, dummy, mock, stub) 본문
[iOS - Swift] 2. 유닛 테스트 방법 - Quick과 Nimble을 이용한 테스트 코드 구현 방법 (BDD, sut, dummy, mock, stub)
jake-kim 2022. 12. 11. 22:331. 유닛 테스트 방법 - Dependency Injection (@Injected) 주입 구조
2. 유닛 테스트 방법 - Quick과 Nimble을 이용한 테스트 코드 구현 방법 <
3. 유닛 테스트 방법 - RxExpect를 이용한 Rx관련 비동기 테스트 코드 구현 방법
번외) 유닛 테스트 방법 - XCTest와 RxSwift만을 이용한 비동기 테스트 구현 방법
* 예제 코드는 이전 Dependency Injection에서 사용된 구조를 사용하므로, 이전 글 먼저 참고
테스트 용어
- sut (System Under Test): 테스트를 하려는 대상
- dummy: 속성이나 메서드의 인자값
- mock: 특정 조건에서 원하는 메서드를 실행하는지에 관한 행위 검증에 기대되는 값 (메소드 리턴값 등..)
- stub: 객체의 상태와 같은 상태 검증에 관한 기대되는 값
가장 일반적인 테스트 방법
BDD (Behavior Deriven Development) - 제한적인 상황을 고려한 인풋에 대한 결과값을 테스트하는 방법
- Given - 테스트 조건
- When - 인풋
- Then - 아웃풋 (= 기대되는 값)
// https://medium.com/@lucianoalmeida1/quick-and-nimble-behavior-driven-development-in-swift-e27d4b213857
Given - The user is logged in
When - The user taps the profile picture
Then - It should be redirected to the profile screen
cf) TDD와 BDD의 차이
- TDD는 제한적인 상황이 없고, BDD는 제한적인 상황이 있는 상태
Quick과 Nimble
- Quick
- BDD 구조를 사용할 수 있게 프레임워크에서 제공
- describe - context - it 형식으로 메소드와 클로저로 제공 (각각 Given - When - Then)
- QuickSpec이라는 것을 override하고 spec() 메소드 재정의
- 메소드 안에서 describe - context - it을 사용할 수 있고, QuickSpec은 XCTestCase의 typealias이므로 XCTestCase 모듈 사용이 가능
import XCTest
import Nimble
import Quick
final class ExDITests: QuickSpec {
override func spec() {
describe("The user is logged in") {
// logged in
context("The user taps the profile picture") {
// taps picture
it("It should be redirected to the profile screen") {
// redirected screen
// 검증
}
}
}
}
}
- suite - 초기화 (1회 실행)
- beforeSuite (= setup)
- afterSuite (= teardown)
- each - suite는 최초 한번만 호출되지만, each는 선언하는 부분 모두 동작하므로 context가 여러개 있는 경우 그 클로저에서 새로운 값을 setup할때 each로 초기화
- beforeEach
- afterEach
ex) describe안에 beforeSuite, afterSuite를 놓고 / context안에 beforeEach, afterEach 사용
실행 순서: describe - context - beforeSuite - beforeEach - it - afterEach - afterSuite
(afterSuite가 실행 안되는 이슈가 존재하여, 혹시 이유를 아신다면 코멘트 부탁드립니다.)
// describe - context - beforeSuite - beforeEach - it - afterEach 순서로 실행
final class SomeExample2Tests: QuickSpec {
override func spec() {
describe("조건") {
var someStub: Int!
var sut: Calculator!
beforeSuite {
someStub = 3
sut = Calculator()
}
afterSuite {
someStub = nil
sut = nil
}
context("액션") {
var value: Int!
beforeEach {
value = sut.plus(0, 3)
}
afterEach {
value = nil
}
it("결과") {
// TODO: 검증
}
}
}
}
}
- Nimble
- 위 it 단계에서 검증할 때, (기댓값과 결과값을 비교할때 사용)
- 선언 형태로 사용이 가능하여 가독성이 높은 장점이 존재
// https://github.com/Quick/Nimble
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())
expect(seagull.squawk).toNot(equal("Oh, hello there!"))
expect(seagull.squawk).notTo(equal("Oh, hello there!"))
ex) nimble까지 적용한 예제
final class SomeExample2Tests: QuickSpec {
override func spec() {
describe("조건") {
print("describe")
var someStub: Int!
var sut: Calculator!
beforeSuite {
print("beforeSuite")
someStub = 3
sut = Calculator()
}
afterSuite {
print("afterSuite")
someStub = nil
sut = nil
}
context("액션") {
print("context")
var value: Int!
beforeEach {
print("beforeEach")
value = sut.plus(0, 3)
}
afterEach {
print("afterEach")
value = nil
}
it("결과") {
print("it")
expect(value)
.to(equal(someStub))
}
}
}
}
}
출력)
describe
context
Test Suite 'SomeExample2Tests' started at 2022-12-11 01:02:00.804
Test Case '-[ExDITests.SomeExample2Tests 조건__액션__결과:]' started.
beforeSuite
beforeEach
it
afterEach
Test Case '-[ExDITests.SomeExample2Tests 조건__액션__결과:]' passed (0.001 seconds).
Test Suite 'SomeExample2Tests' passed at 2022-12-11 01:02:00.806.
Executed 1 test, with 0 failures (0 unexpected) in 0.001 (0.002) seconds
DI 테스트 예제
* @Injected와 Dependency Store를 이용한 코드 준비 (이전 포스팅 글 참고)
- 테스트 내용
- ConfigService를 이용하는 ViewModel을 검증
- ConfigService를 Stub으로 하나 만들고 stub으로 동작했을때 ViewModel에서 기대하는 값이 나오는지 테스트
- ConfigService를 Stub으로 하나 정의
final class StubConfigService: ConfigServiceType {
private var key = ""
var secretKey: String {
key
}
func saveSecretKey(_ key: String) {
self.key = key + "@"
}
}
- describe - context - it 단계로 테스트
- 편의상 afterSuite, afterEach 생략
- DepenencyStore()에 configService의 인스턴스를 stub 데이터로 변경하고 viewModel에 적용
final class ExDITests: QuickSpec {
override func spec() {
describe("The user is logged in") {
var sut: ViewModel!
beforeSuite {
let ds = DependencyStore()
let stubConfigService = StubConfigService()
ds.register(stubConfigService, for: ConfigServiceType.self)
sut = ds.execute {
ViewModel()
}
}
context("The user tap button") {
beforeEach {
sut.textInput("jake-ios")
sut.tapStoreButton()
}
it("Get access token") {
expect(sut.currentSecretKey)
.to(equal("jake-ios@"))
}
}
}
}
}
/*
describe
context
Test Suite 'ExDITests' started at 2022-12-11 01:54:53.518
Test Case '-[ExDITests.ExDITests The_user_is_logged_in__The_user_tap_button__Get_access_token:]' started.
beforeSuite
Test Case '-[ExDITests.ExDITests The_user_is_logged_in__The_user_tap_button__Get_access_token:]' passed (0.004 seconds).
Test Suite 'ExDITests' passed at 2022-12-11 01:54:53.523.
Executed 1 test, with 0 failures (0 unexpected) in 0.004 (0.005) seconds
*/
(Quick, Nimble로 테스트하는 실전 코드는 이 포스팅 글 참고)
* 전체 코드: https://github.com/JK0369/ExDI
* 참고
https://mokacoding.com/blog/quick-beforesuite-aftersuite-behaviour/
https://azderica.github.io/00-test-mock-and-stub/
https://johngrib.github.io/wiki/test-terms/
https://github.com/Quick/Nimble
https://medium.com/towards-data-science/tdd-explained-with-an-example-738d702f87e
https://github.com/Quick/Quick/tree/main/Documentation/ko-kr