WACATE2019 夏のBPPセッションスライドです。
։ൃऀ͔ΒݟΔςετ2019/06/15~16 WACATE2019 Ն BPPηογϣϯkariad/͔Γ͋Ͳ(@kariad_uu)1
View Slide
ࣗݾհ• kariad / @kariad_uu• ยࢁ େथ• ΦΠγοΫεɾϥɾେ iOS App Developer• / / V / ςετ / ઃܭ / ྉཧ / ήʔϜ2
ςετͱͷؔΘΓ• ৽ଔ: SIer ͻͨ͢ΒؤுΔखಈςετɺऴΘΒͳ͍ɺؼΕͳ͍ɺਏ͍….• ҟಈ: ΞδϟΠϧɺTDDɺςετָ͍͠• స৬: ΞδϟΠϧɺUnitςετಋೖɺςετ͍͖ͬͯ• QAΤϯδχΞɺςετΤϯδχΞͱݺΕΔํͱҰॹʹࣄΛͨ͜͠ͱͳ͍Ͱ͢3
ࠓ͢͜ͱ• લճͷࢲͷϙδγϣϯϖʔύʔৼΓฦΓ• ։ൃऀ(ࢲ)͜͏͍͏෩ʹςετʹ͖߹ͬͯ·͢ͱ͍͏ - ςετͷϞνϕʔγϣϯ - ։ൃऀ͕ςετΛ͠ͳ͍(Ͱ͖ͳ͍)ཧ༝4
ҙʂ• ͋͘·Ͱࢲͷओ؍Ͱ͢ɻશͯͷ։ൃऀ͕͜͏Ͱ͋Δʂͱ͍͏Ͱͳ͍ͷͰྃ͝ঝ͍ͩ͘͞
લճ(WACATE2018 ౙ)ͷࢲͷϙδγϣϯϖʔύʔ6
7
վΊͯݟͨ࣌ͷࢲͷײ• ΈΜͳͱൺͯϑΥϯτ͕Ͱ͔͍• †ͱ͔ͬͯதೋප• ͳΜ͔ΤϞ͍͚ͩ• ਖ਼ͪΐͬͱஏ͔͔ͣͬͨ͠…8
ͭ·ΓԿ͕ݴ͍͔ͨͬͨͷ͔• ։ൃऀ͔ΒࢹͰͷςετͷ͍ ςετ͕͋Δ͜ͱͰ৺ཧత҆શੑͷ͋Δ։ൃ͕Ͱ͖Δ ͰΔ͔ΒʹޮతʹΓ͍ͨ• ςετ͕͖͔ͩΒྑͯ͘͠Ͷ ςετք۾ͷίϛϡχςΟॳࢀՃͩͬͨ9
• ࠓճΓ߹͍ͱ͔΄΅͍ͳ͍ͷͰؾܰʹ͔͚ͯ͘͠ΕΔͱخ͍͠Ͱ͢10
։ൃऀͷςετͷ͖߹͍ํ
ςετͷϞνϕʔγϣϯ(ࢲͳΓʹ)12
• ࢲͨͪͷϛογϣϯϢʔβʔʹͱͬͯՁ͋Δ αʔϏεΛఏڙ͢Δ͜ͱ• Ձ͕͋Δ = ͓٬༷(Ϣʔβʔ)͕خ͍͔͠13
• Ͱ࠷ॳ͔Β࠷େԽ͞ΕͨՁΛఏڙ͢Δͷ͍͠• ߟ͑ͨ͜ͱ͕ਖ਼͍͠(Ձ͕͋Δ)ͱݶΒͳ͍ ϦʔϯɾελʔτΞοϓ14
ϦʔϯɾελʔτΞοϓ15
ϦʔϯɾελʔτΞοϓ• ݁ہͲΜͳʹ͍͍ͷΛߟ͑ͨͱ͜ΖͰͦΕࢥ͍ࠐΈԾઆʹ͗͢ͳ͍• MVP(Minimum Valuable Product)Ͱݕূ͢Δ• Ծઆ → Ծઆݕূ → ֶͼ →ҙࢥܾఆͷαΠΫϧΛճ͢16
࠷ۙͷτϨϯυʁ (σʔλੳΛݩʹͨ͠։ൃ)• ԾઆΛݩʹ࣮ͯ͠ϦϦʔε• σʔλΛऔಘͯ͠ੳ• ੳ݁ՌΛݩʹվળͯ͠ϦϦʔε
• ͲͪΒͱʹ͔͘ૣ͘αΠΫϧΛճ͍ͨ͠• ͱ͍͑όά͕͋Γਖ਼ৗʹ͑ͳ͚ΕԾઆݕূͰ͖ͳ͍ ࠷ݶCheckingߦ͍͍ͨ18
• ΞδϟΠϧ։ൃऀͱͯ͠ૣ͘Ձͷ͋ΔαʔϏεΛఏڙ͢ΔͨΊʹCheckingΛ͍ͨ͠ ʢ࣭͕͔ͦͦͬͨΒՁԼ͕Δ…ʣ19
͡Ό͋ͳΜͰChecking(ࠓճUnitςετ)Βͳ͍ͷʁ20
• ΊΜͲ͍͘͞• ςετʹڵຯ͕ͳ͍• Γํ͕Θ͔Βͳ͍• CheckingͷॏཁੑΛཧղ͍ͯ͠ͳ͍
• ΊΜͲ͍͘͞• ςετʹڵຯ͕ͳ͍• Γํ͕Θ͔Βͳ͍• CheckingͷॏཁੑΛཧղ͍ͯ͠ͳ͍ͬͱݴ͏ͱɺΓͨͯ͘Ͱ͖ͳ͍
• ΊΜͲ͍͘͞• ςετʹڵຯ͕ͳ͍• Γํ͕Θ͔Βͳ͍• CheckingͷॏཁੑΛཧղ͍ͯ͠ͳ͍ͬͱݴ͏ͱɺΓͨͯ͘Ͱ͖ͳ͍ࢲ͕ߟ͑ΔUnitςετΛΒͳ͍ཧ༝ͷେ͖ͳҰͭ
Unitςετ࣮ͬͯͦΜͳʹ؆୯Ͱͳ͍24
ςετΛॻ͘ͷ͕͍͠ཧ༝• ςετ͕ॻ͖͍͢ઃܭ(Testable)ʹͳ͍ͬͯͳ͍ͱॻ͘͜ͱ͕͍͠• (ແཧཧ)ॻ͚ͯځ۶Ͱมߋʹऑ͘ͳΔ• ޙ͔Βઃܭมߋຊʹେม…(ςετ͕ͳ͍ͱঘߋ)25
• ଟ͘ͷ߹ઃܭʹ͕͋Δ• ͪΖΜϑϩʔతͳͷ߹͋Δ UnitςετΛॻ͘ϑϩʔʹͳ͍ͬͯͳ͍ɻ͕࣌ؒͳ͍ɻ26
ͦͦઃܭͱ”ؔ৺ͷʹΑͬͯ ෳࡶͳΛ୯७ͳͷ܈ͱͯ͠Γ͚Δ͜ͱ”-iOSΞϓϦઃܭύλʔϯೖ P23 ΑΓ27
• ࠷ۙυϝΠϯతʹٕज़తʹෳࡶԽ͍ͯ͠Δ• ͦͷͨΊʹઃܭΛߦ͍ෳࡶ͞ʹཱ͔ͪ͏• ͖ͪΜͱઃܭ͞Ε͍ͯΔ = ͕୯७Խ͞Ε͍ͯΔ TestableͷୈҰา28
ઃܭύλʔϯ(ΞʔΩςΫνϟ)• MVC / MVP / MVVM / Clean Architecture / Flux…• ઃܭํͱͯ͠ͷϕετϓϥΫςΟεू• ͜ΕΒͷଟ͘Testableಉ࣌ʹߟ͑ΒΕ͍ͯΔ29
ςετ͕ॻ͖͍͢ઃܭͱ• ͕୯७Խ͞Ε͍ͯΔ Ϟδϡʔϧ͝ͱʹςετ͍͕ͨ͠γϯϓϧͰ໌֬• Ϟδϡʔϧؒͷґଘੑ͕ͳ͍ ςετ͍͕ͨ͠ଞϞδϡʔϧͷӨڹΛड͚ͳ͍30
ෳࡶͳΛ࣋ͬͨϞδϡʔϧ• xxઍߦΈ͍ͨͳΫϥε• ͗ͯ͢ԿΛ͍ͬͯΔͷ͔Θ͔Βͳ͍ϝιου• ͨΒͱίϝϯτͰઆ໌ͯ͋͠Δ(ݸਓతҙݟ)31
աଟΛͲ͏ݟ͚Δ͔ʁ32
“ίʔυͷΛௌ͘”33
• ςετ͕ॻ͖ͮΒ͍…• ॲཧ͕ෳࡶԽ͖͍ͯͯ͠Δ…• ඞͣͦ͜ʹࠟ͋Δ• ίʔυͷष͍ͱ34
ઃܭͷݪଇ• ༗໊ͳͷͰSOLIDݪଇ• ୯Ұݪଇɺ։์ดݪଇɺϦείϑͷஔݪଇɺΠϯλʔϑΣʔεͷݪଇɺґଘؔٯసͷݪଇ• ઃܭͷݪଇΛ༻͍ͯίʔυͷष͍ΛऔΓআ͘
Ϟδϡʔϧؒͷґଘੑ͕͋Δ• ଞϞδϡʔϧͷ࣮͕มΘΔ͜ͱͰͪ͜Βͷॲཧ͕ӨڹΛड͚ͯ͠·͏͜ͱ36
func didTapSearchButton(text: String?) {~~~ লུ ~~~SearchModel().search(searchWord: searchWord) { result inswitch result {case .success(let events):~~ লུ ~~case .failure:~~ লུ ~~}}}37
func didTapSearchButton(text: String?) {~~~ লུ ~~~SearchModel().search(searchWord: searchWord) { result inswitch result {case .success(let events):~~ লུ ~~case .failure:~~ লུ ~~}}}͜͜ͷॲཧΛςετ͍ͨ͠38
func didTapSearchButton(text: String?) {~~~ লུ ~~~SearchModel().search(searchWord: searchWord) { result inswitch result {case .success(let events):~~ লུ ~~case .failure:~~ লུ ~~}}}SearchModelͷ࣮ʹґଘ͍ͯ͠Δ39
func didTapSearchButton(text: String?) {~~~ লུ ~~~SearchModel().search(searchWord: searchWord) { result inswitch result {case .success(let events):~~ লུ ~~case .failure:~~ লུ ~~}}}SearchModelͷ࣮͕มΘΔͱͪ͜Βͷ݁ՌมΘͬͯ͠·͏40
func didTapSearchButton(text: String?, model: SearchModel) {~~~ লུ ~~~model.search(searchWord: searchWord) { result inswitch result {case .success(let events):~~ লུ ~~case .failure:~~ লུ ~~}}}֎ଆ͔ΒΦϒδΣΫτΛ͢Α͏ʹ41
func didTapSearchButton(text: String?, model: SearchModelProtocol) {~~~ লུ ~~~model.search(searchWord: searchWord) { result inswitch result {case .success(let events):~~ লུ ~~case .failure:~~ লུ ~~}}}͢ܕΛநԽ͞Εͨܕʹ͢Δ42
43protocol SearchEventModelProtocol {func search(searchWord: String, completion: @escaping ((Result<[ConnpassEvent]>) -> ()))}Protocol
class FakeSearchEventModel: SearchEventModelProtocol {var search_successValue = [ConnpassEvent]()func search(searchWord: String, completion: @escaping ((Result<[ConnpassEvent]>) -> ())) {search_callCount += 1search_arguments = searchWordcompletion(.success(search_successValue))}}class FakeFailureSearchEventModel: SearchEventModelProtocol {func search(searchWord: String, completion: @escaping ((Result<[ConnpassEvent]>) -> ())) {search_arguments = searchWordcompletion(.failure)}}ελϒґଘ͢ΔΦϒδΣΫτͷ݁ՌΛίϯτϩʔϧͰ͖Δ
• ͜ͷґଘ͢ΔΦϒδΣΫτΛ֎ଆ͔Β্ͯ͛͠Δ͜ͱΛDI(Dependency Injection)ͱ͍͏• ͢ΦϒδΣΫτநԽ͞Ε͍ͯΔͷͰɺςετίʔυ͔ΒελϒΛ͢͜ͱ͕Ͱ͖Δ• Modelͷ࣮ʹґଘͤͣޭɺࣦഊͦΕͧΕͷςετ͕ՄೳͱͳΔ45
• ࣮ࡍʹJavaͰ͋ΕMockitoΛ͑͏গָ͠ʹͳͬͨΓ͋Δ• ͕ɺSwiftͰϥΠϒϥϦΛ༻͍ͳ͍͜ͷύλʔϯ͕ൺֱతଟ͍• ͜ΕΒͷ͕ࣝͳ͍ͱॻ͘͜ͱ͕͍͠46
ઃܭҎ֎ͷཁૉ• ςεςΟϯάϑϨʔϜϫʔΫͷ͍ํ(XCTest, Quick/Nimble)• ඇಉظॲཧͷςετ• ಛʹඇಉظ׳Εͳ͍ͱ͍͠ iOSͰReactiveX(RxSwift)͕ελϯμʔυ(Combine)47
ReactiveX• FRP(Functional Reactive Programming)• ͬ͘͟Γ͍͏ͱStreamͱ͍͏࣌ؒͷྲྀΕΛ༻͍ͯඇಉظॲཧΛ؆ܿʹॻ͚ΔΑ͏ʹͨ͠ख๏• บ͕͋ΔͷͰ׳Εͳ͍ͱগ͍͠͠• ςετͰಉظతʹѻͬͯ͋͛ͨΓɺςετ༻ͷStreamͰཧͨ͠Γ48
ςετΛॻͨ͘ΊʹΒͳ͖Ό͍͚ͳ͍͜ͱଟ͍49
• Βͳ͍ͷͰͳ͘ΔεΩϧ͕ແ͔ͬͨ• Γͨͯ͘αΫοͱͰ͖ΔઃܭͰແ͔ͬͨ• Ͱ͜ͷลͷࣝɺͲ͜Ͱֶ͍͍ͷ…ʁ
ֶͿͨΊͷ໌Δ͍ஹ͠51
ίϛϡχςΟͷಇ͖• ςετʹؔ͢Δษڧձ (TestNight)• ΫϥυϑΝϯσΟϯά(PEAKS)ʹΑΔ Android / iOSςετશॻץߦ(iOS8݄༧ఆ)52
ٕज़ॻయલճࢲ͜Μͳͷग़͠·ͨ͠
·ͱΊ• গͳ͘ͱϞόΠϧΞϓϦ։ൃऀͰςετʹର͢Δҙࣝؒҧ͍ͳ͘ߴ·͍ͬͯΔ(ͱࢥ͏)• αʔόɺϑϩϯτͳͲϑϨʔϜϫʔΫͷൃలΞδϟΠϧͷਁಁͱͱʹҙࣝߴ·͍ͬͯΔΑ͏ʹݟ͑Δ54
• ؍ଌൣғͰςετΛͲ͏ʹ͔͍ͨ͠ͱࢥ͍ͬͯΔ։ൃऀ͕ଟ͍• Ձ͋ΔαʔϏεΛಧ͚͍ͨͱ͍͏ࢥ͍୭͠ಉ͡ͳͣ• ։ൃऀ/ςετΤϯδχΞ͕ڠྗͯ͠ςετʹର͢ΔݟΛਂΊ͍͚ͯͨΒخ͍͠55
• iOSΞϓϦઃܭύλʔϯೖ ؔ ོٛɾদؗ େًɾླ େوɾਿ্ ༸ฏɾ࢙ ᠳ৽ɾాத ݡ࣏ɾՃ౻ ਓ ஶ https://peaks.cc/books/iOS_architecture• Clean Architecture ୡਓʹֶͿιϑτΣΞͷߏͱઃܭ Robert.C.Martinஶ ֯ యɾߴ ਖ਼߂ ༁ࢀߟॻ੶
• Test Night https://testnight.connpass.com/• Androidςετશॻ https://peaks.cc/books/android_testing• iOSςετશॻ https://peaks.cc/iOS_testing
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠58