iOSアプリ開発における自動化ノウハウ

 iOSアプリ開発における自動化ノウハウ

9bf923e39671cde83584e3e926296c13?s=128

Kishikawa Katsumi

March 01, 2015
Tweet

Transcript

  1. J04ΞϓϦ։ൃʹ͓͚Δ ࣗಈԽϊ΢ϋ΢ IUUQCJUMZNPTB@BVUPNBUJPO

  2. kishikawa katsumi http://kishikawakatsumi.com Twitter @k_katsumi https://github.com/kishikawakatsumi http://kishikawakatsumi.hatenablog.com

  3. ࠓ೔࿩͢͜ͱ

  4. • ϦϦʔεϓϩηεΛࣗಈԽ͢Δʹ͸ • Ϗϧυ؀ڥΛΫϦʔϯʹอͭ • ख࡞ۀΛͳ͘͢ • ςετͷࣗಈԽ • ܧଓతΠϯςάϨʔγϣϯʢCIʣ

    • ίʔυΧόϨοδ ࠓ೔࿩͢͜ͱ
  5. • ੓࣏తͳ͜ͱ • ؀ڥΛ੔͑Δ࣌ؒΛ֫ಘ͢Δʹ͸ • ͓ۚͷަবͷ࢓ํ • etc… ࠓ೔࿩͞ͳ͍͜ͱ

  6. • ࣗಈԽͷϝϦοτ͕Θ͔Δ • අ༻ରޮՌͷόϥϯε͕Θ͔Δ • ࠓ͙͢ʹࣗಈԽΛ࣮ફͰ͖ΔΑ͏ʹͳΔ • Ͱ͖Δ͜ͱ͔Β࢝ΊΒΕΔ • ҰਓͰ΋Ͱ͖Δ

    ࠓ೔ͷΰʔϧ
  7. ෆ҆Λ๊͑ͯ ϦϦʔε͍ͯ͠·ͤΜ͔ʁ

  8. มߋ มߋ มߋ มߋ มߋ W ϦϦʔε ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε

  9. มߋ มߋ มߋ มߋ มߋ W ϦϦʔε ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε HJUQVMM "MSFBEZVQUPEBUF

    2. खݩͷ؀ڥΛ࠷৽ʹߋ৽ͯ͠ 1. ϦϞʔτϦϙδτϦͷߋ৽ΛࢭΊͯ 3. όʔδϣϯ൪߸Λߋ৽ͯ͠
  10. ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε 4. XcodeͰϦϦʔεߏ੒ͰϏϧυˍΞʔΧΠϒ

  11. ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε 5. ผʹAdHoc൛ΛϏϧυͯؔ͠܎ऀʹ഑෍ˍݕূ 6. iTunes ConnectͰ৽͍͠όʔδϣϯΛ࡞੒ɺߋ৽಺༰ͳͲΛهड़ 7. XcodeͷΦʔΨφΠβʔ͔ΒΞοϓϩʔυ

  12. ຊ౰ʹେৎ෉Ͱ͔͢ʁ

  13. มߋ มߋ มߋ มߋ มߋ W ਃ੥ ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε HJUQVMM 6QEBUJOHFFCE

    FSSPS:PVSMPDBMDIBOHFTUPUIFGPMMPXJOHpMFTXPVMECFPWFSXSJUUFOCZNFSHF 6CJSFHJ"QQ%FMFHBUFN 1MFBTF DPNNJUZPVSDIBOHFTPSTUBTIUIFNCFGPSFZPVDBONFSHF "CPSUJOH 2. खݩͷ؀ڥΛ࠷৽ʹߋ৽ͯ͠ 1. ϦϞʔτϦϙδτϦͷߋ৽ΛࢭΊͯ ߋ৽ʹࣦഊͨ͠ͷʹؾ͔ͮͳ͔ͬͨ
  14. มߋ มߋ มߋ มߋ มߋ W ਃ੥ ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε HJUGFUDI 

    2. खݩͷ؀ڥΛ࠷৽ʹߋ৽ͯ͠ 1. ϦϞʔτϦϙδτϦͷߋ৽ΛࢭΊͯ HJUGFUDI͚ͩͯ͠NFSHFΛ๨Εͨ
  15. มߋ มߋ มߋ มߋ มߋ W ਃ੥ ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε HJUQVMM "MSFBEZVQUPEBUF

    2. खݩͷ؀ڥΛ࠷৽ʹߋ৽ͯ͠ 1. ϦϞʔτϦϙδτϦͷߋ৽ΛࢭΊͯ ࣮͸खݩͷํ͕ϦϞʔτΑΓਐΜͰ͍ͨ
  16. ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε 4. XcodeͰϦϦʔεߏ੒ͰϏϧυˍΞʔΧΠϒ Ϗϧυߏ੒Λؒҧ͑ͨ

  17. มߋ มߋ มߋ W ਃ੥ ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε 1. ϦϞʔτϦϙδτϦͷߋ৽ΛࢭΊͯ มߋ มߋ

    ਃ੥ͨ͠ͷ͸ຊ౰ʹ͜ͷϦϏδϣϯʁ ʢ࣮͸୭͔͕1VTI͍͔ͯͨ͠΋͠Εͳ͍ʣ
  18. มߋ มߋ มߋ W ਃ੥ ख࡞ۀͷʢ͚ͩͲΑ͋͘ΔʣϦϦʔεϓϩηε 1. ϦϞʔτϦϙδτϦͷߋ৽ΛࢭΊͯ มߋ มߋ

    ͜ͷ࣌఺ͰϦδΣΫτʹͳͬͨ৔߹Ͳ͏͢Δʁ
  19. • खݩͷ؀ڥ͕ϦϞʔτͱҟͳ͍ͬͯͨ • Ϗϧυߏ੒͕ҧ͍ͬͯͨ • Provisioning Profile͕ҧ͏ • όʔδϣϯ൪߸Λ͚ͭؒҧ͑ͨ •

    ϦδΣΫτ͞Εͨ৔߹ɺͲ͜ʹ໭Δ͔ • etc… ख࡞ۀͷϦϦʔεϓϩηεʹ͓͚Δࣦഊྫ
  20. ख࡞ۀͷϦϦʔεϓϩηεͷؒҧ͍͸ݕূ͕ࠔ೉

  21. ϦϦʔεϓϩηε͕ ࣗಈԽ͞Ε͍ͯΔ৔߹

  22. ࣗಈԽ͞ΕͨϦϦʔεϓϩηε 1. ୭͔͕νϟοτͰʮ@ubibot releaseʯͱൃݴ͢Δ BϦϦʔεϒϥϯν͕࡞ΒΕΔ CϦϦʔεϒϥϯν͔ΒNBTUFS΁ͷ 1VMM3FRVFTU͕࡞ΒΕΔ DϦϦʔεϏϧυ͕࡞ΒΕΔ

  23. ࣗಈԽ͞ΕͨϦϦʔεϓϩηε 2. ୭͔͕νϟοτͰʮ@ubibot submitʯͱൃݴ͢Δ EJ5VOFT$POOFDUʹΞοϓϩʔυ͞ΕΔ

  24. ࣗಈԽ͞ΕͨϦϦʔεϓϩηε @ubibot release ϦϦʔεϒϥϯν ϓϧϦΫΤετ "E)PD഑෍ ΫϥογϡϨϙʔτ ਃ੥

  25. ࣗಈԽʹΑͬͯ΋ͨΒ͞ΕΔ΋ͷ 2. ࡞ۀ͕ՄࢹԽ͞ΕΔ 1. ࡞ۀ͕෼୲Ͱ͖ΔΑ͏ʹͳΔ

  26. ࣗಈԽʹΑͬͯ΋ͨΒ͞ΕΔ΋ͷ 3. ͲͷϦϏδϣϯ͕ਃ੥͞Εͨͷ͔ه࿥ʹ࢒Δ

  27. มߋ มߋ มߋ W ਃ੥ ࣗಈԽʹΑͬͯ΋ͨΒ͞ΕΔ΋ͷ มߋ มߋ ͜ͷ࣌఺ͰϦδΣΫτʹͳͬͯ΋ɺ

  28. มߋ มߋ มߋ W ࠶ਃ੥ ࣗಈԽʹΑͬͯ΋ͨΒ͞ΕΔ΋ͷ มߋ มߋ मਖ਼ΛϦϦʔεϒϥϯνʹੵΜͰ࠶ਃ੥͢Δ͚ͩ

  29. • ܁Γฦ͠ͷख࡞ۀ͔Βղ์͞ΕΔ • ຖճಉ͡ߏ੒Ͱ࣮֬ʹϏϧυͰ͖Δ • ΫϦʔϯͳ؀ڥͰϏϧυ͞ΕΔ • ͲͷϦϏδϣϯ͕Ϗϧυ͞Εͨͷ͔ه࿥ʹ࢒Δ • ୭Ͱ΋ͦͷ࡞ۀ͕Ͱ͖ΔΑ͏ʹͳΔ

    • ࡞ۀ͕෼୲Ͱ͖Δ • ࡞ۀͷه࿥͕࢒Δ • ࡞ۀ͕ՄࢹԽ͞ΕΔ • etc… ࣗಈԽʹΑͬͯ΋ͨΒ͞ΕΔ΋ͷ
  30. • ܁Γฦ͠ͷख࡞ۀ͔Βղ์͞ΕΔ • ຖճಉ͡ߏ੒Ͱ࣮֬ʹϏϧυͰ͖Δ • ΫϦʔϯͳ؀ڥͰϏϧυ͞ΕΔ • ͲͷϦϏδϣϯ͕Ϗϧυ͞Εͨͷ͔ه࿥ʹ࢒Δ • ୭Ͱ΋ͦͷ࡞ۀ͕Ͱ͖ΔΑ͏ʹͳΔ

    • ࡞ۀ͕෼୲Ͱ͖Δ • ࡞ۀͷه࿥͕࢒Δ • ࡞ۀ͕ՄࢹԽ͞ΕΔ • etc… ࣗಈԽʹΑͬͯ΋ͨΒ͞ΕΔ΋ͷ
  31. w ʮࣗಈԽͰ͖Δঢ়ଶʯΛอͭ͜ͱ͕ॏཁ w ʮࣗಈԽͰ͖Δঢ়ଶʯͱ͸λεΫ͕γϯϓϧʹ෼ׂ ͞ΕͯػցͰ΋࣮ߦՄೳͰ͋Δͱ͍͏͜ͱ w ػցͰ࣮ߦͰ͖ΔͳΒɺ܁Γฦͨ͠Γɺه࿥΍࿈ܞ ͕༰қͰ͋Δ w ୭͕ݟͯ΋ঢ়ଶ͕೺ѲͰ͖ΔΑ͏ʹͳΔ

    w ࣗಈԽ͕໨తͰ͸ͳͯ͘ɺࣗಈԽͰ͖͍ͯΔͳΒʮࣗ ಈԽͰ͖Δঢ়ଶʯ͕ҡ࣋Ͱ͖͍ͯΔͱ͍͏͜ͱ w ࣗಈԽʹΑͬͯʮࣗಈԽͰ͖Δঢ়ଶʯ͕ҡ࣋͞Εͯ Δ͜ͱΛอূ͢Δ
  32. ϓϩδΣΫτ͔ΒϒϥοΫϘοΫεΛແ͘͢

  33. ࣗಈԽ΁ͷऔΓ૊Έ

  34. • มߋ؅ཧ • Github • Pull Request • ίʔυϨϏϡʔ •

    ϥΠϒϥϦ؅ཧ • ࣗಈϏϧυ • ςετ • ςετͷࣗಈԽ • ܧଓతΠϯςάϨʔγϣϯʢCIʣ • ίʔυΧόϨοδ ࣗಈԽ΁ͷऔΓ૊Έ
  35. • ϦϦʔεͷࣗಈԽ • ࣗಈϏϧυ • ഑෍ͷࣗಈԽ • ਃ੥ͷࣗಈԽ ࣗಈԽ΁ͷऔΓ૊Έ

  36. ࣗಈԽ͢Δલͷ४උ

  37. w ϓϩδΣΫτΛμ΢ϯϩʔυͨ͠Βɺ ͦͷ··ϏϧυͰ͖ΔΑ͏ʹ͢Δ w ϓϩδΣΫτಛ༗ͷϧʔϧΛແ͘͢

  38. ࠷ॳʹ͍͔ͭ͘ͷϑΝΠϧΛੜ੒͢Δඞཁ͕͋Δ $ update-hostname.rb

  39. ىಈ࣌ύϥϝʔλͰࢦఆ͢ΔΑ͏ʹมߋ

  40. w Ϗϧυߏ੒Λγϯϓϧʹ͢Δ w ϓϩδΣΫτಛ༗ͷϧʔϧΛແ͘͢ w ୅ΘΓʹ୭Ͱ΋஌͍ͬͯΔϝδϟʔ ͳπʔϧΛ࢖͓͏

  41. ىಈ࣌ύϥϝʔλͰࢦఆ͢ΔΑ͏ʹมߋ

  42. ىಈ࣌ύϥϝʔλͰࢦఆ͢ΔΑ͏ʹมߋ

  43. มߋ؅ཧ

  44. • ෼ࢄܕόʔδϣϯ؅ཧγεςϜ • Git • Mercurial มߋ؅ཧ

  45. • ෼ࢄܕόʔδϣϯ؅ཧγεςϜ • Git → Github, Bitbucket • Mercurial →

    Bitbucket มߋ؅ཧ Github͔BitbucketΛҰॹʹ࢖͓͏ʂ
  46. 1VMM3FRVFTU

  47. None
  48. None
  49. Pull Request Pull Request github-flow masterϒϥϯν͸ৗʹϦϦʔεՄೳͳঢ়ଶʹอͭ master

  50. Pull Request Pull Request git-flow masterϒϥϯν͸ৗʹϦϦʔεՄೳͳঢ়ଶʹอͭ develop master

  51. • มߋ͕୭ʹͰ΋ݟ͑ΔΑ͏ʹͳΔ • ࡞ۀͷཤྺ͕͢΂ͯه࿥ʹ࢒Δ • ίʔυϨϏϡʔ • ਐḿ͕ՄࢹԽ͞ΕΔ Pull RequestʹΑΔ։ൃελΠϧ

  52. [WIP] Work In Progressʢ࡞ۀதʣPull Requestͷ׆༻

  53. • ํ਑Λݕ౼͠ͳ͕ΒਐΊΒΕΔ • ͱΓ͋͑ͣಈ͔ͯ͠ࢼͯ͠ΈΔ • ϦϦʔεϒϥϯνʹӨڹΛ༩͑ͳ͍ • etc… [WIP] Work

    In Progressʢ࡞ۀதʣPull Requestͷ׆༻
  54. େ͖ͳมߋ͸ϒϥϯνΛผʹ͢Δ

  55. Pull Request Pull Request master 3_0ϒϥϯν

  56. ֎෦ϥΠϒϥϦͷ؅ཧ

  57. ΞϓϦͱϥΠϒϥϦͷίʔυ͕͍ࠞͬͯ͟Δ৔߹

  58. • ݱࡏͲͷόʔδϣϯΛ࢖ͬͯΔ͔Θ͔ Βͳ͍ • ΋͔ͨ͠͠ΒԿ͔खΛೖΕͯΔ͔΋͠ Εͳ͍ →্ॻ͖ͨ͠ΒյΕΔ ΞϓϦͱϥΠϒϥϦͷίʔυ͕͍ࠞͬͯ͟Δ৔߹

  59. $PDPB1PET

  60. source 'https://github.com/CocoaPods/Specs.git' source 'https://1234abcdxxxxxxxxxxxx@github.com/ubiregiinc/Specs.git' platform :ios, '7.0' inhibit_all_warnings! pod 'UUIDShortener'

    pod 'JLRoutes' pod 'NLCoreData' pod 'AFNetworking' pod 'AFNetworkActivityLogger' pod 'SDWebImage' pod 'PEPhotoCropEditor' pod 'UICKeyChainStore' pod 'GCDWebServer', '~> 1.4' pod 'FXImageView' pod 'FastCoding' pod 'SSZipArchive' pod 'Helpshift' pod 'CrittercismSDK' pod 'Aspects' pod 'GoogleAnalytics-iOS-SDK' target :Ubiregi2Tests do pod 'OCMock' pod 'OCHamcrest' pod 'OHHTTPStubs' end
  61. PODS: - AFNetworkActivityLogger (2.0.3): - AFNetworking/NSURLConnection (~> 2.0) - AFNetworking/NSURLSession

    (~> 2.0) - AFNetworking (2.5.0): - AFNetworking/NSURLConnection (= 2.5.0) - AFNetworking/NSURLSession (= 2.5.0) - AFNetworking/Reachability (= 2.5.0) - AFNetworking/Security (= 2.5.0) - AFNetworking/Serialization (= 2.5.0) - AFNetworking/UIKit (= 2.5.0) - Aspects (1.4.1) - CoineyKit-iOS (0.0.7) - CrittercismSDK (5.0.7) - FastCoding (3.2) - FXImageView (1.3.5) - GCDWebServer (1.4) - GoogleAnalytics-iOS-SDK (3.10): - GoogleAnalytics-iOS-SDK/Core (= 3.10) - GoogleAnalytics-iOS-SDK/Core (3.10) - Helpshift (4.9.1) - JLRoutes (1.5.1) - M13BadgeView (1.0.4) - NLCoreData (0.5.3) - OCHamcrest (4.1.1) - OCMock (3.1.2) - OHHTTPStubs (3.1.10): - OHHTTPStubs/Core (= 3.1.10) - OHHTTPStubs/Core (3.1.10) - PEPhotoCropEditor (1.3.1) - SDWebImage (3.7.1): - SDWebImage/Core (= 3.7.1) - SDWebImage/Core (3.7.1) - SEJSONViewController (0.2.0) - SSZipArchive (0.3.2) - UICKeyChainStore (2.0.2) - UUIDShortener (1.0.1) - YLProgressBar (3.5.1)
  62. None
  63. ґଘπʔϧͷ؅ཧ

  64. #VOEMFS

  65. None
  66. source 'https://rubygems.org' gem 'rake' gem 'cocoapods' gem 'xcpretty' gem 'xcjobs'

  67. • CocoaPods • iOS/OS XΞϓϦͷϥΠϒϥϦ؅ཧπʔ ϧ • Bundler • RubygemsʢRubyͷϥΠϒϥϦʣ؅ཧ

    πʔϧ • ͲͪΒ΋σϑΝΫτελϯμʔυ ֎෦ϥΠϒϥϦͷ؅ཧ
  68. • ࣗ෼͕ϝϯςφϯε͍ͯ͠Δίʔυͱɺ ͦ͏Ͱͳ͍ίʔυΛ۠ผ͢Δ ʢϓϩδΣΫτΛγϯϓϧʹอͭʣ • ࠓԿͷͲͷόʔδϣϯΛ࢖͍ͬͯΔͷ ͔Θ͔ΔΑ͏ʹ͢Δ • มߋΛه࿥ʹ࢒͢ ֎෦ϥΠϒϥϦͷ؅ཧ

  69. ܧଓతΠϯςάϨʔγϣϯ $POUJOVPVT*OUFHSBUJPO

  70. • master (git flowͷ৔߹͸develop) ΛৗʹϦϦʔε Մೳͳঢ়ଶʹอͭ • ࣗಈϏϧυʢςετͳ͠ʣ͚ͩͰ΋ޮՌΞϦ • ഑෍·ͰࣗಈԽ͢ΔͱޮՌେ

    • ΋ͪΖΜςετ͕͋Δͱ͞Βʹྑ͍ ܧଓతΠϯςάϨʔγϣϯʢCIʣ
  71. • WebαʔϏε • Travis CI • Circle CI • Greenhouse

    CI • ιϑτ΢ΣΞ • Jenkins มߋ؅ཧ
  72. • Travis CI • CIαʔϏεͷ࿝ฮ • ϓϥΠϕʔτϦϙδτϦ͸༗ྉ • ೚ҙͷॲཧΛ࣮ߦͰ͖Δ มߋ؅ཧ

  73. • Circle CI • ϓϥΠϕʔτϦϙδτϦ΋ແྉ • iOS/OS XͷϏϧυ͸ϕʔλ • ೚ҙͷॲཧΛ࣮ߦͰ͖Δ

    มߋ؅ཧ
  74. • Greenhouse CI • ϓϥΠϕʔτϦϙδτϦ΋ແྉ • ॲཧͷΧελϚΠζ͸Ͱ͖ͳ͍ʢͨͩ ͠ඪ४ͷॲཧ͕ඇৗʹ༏लʣ มߋ؅ཧ

  75. Greenhouse CI

  76. Greenhouse CI

  77. $*αʔϏεͱ࿈ܞ͢Δʹ͸

  78. ίϚϯυϥΠϯ

  79. xcodebuild test -workspace Ubiregi2.xcworkspace -scheme Ubiregi2-Release -sdk iphonesimulator -configuration Release

    -derivedDataPath build -destination name=iPad 2,OS=7.1 -destination name=iPad Air,OS=8.1 CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO GCC_SYMBOLS_PRIVATE_EXTERN=NO 1. γϛϡϨʔλ༻ʹϏϧυͯ͠ɺςετΛ࣮ߦ ίϚϯυϥΠϯ • Ϗϧυߏ੒(Debug/Release)͸Ҿ਺Ͱࢦఆ • destinationύϥϝʔλͰ࣮ߦ؀ڥΛࢦఆͰ͖Δ • GCC_SYMBOLS_PRIVATE_EXTERNύϥϝʔλΛҾ਺Ͱ্ॻ͖
  80. $ xcodebuild build -workspace Ubiregi2.xcworkspace -scheme Ubiregi2-Release -configuration Release -derivedDataPath

    "build" CONFIGURATION_TEMP_DIR="build/temp" CODE_SIGN_IDENTITY="iPhone Distribution: Ubiregi Inc. (A1234567XZ)" 2. σόΠε༻ʹϏϧυ͢Δɻ ίϚϯυϥΠϯ • ϓϩϏδϣχϯάϓϩϑΝΠϧ͸Ҿ਺Ͱࢦఆ ʢෳ਺ͷλʔήοτ͕͋Δ৔߹͸.xcodeprojͰࢦఆ͢Δʣ
  81. $ xcodebuild archive -workspace Ubiregi2.xcworkspace -scheme Ubiregi2-Release -configuration Release -derivedDataPath

    "build" CONFIGURATION_TEMP_DIR="build/temp" CODE_SIGN_IDENTITY="iPhone Distribution: Ubiregi Inc. (A1234567XZ)" -archivePath "build/Ubiregi2-Release" 3. ϦϦʔεߏ੒ͰϏϧυͯ͠ɺ.xcarchiveʢਃ੥༻ͷΞʔΧΠϒʣΛ࡞Δ ίϚϯυϥΠϯ • ϓϩϏδϣχϯάϓϩϑΝΠϧ͸Ҿ਺Ͱࢦఆ ʢෳ਺ͷλʔήοτ͕͋Δ৔߹͸.xcodeprojͰࢦఆ͢Δʣ
  82. $ xcodebuild -exportArchive -archivePath "build/Ubiregi2-Release.xcarchive" -exportFormat "IPA" -exportPath "build/Ubiregi2-AdHoc.ipa" -exportProvisioningProfile

    "Ubiregi AdHoc" 4. ਃ੥༻ͷϏϧυ͔ΒɺϓϩϏδϣχϯάϓϩϑΝΠϧ͚ͩม͑ͯ ݕূ༻ͷϕʔλ൛ͷ.ipaΛ࡞੒͢Δ ʢਃ੥ͨ͠΋ͷͱॺ໊Ҏ֎͸શ͘ಉ͡όΠφϦͰݕূͰ͖Δʣ ίϚϯυϥΠϯ
  83. $ xcodebuild -exportArchive -archivePath "build/Ubiregi2-Release.xcarchive" -exportFormat "IPA" -exportPath "build/Ubiregi2-Release-AppStore.ipa" -exportProvisioningProfile

    "Ubiregi App Store" 5. ਃ੥༻ͷϏϧυ͔ΒɺApplication LoaderͰΞοϓϩʔυ͢ΔͨΊʹ.ipaΛ࡞੒͢Δ ίϚϯυϥΠϯ
  84. 3BLF

  85. def build_options(opt = {configuration: "Release"}) options = { sdk: "iphoneos",

    workspace: "Ubiregi2-Release.xcworkspace", scheme: "Ubiregi2-Release", }.merge opt join_options(options: options, prefix: "-", separator: " ") end def build_settings(configuration: "Release") settings = { CONFIGURATION_BUILD_DIR: "build", CONFIGURATION_TEMP_DIR: "build/temp", CODE_SIGN_IDENTITY: "iPhone Distribution: Ubiregi Inc. (Y7522692LT)", } settings = join_options(options: settings, prefix: "", separator: "=") end def test(configuration: "Release") options = build_options(sdk: "iphonesimulator", configuration: configuration) options << DESTINATIONS.map { |destination| %(-destination "#{destination}") }.join(" ") sh "xcodebuild #{options} test | xcpretty -c" end def build_xcarchive(configuration: "Release") options = build_options(configuration: configuration) settings = build_settings(configuration: configuration) sh "xcodebuild #{options} #{settings} archive -archivePath #{ARCHIVE_FILE} | xcpretty -c" end def export_ipa sh "xcodebuild -exportArchive -exportFormat IPA -archivePath #{ARCHIVE_FILE} -exportPath #{IPA_FILE} | xcpretty -c" end ֤ίϚϯυΛλεΫʹ͢Δ Rake
  86. release_date = DateTime.now.strftime("%Y/%m/%d %H:%M:%S") release_notes = "Build: #{InfoPlist.marketing_and_build_version} \nUploaded: #{release_date}"

    fields = { file: "@#{IPA_FILE}", api_token: API_TOKEN, team_token: TEAM_TOKEN, notes: release_notes, notify: true, distribution_lists: DISTRIBUTION_LISTS } fields = join_option(options: fields, prefix: "-F ", seperator: "=") sh "curl -sL -w \"%{http_code} %{url_effective}\\n\" http:// testflightapp.com/api/builds.json #{fields} -o /dev/null ֤ίϚϯυΛλεΫʹ͢Δ Rake
  87. release_date = DateTime.now.strftime("%Y/%m/%d %H:%M:%S") release_notes = "Build: #{InfoPlist.marketing_and_build_version} \nUploaded: #{release_date}"

    fields = { file: "@#{IPA_FILE}", api_token: API_TOKEN, team_token: TEAM_TOKEN, notes: release_notes, notify: true, distribution_lists: DISTRIBUTION_LISTS } fields = join_option(options: fields, prefix: "-F ", seperator: "=") sh "curl -sL -w \"%{http_code} %{url_effective}\\n\" http:// testflightapp.com/api/builds.json #{fields} -o /dev/null ֤ίϚϯυΛλεΫʹ͢Δ Rake
  88. $ rake test $ rake archive $ rake testflight ֤ίϚϯυΛλεΫʹ͢Δ

    Rake
  89. 9$+PCT

  90. XCJobs::Test.new do |t| t.workspace = 'Ubiregi2' t.scheme = 'Ubiregi2-Release' t.configuration

    = 'Release' t.build_dir = 'build' t.add_destination('name=iPad 2,OS=7.1') t.add_destination('name=iPad Air,OS=8.1') end xcodebuild test -workspace Ubiregi2.xcworkspace -scheme Ubiregi2-Release -sdk iphonesimulator -configuration Release -derivedDataPath build -destination name=iPad 2,OS=7.1 -destination name=iPad Air,OS=8.1 CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO GCC_SYMBOLS_PRIVATE_EXTERN=NO $ rake test 1. γϛϡϨʔλ༻ʹϏϧυͯ͠ɺςετΛ࣮ߦ
  91. XCJobs::Archive.new do |t| t.workspace = APP_NAME t.scheme = SCHEME t.configuration

    = configuration t.signing_identity = signing_identity t.build_dir = BUILD_DIR t.formatter = 'xcpretty -c' end xcodebuild archive -workspace Ubiregi2.xcworkspace -scheme Ubiregi2-Release -configuration Release -derivedDataPath "build" CONFIGURATION_TEMP_DIR="build/temp" CODE_SIGN_IDENTITY="iPhone Distribution: Ubiregi Inc. (A1234567XZ)" -archivePath "build/Ubiregi2-Release" $ rake build:archive 3. ϦϦʔεߏ੒ͰϏϧυͯ͠ɺ.xcarchiveʢਃ੥༻ͷΞʔΧΠϒʣΛ࡞Δ
  92. XCJobs::Export.new do |t| t.archive_path = File.join('build', 'Ubiregi2.xcarchive') t.export_path = File.join('build',

    'Ubiregi2.ipa') t.export_provisioning_profile = 'Ubiregi_Ad_Hoc.mobileprovision' t.formatter = 'xcpretty -c' end xcodebuild -exportArchive -archivePath "build/Ubiregi2.xcarchive" -exportFormat "IPA" -exportPath "build/Ubiregi2-AdHoc.ipa" -exportProvisioningProfile "Ubiregi AdHoc" $ rake build:export 4. ਃ੥༻ͷϏϧυ͔ΒɺϓϩϏδϣχϯάϓϩϑΝΠϧ͚ͩม͑ͯ ݕূ༻ͷϕʔλ൛ͷ.ipaΛ࡞੒͢Δ
  93. language: objective-c install: - bundle install - bundle exec pod

    install script: - bundle exec rake test profiles:install certificates:install version:set_build_version build:archive build:export distribute:testflight CIͱ؆୯ʹ࿈ܞͰ͖Δ
  94. XCJobs::Test.new do |t| t.workspace = 'UCZProgressView' t.scheme = 'UCZProgressView' t.configuration

    = 'Release' t.build_dir = 'build' t.coverage = true t.formatter = 'xcpretty -c' end XCJobs::Coverage::Coveralls.new() do |t| t.add_extension('.m') t.add_exclude('Example') end ؆୯ͳϓϩδΣΫτͷ৔߹
  95. language: objective-c install: - bundle install script: - bundle exec

    rake test coverage:coveralls ؆୯ͳϓϩδΣΫτͷ৔߹
  96. GBTUMBOF IUUQGBTUMBOFUPPMT

  97. None
  98. Ϣχοτςετ

  99. • ςετΛϓϩάϥϜͰهड़͢Δ • Կ౓Ͱ΋ࣗಈͰςετͰ͖Δ • มߋʹڧ͘ͳΔ • มߋ͕͠΍͘͢ͳΔ Ϣχοτςετ

  100. • ख࡞ۀ͕ܰݮ͢Δ • มߋʹର͢Δ৺ཧతෆ҆ΛऔΓআ͘ Ϣχοτςετ ̎छྨͷҟͳΔϝϦοτ

  101. • ࣗಈԽ͢Δͱ඼্࣭͕Δ • ୭Ͱ΋ॻ͚Δ • ςετΛॻ͘εΩϧ • ςετ͠΍͍͢ίʔυΛॻ͘εΩϧ • ݁߹ςετ͕ෆཁʹͳΔ

    Ϣχοτςετ Α͋͘Δޡղ
  102. Ϣχοτςετ 1. ςετλʔήοτΛ௥Ճ͢Δ

  103. 2. ςετίʔυΛ௥Ճ͢Δ Ϣχοτςετ

  104. 3. खݩͰ࣮ߦ͢Δ Ϣχοτςετ

  105. 4. CIͱ࿈ܞ͢Δ Ϣχοτςετ

  106. Ϣχοτςετ /* "vat" : 5 "price" : "2000.0", "price_type" :

    "intax" */ UBCheckoutItem *checkoutItem = nil; for (checkoutItem in self.checkoutItems) { if (checkoutItem.checkoutMenuItem.isTaxIncluded) { break; } } checkoutItem.discount = [NSDecimalNumber decimalNumberWithString:@"500"]; checkoutItem.count = @(1); XCTAssertEqualObjects(checkoutItem.apiSalesNet, [NSDecimalNumber decimalNumberWithString:@"1905"]); XCTAssertEqualObjects(checkoutItem.apiSalesTax, [NSDecimalNumber decimalNumberWithString:@"95"]); XCTAssertEqualObjects(checkoutItem.apiDiscountNet, [NSDecimalNumber decimalNumberWithString:@"476"]); XCTAssertEqualObjects(checkoutItem.apiDiscountTax, [NSDecimalNumber decimalNumberWithString:@"24"]); XCTAssertEqualObjects(checkoutItem.discountRatio, [NSDecimalNumber decimalNumberWithString:@"25"]); checkoutItem.count = @(2); XCTAssertEqualObjects(checkoutItem.apiSalesNet, [NSDecimalNumber decimalNumberWithString:@"3810"]); XCTAssertEqualObjects(checkoutItem.apiSalesTax, [NSDecimalNumber decimalNumberWithString:@"190"]); XCTAssertEqualObjects(checkoutItem.apiDiscountNet, [NSDecimalNumber decimalNumberWithString:@"476"]); XCTAssertEqualObjects(checkoutItem.apiDiscountTax, [NSDecimalNumber decimalNumberWithString:@"24"]); ֹۚͷςετ
  107. Ϣχοτςετ NSManagedObjectContext *context = [NSManagedObjectContext mainContext]; NSMutableOrderedSet *checkoutItems = [[NSMutableOrderedSet

    alloc] init]; UBCheckoutItem *item; item = [UBCheckoutItem checkoutItemWithMenuItem:self.menuItems[@"intaxItem"]]; item.count = @(1); item.discount = [NSDecimalNumber decimalNumberWithString:@"500"]; [checkoutItems addObject:item]; item = [UBCheckoutItem checkoutItemWithMenuItem:self.menuItems[@"outtaxItem"]]; item.count = @(1); item.discount = [NSDecimalNumber decimalNumberWithString:@"190"]; [checkoutItems addObject:item]; item = [UBCheckoutItem checkoutItemWithMenuItem:self.menuItems[@"nosalesItem"]]; item.count = @(1); item.discount = [NSDecimalNumber decimalNumberWithString:@"-500"]; [checkoutItems addObject:item]; item = [UBCheckoutItem checkoutItemWithMenuItem:self.menuItems[@"percentageItem"]]; item.count = @(1); [checkoutItems addObject:item]; UBCheckout *checkout = [UBCheckout insertInContext:context]; checkout.checkoutItems = checkoutItems; XCTAssertEqualObjects(checkout.total, [NSDecimalNumber decimalNumberWithString:@"3737"], ɹɹɹɹɹɹɹɹɹɹɹɹɹ@"Ϣʔβʔʹ੥ٻ͞ΕΔֹۚ"); XCTAssertEqualObjects(checkout.tax, [NSDecimalNumber decimalNumberWithString:@“192"], ɹɹɹɹɹɹɹɹɹɹɹɹɹ @"಺ফඅ੫ֹ"); ֹۚͷςετ
  108. Ϣχοτςετ NSDictionary *response = [UBTestUtils JSONObjectWithPath:self.dataPath]; [UBTestUtils stubWithJSONObject:response path:self.endpoint]; NSManagedObjectContext

    *context = [NSManagedObjectContext mainContext]; UBAccount *account = [UBAccount currentAccountInContext:context]; UBAccountSynchronizer *synchronizer = [[UBAccountSynchronizer alloc] initWithAccountID:account.identifier]; XCTestExpectation *expectation = [self expectationWithDescription:@"finish synchronization"]; synchronizer.completionHandler = ^(NSError *error) { assertThat(error, nilValue()); [expectation fulfill]; }; [synchronizer synchronize]; [self waitForExpectationsWithTimeout:10.0 handler:nil]; [context refreshObject:account mergeChanges:YES]; NSDictionary *result = account.dictionaryRepresentation; NSDictionary *expected = response[@"account"]; XCTAssertEqualObjects(result[@"id"], expected[@"id"]); XCTAssertEqualObjects(result[@"login"], expected[@"login"]); XCTAssertEqualObjects(result[@"name"], expected[@"name"]); XCTAssertEqualObjects(result[@"email"], expected[@"email"]); ௨৴ͷςετ
  109. Ϣχοτςετ + (id)stubWithJSONObject:(id)JSONObject statusCode:(int)statusCode path: (NSString *)path { id<OHHTTPStubsDescriptor> stub

    = nil; stub = [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { return [request.URL.path hasPrefix:path]; } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) { return [[OHHTTPStubsResponse responseWithJSONObject:JSONObject statusCode:statusCode headers:nil] requestTime:1.0 responseTime:1.0]; }]; return stub; } ௨৴Λελϒʹ͢Δ
  110. • Ϟσϧͷςετ͸ॻ͖΍͍͢ • ௨৴ͷςετ͸ελϒɾϞοΫΛ͏·͘࢖͏ • جຊతʹ֎෦ཁҼͰࣦഊ͢Δςετ͸ϝϯ ςφϯε͕೉͍͠ Ϣχοτςετ

  111. • ςετ͸Πϯϑϥ • ؀ڥΛ੔͑Δ͜ͱ͕େࣄ • جຊతʹ໘౗ͳͷͰࣗಈతʹ࣮ߦ͞ΕΔ࢓૊ ΈΛ࡞Δ • ఆظతʹ࣮ߦ͢Δ •

    ॻ͖΍͍͢ͱ͜Ζ͔Βॻ͘ • େࣄͳͱ͜Ζ͔Βॻ͘ • ॻ͖΍͍͢Α͏ͳઃܭΛ͜͜Ζ͕͚Δ Ϣχοτςετ
  112. • ςετΛॻ͘ʹ͸εΩϧ͕ඞཁ • ςετ͕͋Δ͜ͱͰಘΒΕΔ҆৺ײͳͲΛܦ ݧ͢Δ͜ͱ͕େࣄ • ͍ͬͨΜςετ͕ॻ͔Εͩͨ͠Βৗʹςετ ͕ಈ͘؀ڥΛอ࣋͢Δ • ޲͖ෆ޲͖͕͋ΔͷͰίετʹݟۃΊ͸େ੾

    • ͕Μ͹Γ͗͢ͳ͍ Ϣχοτςετ
  113. • ख๏ʹͱΒΘΕ͗͢ͳ͍ • ςετϑΝʔετ, BDD, etc… • ֤ࣗͷελΠϧʹ೚ͤΔ Ϣχοτςετ

  114. • 6ʙ8ׂ͘Β͍ಈ͘Α͏ʹͳͬͨͱ͜ΖͰ͓ ΋ΉΖʹςετΛॻ͖࢝ΊΔ • ਖ਼ৗέʔεΛओମʹ؆୯ͳҎ্έʔεΛ2, 3 ݸ • ॻ͍͍ͯ͘͏ͪʹΘ͔Γʹ͍͘ίʔυɺ૝ఆ ࿙Εͷέʔε͕ग़ͯ͘Δ

    • ௚͢ɺ܁Γฦ͠ Ϣχοτςετ ͪͳΈʹࢲ͸
  115. ίʔυΧόϨοδ

  116. XCJobs::Test.new do |t| t.workspace = 'Ubiregi2' t.scheme = ‘Ubiregi2-Release' t.configuration

    = 'Release' t.build_dir = 'build' t.add_destination('name=iPad 2,OS=7.1') t.add_destination('name=iPad Air,OS=8.1') t.coverage = true t.formatter = 'xcpretty -c' end XCJobs::Coverage::Coveralls.new() do |t| t.service_name = 'travis-pro' t.repo_token = ‘xxx...’ t.add_extension('.m') t.add_exclude('Pods') t.add_exclude('Ubiregi2Tests') t.add_exclude_pattern('.*ViewController\.[hm]') end ίʔυΧόϨοδ XCJobsͰઃఆ͢Δ৔߹
  117. None
  118. None
  119. • ΧόϨοδͷ׆༻͸೉͍͠ • ϓϩμΫτίʔυͷ৔߹͍͍ͨͯ͸ը໘ͷΧ όϨοδ͕଍ΛҾͬுΔ • ద౰ʹؒҾ͘ • ݱঢ়ͷ೺Ѳʹ࢖͏ •

    Ϟνϕʔγϣϯ্͕͕ΔΑ͏ʹίϯτϩʔϧ ͢Δ • ͋·Γؾʹ͗͢͠ͳ͍ ίʔυΧόϨοδ