์๋ ํ์ธ์ Foma ๐ป ์ ๋๋ค!
์ค๋์ ์์กด์ฑ ์ฃผ์ ์ด ๋ฌด์์ด๊ณ ์จ์ผํ๋ ์ด์ ์ ๋ํด์ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
SOLID์์น์ ํ๋์ด๊ธฐ๋ ํ๊ณ ์ต๊ทผ RIBs ์ํคํ ์ฒ๋ฅผ ๊ณต๋ถํ๊ณ ์๋๋ฐ UIViewController๋ ์ง์ ์ฑํํ์ง ์๊ณ
ViewControllable๋ก ํ๋กํ ์ฝ์ ๋ง๋ค์ด์ ์ฃผ์ ํ๋๋ผ๊ตฌ์..
๋ญ๊ฐ ์ถ์์ ์ผ๋ก๋ ์๊ฒ ๋๋ฐ ๊ตฌ์ฒด์ ์ผ๋ก ๋ช ํํ๊ฒ ์ด๊ฒ ๋ญ๊ณ ์ด๊ฒ ์ ํ์ํ๊ฐ์ ๋ํด์ ์์ง ๋ชปํด์
๊ธ์ ์ ๋ฆฌํ๋ ค๊ณ ํฉ๋๋ค!
๋ฐ๋ก ์์ํ ๊ฒ์~
์์กด์ฑ์ ๊ฐ๋๋ค๋ ๊ฒ์ ๋ฌด์์ผ๊น? ๐ค
๋ง์ฝ ๋ฐฐํฐ๋ฆฌ๊ฐ ์ผ์ฒดํ์ธ ์๋์ฐจ ์ฅ๋๊ฐ ๐ ์ด ์๋ค๊ณ ๊ฐ์ ํ ๊ฒ์.
์ด ์๋์ฐจ ์ฅ๋๊ฐ์ ๋ฐฐํฐ๋ฆฌ๊ฐ ๋ค ๋ณ๊ฒ ๋๋ฉด ๋ ์ด์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ด ๊ฒฝ์ฐ ์๋์ฐจ ์ฅ๋๊ฐ์ ๋ฐฐํฐ๋ฆฌ์ ์์กดํ๊ณ ์๋ ๊ฒ์ ๋๋ค.
์ฝ๋๋ก ์ค๋ช ์ ํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
class ToyCar {
var battery:Battery = Battery()
}
class Battery {
var energy:Int = 100
}
ToyCar ํด๋์ค์ battery๋ ๋ฐฐํฐ๋ฆฌ ํด๋์ค๋ฅผ ๋ด๋ถ์์ ์ง์ ์์ฑํ์ฌ ๊ฐ์ง๊ณ ์์ต๋๋ค.
์ด๋ ๊ฒ ๋๋ฉด ๋ด๋ถ์ ์์ฑ๋ Battery ํด๋์ค์ energy๊ฐ ๋ค ๋ณ๊ฒ ๋๋ฉด ToyCar ํด๋์ค๋ฅผ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์
ToyCar ํด๋์ค๊ฐ Battery ํด๋์ค์ ์์กดํ๊ณ ์๋ ๊ฒ์ ๋๋ค.
์์กด์ฑ ์ฃผ์ ์ด๋?
"์ํํธ์จ์ด ์์ง๋์ด๋ง์์ ์์กด์ฑ ์ฃผ์ (dependency injection)์ ํ๋์ ๊ฐ์ฒด๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด์ ์์กด์ฑ์ ์ ๊ณตํ๋ ํ ํฌ๋์ด๋ค. "์์กด์ฑ"์ ์๋ฅผ ๋ค์ด ์๋น์ค๋ก ์ฌ์ฉํ ์ ์๋ ๊ฐ์ฒด์ด๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ค ์๋น์ค๋ฅผ ์ฌ์ฉํ ๊ฒ์ธ์ง ์ง์ ํ๋ ๋์ , ํด๋ผ์ด์ธํธ์๊ฒ ๋ฌด์จ ์๋น์ค๋ฅผ ์ฌ์ฉํ ๊ฒ์ธ์ง๋ฅผ ๋งํด์ฃผ๋ ๊ฒ์ด๋ค. "์ฃผ์ "์ ์์กด์ฑ(์๋น์ค)์ ์ฌ์ฉํ๋ ค๋ ๊ฐ์ฒด(ํด๋ผ์ด์ธํธ)๋ก ์ ๋ฌํ๋ ๊ฒ์ ์๋ฏธํ๋ค. ์๋น์ค๋ ํด๋ผ์ด์ธํธ ์ํ์ ์ผ๋ถ์ด๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์๋น์ค๋ฅผ ๊ตฌ์ถํ๊ฑฐ๋ ์ฐพ๋ ๊ฒ์ ํ์ฉํ๋ ๋์ ํด๋ผ์ด์ธํธ์๊ฒ ์๋น์ค๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ด ํจํด์ ๊ธฐ๋ณธ ์๊ฑด์ด๋ค." - ์ํค ๋ฐฑ๊ณผ -
์ฆ, ํด๋์ค ๋ด๋ถ์์ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ ์ธ๋ถ์์ ํด๋์ค๋ฅผ ์ฃผ์ ํด์ฃผ๋ ๊ฒ์ด์ฃ .
์ด๋ ๊ฒ ๋งํ๋ฉด ์ดํดํ๊ธฐ ํ๋์ค๊ฑฐ์์.
์์ ๋ฅผ ํตํด์ ์์๋ณด๊ฒ ์ต๋๋ค.
์์กด์ฑ ์ฃผ์ ํด๋ณด๊ธฐ
์์์ ์ค๋ช ํ ์๋์ฐจ ์ฅ๋๊ฐ์ ์์๋ก ๋ค๊ฒ ์ต๋๋ค.
battery๋ฅผ ๋ด๋ถ์์ ์์ฑํ๊ธฐ ๋๋ฌธ์ ๋ฐฐํฐ๋ฆฌ์ ์์กดํ๊ณ ์๋๊ฒ ๋ฌธ์ ๋ผ๊ณ ํ์์ฃ ?
์๋์ ๊ฐ์ด battery๋ฅผ ToyCar ์ด๊ธฐํ ํจ์์ ํ๋ผ๋ฏธํฐ์ battery๋ฅผ ์ธ๋ถ์์ ์ ์ฉ๋ฐ์ ์ ์๊ฒ ํด์ค๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด ๋ฐฐํฐ๋ฆฌ๋ฅผ ์ธ๋ถ์์ ๋ฐ๊ฒ ๋๊ณ ๋ฐฐํฐ๋ฆฌ๊ฐ ๋ค ๋ณ์์ ธ๋
๋ค๋ฅธ ๋ฐฐํฐ๋ฆฌ๋ฅผ ์ธ๋ถ์์ ๋ฐ์์ ์ ์ฉํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ์์กด์ฑ์ด ๋ฎ์์ง๋๋ค.
class ToyCar {
var battery:Battery
init(battery:Battery) {
self.battery = battery
}
func setBattery(battery:Battery) {
self.battery = battery
}
}
//ToyCar(battery: Battery())
ํ์ง๋ง ์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ๋ ๋ฐ์ํฉ๋๋ค.
๋ง์ฝ ๋ฐฐํฐ๋ฆฌ๊ฐ ์๋ ์ฝ๋๋ก ์ถฉ์ ํ๋ ์๋์ฐจ ์ฅ๋๊ฐ์ด ์๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น์?
์๋์ ๊ฐ์ด ๋ฐฐํฐ๋ฆฌ๋ก ๋ ์๋์ฐจ ์ฅ๋๊ฐ ํด๋์ค์ ์ฝ๋๋ก๋ ์๋์ฐจ ์ฅ๋๊ฐ ํด๋์ค๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด์ค์ผ๊ฒ ์ฃ ?
class BatteryToyCar {
var battery:Battery
init(battery:Battery) {
self.battery = battery
}
}
class CodeToyCar {
var code:Code
init(code:Code) {
self.code = code
}
}
๊ณ ๋ก ๊ณตํต์ ์ธ ๋ถ๋ถ์ด ์๋ Charger ํ๋กํ ์ฝ์ ๋ฐ๋ก ์์ฑํ์ฌ ๋ฐฐํฐ๋ฆฌ์ ์ฝ๋ ํด๋์ค์ ์ฃผ์ ํด์ค๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด Battery,Code ํด๋์ค ๋ชจ๋ Charger ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ ํด๋์ค๊ฐ ๋ฉ๋๋ค.
๊ทธ ๋ค์ ToyCar battery๋ฅผ charger๋ก ๋ชจ๋ ๋ฐ๊ฟ์ฃผ๋ฉด ToyCar๋ ๋ฐฐํฐ๋ฆฌ๋ก ์ถฉ์ ํ๋ ์ฝ๋๋ก ์ถฉ์ ํ๋ ์๊ด์ด ์๊ฒ๋ฉ๋๋ค.
protocol Charger {
}
class ToyCar {
var charger:Charger
init(charger:Charger) {
self.charger = charger
}
func setCharger(charger:Charger) {
self.charger = charger
}
}
class Battery:Charger {
var energy:Int = 100
}
class Code:Charger {
var volt:Int = 220
}
๋ง์ฝ ์ฌ๊ธฐ์ ์ถฉ์ ๊ธฐ์ ์ถฉ์ ํ๋ ํจ์์ ์ถฉ์ ์ ๋ฉ์ถ๋ ํจ์๋ฅผ ๋ฏธ๋ฆฌ ๊ตฌํํด๋์ผ๋ฉด
protocol Charger {
func charge()
func stopCharge()
}
Charger์ ํ์ํ ์ฝ๋๊ฐ ์๋์ผ๋ก ์์ฑ๋์ด ์ฝ๋๋ฅผ ์ฌํ์ฉํ ์ ์๊ฒ๋ฉ๋๋ค.
class Battery:Charger {
var energy:Int = 100
func charge() {
}
func stopCharge() {
}
}
class Code:Charger {
var volt:Int = 220
func charge() {
}
func stopCharge() {
}
}
์์กด์ฑ ์ฃผ์ ์ ํ๋ฉด ์ ํ ์คํธ๊ฐ ํธํด์ง๊น? ๐ง
์ฐ์ ํ ์คํธ๋ฅผ ํ ๋๋ ํ์์ ์ผ๋ก ๊ฐ์ง(Mock) ๋ฐ์ดํฐ,์๋น์ค,๋ฐธ๋ฆฌ๋ฐ์ดํฐ ๋ฑ์ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
ํ์๊ฐ์ ์ ํ ๋ ์น์๋น์ค๋ฅผ ์์กด์ฑ ์ฃผ์ ์์ด ๋ง๋ค์๋ค๊ณ ๊ฐ์ ํ๊ฒ ์ต๋๋ค.
class SignUpWebService {
private var urlSession:URLSession
private var urlString:String
init(urlString:String,urlSession:URLSession = .shared) {
self.urlString = urlString
self.urlSession = urlSession
}
func signUp(with requestModel:SignUpRequestModel,completion:@escaping(SignUpResponseModel?,SignUpError?)-> Void) {
guard let url = URL(string: urlString) else {
completion(nil,SignUpError.invalidRequestURLString)
return
}
...
}
๊ทธ ๋ค์ ํ ์คํธ๋ฅผ ์งํํ๋ ค๊ณ ๊ฐ์งํ์๊ฐ์ ์น์๋น์ค๋ฅผ ๋ง๋๋ ค๊ณ ํ ๋ ์ง์ ํ์๊ฐ์ ์น์๋น์ค์ ์ด๋ค ํ๋กํผํฐ๊ฐ ์๋์ง
์ด๋ค ๋ฉ์๋๊ฐ ์๋์ง ์ง์ ์์ฑํด์ฃผ์ด์ผ ํฉ๋๋ค.
๋ํ ๋ฉ์๋๋ฅผ ๋นผ๋จน์ ๊ฒฝ์ฐ ํ ์คํธ๋ฅผ ๋นผ๋จน์ ์ ์๋ ๊ฐ๋ฅ์ฑ๋ ์กด์ฌํฉ๋๋ค.
class MockSignUpWebService {
var isSignUpMethodCalled:Bool = false
var shouldReturnError:Bool = false
...
}
ํ์ง๋ง ์๋์ ๊ฐ์ด ํ์๊ฐ์ ์น์๋น์ค ํ๋กํ ์ฝ์ผ๋ก ํ์ํ ๋ฉ์๋๋ฅผ ๋ฏธ๋ฆฌ ์ ์ํด๋๊ณ
protocol SignUpWebServiceProtocol {
func signUp(with requestModel:SignUpRequestModel,completion:@escaping(SignUpResponseModel?,SignUpError?)-> Void)
}
๊ฐ์ง ์น์๋น์ค์ ์ฑํํ๊ฒ ๋๋ฉด ์ด๋ค ๋ฉ์๋๊ฐ ํ์ํ์ง ์๋์ผ๋ก ์ธํ ๋์ด ๋นผ๋จน์ ๊ฐ๋ฅ์ฑ์ด ์๊ณ ์ง์ ์ฝ๋๋ฅผ ์์ฑํ์ง ์์๋ ๋ฉ๋๋ค.
class MockSignUpWebService:SignUpWebServiceProtocol {
var isSignUpMethodCalled:Bool = false
var shouldReturnError:Bool = false
func signUp(with requestModel: SignUpRequestModel, completion: @escaping (SignUpResponseModel?, SignUpError?) -> Void) {
isSignUpMethodCalled = true
if shouldReturnError {
completion(nil,SignUpError.failedRequest(description: "SignUp request was not successful"))
}else {
let response = SignUpResponseModel(status: "ok")
completion(response,nil)
}
}
}
์ ๋ฆฌ
์์กด์ฑ ์ฃผ์ ์ ํด๋์ค ๋ด๋ถ์ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ง ์๊ณ ์ธ๋ถ์์ ์ฃผ์ ๋ฐ๋ ๊ฒ์ด๋ค.
์์กด์ฑ ์ฃผ์ ์ ํด์ผํ๋ ์ด์ ๋ ๊ฐ ํด๋์ค๊ฐ ์์กดํ์ง ์๊ฒ ํ์ฌ ํ ํด๋์ค์์ ๋ฌธ์ ๊ฐ ์๊ธฐ๋๋ผ๋
ํด๋น ํด๋์ค๋ง ๊ณ ์น๋ฉด ๋๋๋ก ํ๊ธฐ ์ํจ์ด๋ค.
๊ณตํต์ ์ธ ๋ถ๋ถ์ ํ๋กํ ์ฝ๋ก ๋ฏธ๋ฆฌ ์ ์ํด๋๊ณ ๊ฐ ํด๋์ค์ ์ฑํํ์ฌ ์ฌ์ฉํ๊ฒ ๋๋ฉด ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉํ ์ ์๊ณ
๋ค๋ฅธ ํด๋์ค๋ฅผ ํ์ฅํ๋๋ผ๋ ํํ ์ฝ์ฝ๋ง ์ฑํํด์ฃผ๋ฉด ๊ฐํธํ๊ฒ ์ฃผ์ ํด์ค ์ ์๋ค.
๋ํ ํ ์คํธ ์ Mock ํด๋์ค๋ฅผ ๋ง๋ค ๋ ์์กด์ฑ์ ์ฃผ์ ํ์ฌ ์ฝ๋๋ฅผ ์ฝ๊ฒ ์ค์์์ด ์์ฑํ๊ฒ ํ์ฌ
ํ ์คํธ๋ฅผ ๊ฐํธํ๊ฒ ํ ์ ์๊ฒ ํ๋ค.
Reference
https://devlog-wjdrbs96.tistory.com/165
๋๊ธ