์๋ ํ์ธ์ Foma ๐ป ์ ๋๋ค!
ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์ฐพ์๋ณด๋ค๊ฐ ์ฐ์ฐํ ๋ง์ ๊ฐ๋ฐ์๋ถ๋ค์ด Quick๊ณผ Nimble์ ์ฌ์ฉํ๊ณ ์๋ค๋ ๊ฒ์
์๊ฒ ๋์์ต๋๋ค.
๊ทธ๋์ ์ค๋์ Quick๊ณผ Nimble์ด ์ด๋ค ๊ฒ์ด๊ณ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์๋ณด๋ ค๊ณ ํฉ๋๋ค.
๋ฐ๋ก ์์ํ ๊ฒ์~
Quick
๋จผ์ Quick์ ์๋์ ๊ฐ์ด ์์ ๋ค์ ์๊ฐํ๊ณ ์์ต๋๋ค.
RSpec, Specta,Ginkgo์์ ์๊ฐ์ ๋ฐ์ Swift ๋ฐ Objective-C๋ฅผ ์ํ ํ๋ ์ค์ฌ ๊ฐ๋ฐ ํ๋ ์์ํฌ์ ๋๋ค.
์ฆ iOS ์ ์ฉ BDD ํ๋ ์์ํฌ๋ผ๋ ๊ฒ์ด์ฃ .
์ ๋ง ๋๋๊ฒ๋ ํ๊ตญ์ด ๋ฒ์ ์ผ๋ก ๋ฌธ์๊ฐ ์ ๊ณต๋์ด ์์ด ๊ตฌ์ฒด์ ์ผ๋ก ๊ถ๊ธํ์ ๋ถ๋ค์ ์ฌ๊ธฐ์์ ์ฝ์ด๋ณด์๊ธธ ๋ฐ๋๋๋ค.
Nimble
Nimble์ ์๋์ ๊ฐ์ด ์์ ๋ค์ ์๊ฐํฉ๋๋ค.
Cedar์์ ์๊ฐ์ ๋ฐ์ Swift ๋ฐ Objective-C ํํ์์ ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ํ๋ด์ฃผ๋ Matcher ํ๋ ์์ํฌ์ ๋๋ค.
์ฆ, XCTest์์ Assertion์ ๋์ฑ ํธ๋ฆฌํ๊ฒ ์ธ ์ ์๋๋ก ํ๋ ํ๋ ์์ํฌ์ ๋๋ค.
Nimble์ Apple์์ ์ ๊ณตํ๋ XCTest Assertion์์ ๋จ์ ์ ๊ทน๋ณตํ๊ธฐ ์ํด ๋ง๋ค์ด์ก๋๋ฐ์.
1. ์ถฉ๋ถํ์ง ์์ ๋งคํฌ๋ก
XCTest Assertion์๋ ๋ฌธ์์ด์ ํน์ ํ์ ๋ฌธ์์ด์ด ํฌํจ๋๊ฑฐ๋ ์ซ์๊ฐ ๋ค๋ฅธ ๋ฌธ์์ด๋ณด๋ค ์๊ฑฐ๋ ๊ฐ๋ค๊ณ ํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
2. ๋น๋๊ธฐ ํ ์คํธ ์์ฑ์ ์ด๋ ค์
XCTest๋ฅผ ์ฌ์ฉํ๋ฉด ๋ง์ ์์ฉ๊ตฌ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
์ด ๋๊ฐ์ง ์ธ์๋ ์ ๋ง ๋ง์ ์ด์ ์ด ์๋๋ฐ ์ฌ๊ธฐ์ ๋ค ์๊ฐ๋ ๋ชป๋๋ ค์ ์๋์ ๋งํฌ๋ฅผ ๋จ๊ฒจ๋๊ฒ ์ต๋๋ค.
Example
Quick๊ณผ Nimble์ ์ฌ์ฉํ๋ ์์ ๋ก ์ํ ๋ชฉ๋ก์ ํ ์ด๋ธ๋ทฐ์ ๋์ฐ๋ ๊ฐ๋จํ ํ๋ก์ ํธ๋ฅผ ํ ์คํธ ํด๋ณด๊ฒ ์ต๋๋ค.
Storyboard
๋ทฐ์ปจํธ๋กค๋ฌ์ ํ ์ด๋ธ๋ทฐ๋ฅผ ์ธํ ํด์ค๋๋ค.
ํ ์ด๋ธ๋ทฐ ์คํ์ผ์ Right Detail๋ก ํด์ฃผ์๊ณ Identifier๋ MovieTableViewCell๋ก ํด์ฃผ์ธ์.
Movie
์ํ ๋ชจ๋ธ์ ๋ง๋ค์ด์ค๋๋ค.
struct Movie {
var title: String
var genre: Genre
func genreString() -> String {
switch genre {
case .Action:
return "Action"
case .Animation:
return "Animation"
default:
return "None"
}
}
}
Genre
์ํ ์ฅ๋ฅด๋ฅผ enum์ผ๋ก ๋์ดํด์ค๋๋ค.
enum Genre: Int {
case Animation
case Action
case None
}
MoviesDataHelper
์ํ ์ ๋ชฉ๊ณผ ์ฅ๋ฅด๊ฐ ๋ด๊ฒจ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋์ต๋๋ค.
class MoviesDataHelper {
static func getMovies() -> [Movie] {
return [
Movie(title: "The Emoji Movie", genre: .Animation),
Movie(title: "Logan", genre: .Action),
Movie(title: "Wonder Woman", genre: .Action),
Movie(title: "Zootopia", genre: .Animation),
Movie(title: "The Baby Boss", genre: .Animation),
Movie(title: "Despicable Me 3", genre: .Animation),
Movie(title: "Spiderman: Homecoming", genre: .Action),
Movie(title: "Dunkirk", genre: .Animation)
]
}
}
MovieViewController
MovieViewController์ ํ ์ด๋ธ๋ทฐ๋ฅผ ์ฐ๊ฒฐํด์ค๋๋ค.
์ํ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ณ ํ ์ด๋ธ๋ทฐ์ ํ์ดํ๊ณผ ์ฅ๋ฅด๋ฅผ ๋ฟ๋ ค์ค๋๋ค.
import UIKit
class MovieViewController: UIViewController {
//MARK:- IBOutlets
@IBOutlet weak var table: UITableView!
//MARK:- Properties
var movies:[Movie] {
return MoviesDataHelper.getMovies()
}
//MARK:- Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
//MARK:- Helper Functions
private func configure() {
table.dataSource = self
}
}
//MARK:- UITableViewDataSource
extension MovieViewController:UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCell(withIdentifier: "MovieTableViewCell")
cell?.textLabel?.text = movies[indexPath.row].title
cell?.detailTextLabel?.text = movies[indexPath.row].genreString()
return cell!
}
}
Pod
ํด๋น ํ๋ก์ ํธ์ pod init์ ํด์ฃผ์ ๋ค ๋ ํ๋ ์์ํฌ๋ฅผ ์ถ๊ฐํ ๋ค pod install ํด์ฃผ์ธ์.
pod 'Quick'
pod 'Nimble'
MovieViewControllerSpec
ํ๋ก์ ํธ๋ช Tests์ Unit Test Case Class๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
์ด๋ฆ์ MovieViewControllerSpec์ผ๋ก ํด์ค๋๋ค.
Quick๊ณผ Nimble ๊ทธ๋ฆฌ๊ณ ํด๋น ํ๋ก์ ํธ๋ฅผ import ํด์ฃผ์ธ์.
import Quick
import Nimble
@testable import Quick_Nimble_Example
ํด๋์ค๋ฅผ QuickSpec์ผ๋ก ๋ฐ๊ฟ์ฃผ์ธ์.
class MovieViewControllerSpec: QuickSpec {
์์ ๋ ๋ชจ๋ ๋ฉ์๋๋ฅผ ์ง์ฐ๊ณ spec() ์ ์์ฑํด์ค๋๋ค.
์ด์ ๋ชจ๋ ํ ์คํธ๋ spec() ๋ธ๋ก ์์ ์์ฑํด์ฃผ๊ฒ ์ต๋๋ค.
override func spec() { ... }
๋จผ์ describe๋ BDD์์ Given์ ์๋ฏธํฉ๋๋ค.
์ฆ, ์ด๋ค ํ๊ฒฝ์ ์๋์ง๋ฅผ ์ ์ํด์ค๋๋ค.
beforeEach ๋ธ๋ก์ ํ ์คํธ๊ฐ ์คํ๋๊ธฐ ์ ์ ๋ฏธ๋ฆฌ ํ๊ฒฝ์ ์ค์ ํ๋ ๊ฒ์ธ๋ฐ์.
MovieViewController๋ฅผ instantiate ํด์ฃผ๊ณ ํด๋น ๋ทฐ๋ฅผ ์ค์ ํด์ค๋๋ค. (viewDidLoad์ ๋น์ทํฉ๋๋ค.)
context๋ BDD์์ When์ ์๋ฏธํฉ๋๋ค.
์ฆ, ์ด๋ค ํ์๊ฐ ์ผ์ด๋ ๋๋ฅผ ์ ์ํด์ค๋๋ค.
it์ BDD์์ Then์ ์๋ฏธํฉ๋๋ค.
์ฆ, ์์๋๋ ๊ฒฐ๊ณผ๋ฅผ ์ ์ํด์ค๋๋ค.
๊ทธ๋ฆฌ๊ณค Nimble์ assertion์ ์ด์ฉํด์ ํ ์ด๋ธ๋ทฐ์ Row์ ๊ฐฏ์๊ฐ ์ ํํ์ง ํ ์คํธํด์ค๋๋ค.
var subject: MovieViewController!
describe("๋ฌด๋น๋ทฐ์ปจํธ๋กค๋ฌ์์") {
beforeEach {
subject = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MovieViewController") as? MovieViewController
_ = subject.view
}
context("๋ทฐ๊ฐ ๋ก๋ ๋ ๋") {
it("8๊ฐ ์ํ๊ฐ ๋ก๋๋์ด์ผ ํด") {
expect(subject.table.numberOfRows(inSection: 0)).to(equal(8))
}
}
์ํ ๋ฐ์ดํฐ๊ฐ 8๊ฐ ์ด๋ฏ๋ก ํ ์ด๋ธ๋ทฐ๋ Row๊ฐ 8๊ฐ ์ผ ๊ฒ์ ๋๋ค.
๊ณ ๋ก ํ ์คํธ๊ฐ ํต๊ณผํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๋ํ ์ผ์ชฝ ํ ์คํธ ํญ์์ ์ ์ํด์ค ํ ์คํธ๊ฐ ์ ์์ ์ผ๋ก ํต๊ณผํ๋์ง ๋ณผ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์ ํ ์คํธ๋ฅผ ํ๊ฐ ๋ ์งํํ ๊ฑด๋ฐ์.
ํ ์ด๋ธ๋ทฐ์ ์ ์ฌ๋ฐ๋ฅธ ์ ๋ชฉ๊ณผ ์ฅ๋ฅด๊ฐ ๋์์ง๋์ง ํ ์คํธํ๊ฒ ์ต๋๋ค.
Given์ ๋๊ฐ์ด ๋ฌด๋น ๋ทฐ์ปจํธ๋กค๋ฌ์์ ์ด๊ณ When๊ณผ Then๋ง ๋ฐ๊ฟ ๋ณด๊ฒ ์ต๋๋ค.
When์ ํ ์ด๋ธ๋ทฐ์ ์ด ๋ก๋๋ ๋๋ก context์ ์ ์ํด์ค๋๋ค.
beforeEach ๋ธ๋ก์ ํ ์ด๋ธ๋ทฐ ์ ์ ๊ตฌํํด์ค๋๋ค.
it์ ์์๋๋ ๊ฒฐ๊ณผ๋ฅผ ์ ์ํด์ฃผ๊ณ Nimble์ assertion์ ์ด์ฉํ์ฌ ์์๋๋ ์ ๋ชฉ๊ณผ ์ฅ๋ฅด๋ฅผ ์ ์ด์ค๋๋ค.
context("ํ
์ด๋ธ๋ทฐ์
์ด ๋ก๋๋ ๋") {
var cell: UITableViewCell!
beforeEach {
cell = subject.tableView(subject.table, cellForRowAt: IndexPath(row: 0, section: 0))
}
it("ํ์ดํ๊ณผ ์ฅ๋ฅด๊ฐ ๋ณด์ฌ์ผ ํด") {
expect(cell.textLabel?.text).to(equal("The Emoji Movie"))
expect(cell.detailTextLabel?.text).to(equal("Animation"))
}
}
์ด๋ ๊ฒ ์งํํ๋ฉด ํ ์คํธ๊ฐ ๋๊ฐ ๋ค ํต๊ณผํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ค๋์ ์ด๋ ๊ฒ ํ ์คํธ ํ๋ ์์ํฌ์ธ Quick๊ณผ Nimble์ ์์๋ณด์๋๋ฐ์.
์ด Quick ๋์ BDD๋ฅผ ์ด๋ป๊ฒ ํ ์คํธ์ ์ ์ฉํ๋์ง ๊ตฌ์ฒด์ ์ผ๋ก ์ ์ ์๊ฒ ๋์๊ณ Nimble์ ํตํด์
๋์ฑ ํธ๋ฆฌํ assertion์ ์ธ ์ ์์์ต๋๋ค.
ํน์๋ผ๋ ๊ถ๊ธํ์ ์ ์ด๋ ํ๋ฆฐ ๋ถ๋ถ์ด ์๋ค๋ฉด ์ธ์ ๋ ๋๊ธ๋ก ์๋ ค์ฃผ์ธ์!
Reference
'๐ iOS > Architecture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Design Pattern] MVVM(Model - View - ViewModel) ํจํด์ด๋? (feat. Swift) (4) | 2021.10.19 |
---|---|
[RIBs] AddMemo ๊ตฌํํ๊ธฐ (๋ฉ๋ชจ ์ถ๊ฐํ๊ธฐ) (0) | 2021.08.26 |
[RIBs] Memo ์ญ์ ,์์ ๊ตฌํํด๋ณด๊ธฐ (0) | 2021.08.26 |
[RIBs] Router๋ฅผ ์ด์ฉํด ํ๋ฉด ์ ํํด๋ณด๊ธฐ (feat. Memo) (0) | 2021.08.24 |
[RIBs] Interactor๋ก ๋น๋์ง์ค ๋ก์ง ์ฒ๋ฆฌํด๋ณด๊ธฐ (feat. ์ด๊ธฐ์ธํ ) (0) | 2021.08.18 |
๋๊ธ