Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SOLID原則のSとDとテストの話 - 「Swiftらしく設計する」Another / 20181221 #roppongiswift

takasek
December 21, 2018

SOLID原則のSとDとテストの話 - 「Swiftらしく設計する」Another / 20181221 #roppongiswift

ROPPONGI.swift 第6回 望年会 - connpass
https://visits.connpass.com/event/111355/
での発表資料です。

一般発売目前となった「iOSアプリ設計パターン入門」
https://peaks.cc/iOS_architecture
の第3章「Swiftらしく設計する」
で説明したSOLID原則のS(単一責任原則)とD(依存関係逆転の原則)について、
ちょっと角度を変えてテストの側面から捉えてみます。

takasek

December 21, 2018
Tweet

More Decks by takasek

Other Decks in Programming

Transcript

  1. 3ষͷվળର৅ίʔυʢൈਮʣ final class MessageSender { private let api = CommonMessageAPI()

    init(messageType: MessageType) { ... } private var isTextValid: Bool { ... } private var isImageValid: Bool { ... } var isValid: Bool { switch messageType { case .text: return isTextValid case .image: return isTextValid && isImageValid case .official: return false // OfficialMessage͸͋Γ͑ͳ͍ } } ... 8
  2. let sut = MessageSender(messageType: .image) let cases: [(line: UInt, text:

    String?, image: UIImage?, expects: Bool)] = [ (line: #line, text: nil, image: UIImage(), expects: true), // text͕͋Δ৔߹ɺ80จࣈҎ಺Ͱͳ͍ͱinvalid (line: #line, text: String(repeating: "x", count: 80), image: UIImage(), expects: true), (line: #line, text: String(repeating: "x", count: 81), image: UIImage(), expects: false), // image͕ͳ͚Ε͹͍͔ͳΔ৔߹΋invalid (line: #line, text: nil, image: UIImage(), expects: false), (line: #line, text: String(repeating: "x", count: 80), image: UIImage(), expects: false), (line: #line, text: String(repeating: "x", count: 81), image: UIImage(), expects: false), ] cases.forEach { sut.text = $0.text sut.image = $0.image XCTAssertEqual(sut.isValid, $0.expects, line: $0.line) } 12
  3. ղܾࡦɿ ґଘؔ܎ٯసͷݪଇ ❌ final class MessageSender { private let api

    = CommonMessageAPI() init(messageType: MessageType) { ... } } 16
  4. ղܾࡦɿ ґଘؔ܎ٯసͷݪଇ ⭕ final class MessageSender { private let api:

    CommonMessageAPIProtocol // protocolԽ init(messageType: MessageType, api: CommonMessageAPIProtocol) { // DI ... } } 17
  5. let sut = MessageSender( api: /* ! ͜͜ʹԿΛೖΕΑ͏…ʁ */, messageType:

    .image ) let cases = [ ... ] cases.forEach { sut.text = $0.text sut.image = $0.image XCTAssertEqual(sut.isValid, $0.expects, line: $0.line) } 19
  6. struct ImageMessageInputValidator { let image: UIImage? let text: String? var

    isValid: Bool { if image == nil { return false } if let text = text, text.count > 80 { return false } return true } } ※ iOSઃܭຊͰ͸͞ΒʹઌͷਐԽ͕͋Γ·͢ɻ ɹؾʹͳΔਓ͸ຊΛಡΜͰ͔֬ΈͯΈΖʂ 22
  7. վળςετίʔυ // apiͷ͜ͱΛҙࣝ͢Δඞཁ͕ͳ͘ͳͬͨ // messageType΋ཁΒͳ͘ͳͬͨ let cases = [ ...

    ] cases.forEach { let sut = ImageMessageInputValidator( text: $0.text, image: $0.image ) XCTAssertEqual(sut.isValid, $0.expects, line: $0.line) } 23
  8. Α͘ݟΔίʔυ final class HogeListPresenter { private func didReceiveResponse(hoges: [Hoge]) {

    updateViewDataArray(with: hoges) } private func updateViewDataArray(with hoges: [Hoge]) { self.viewDataArray = hoges.map { if hoge.isXXX { return HogeViewData(...) } else { return HogeViewData(...) } } } } 27
  9. Α͘ݟΔίʔυ final class HogeListPresenter { private func didReceiveResponse(hoges: [Hoge]) {

    updateViewDataArray(with: hoges) // Ͳ͏͍͏෭࡞༻͕͋Δͷ͔҉໧త } private func updateViewDataArray(with hoges: [Hoge]) { self.viewDataArray = hoges.map { if hoge.isXXX { return HogeViewData(...) } else { return HogeViewData(...) } } } } 28
  10. ͡Ό͋͜͏͢Δʁ final class HogeListPresenter { private func didReceiveResponse(hoges: [Hoge]) {

    self.viewDataArray = makeViewDataArray(from: hoges) } private func makeViewDataArray(from hoges: [Hoge]) -> [HogeViewData] { return hoges.map { if hoge.isXXX { return HogeViewData(...) } else { return HogeViewData(...) } } } } 29
  11. ͦΕͳΒ͜͏Ͱ͸ final class HogeListPresenter { func didReceiveResponse(hoges: [Hoge]) { self.viewDataArray

    = hoges.map(HogeViewData.init) } } private extension HogeViewData { init(hoge: Hoge) { ... } } 31