Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
不安定なテストは200種類あんねん
Search
yimajo
October 27, 2023
Programming
1.2k
3
Share
不安定なテストは200種類あんねん
#ios_test_night
https://testnight.connpass.com/event/295913/
yimajo
October 27, 2023
More Decks by yimajo
See All by yimajo
OSSとなったswift-buildで Xcodeのビルドを差し替えられるため 自分でXcodeを直せる時代になっている ダイアモンド問題編
yimajo
3
860
いま中途半端なSwift 6対応をするより、Default ActorやApproachable Concurrencyを有効にしてからでいいんじゃない?
yimajo
2
680
11年かかって やっとVibe Codingに 時代が追いつきましたね
yimajo
1
380
iOSアプリ開発で 関数型プログラミングを実現する The Composable Architectureの紹介
yimajo
3
320
良いテストコードのために悪いテストコードを理解する - 不安定なテスト編: iOSアプリ開発ユニットテストの場合
yimajo
22
6.7k
TCAの Shared Stateって どういう仕組みになってんの?
yimajo
0
2.6k
Swift 5.9 からの Observation はiOS17 未満 からも使えて struct の変更検知もできるんすかね?
yimajo
2
1.2k
TCA v0.19.0からのSwitchStore/CaseLetが良い
yimajo
0
2k
TCAでViewStoreにKeyPath DynamicMemberLookupが使われてる件
yimajo
0
1.3k
Other Decks in Programming
See All in Programming
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
450
SPMマルチモジュールで テストカバレッジを取得する技法
yosshi4486
0
140
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.5k
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
3
1.2k
Modding RubyKaigi for Myself
yui_knk
0
860
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.1k
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
13
3.3k
CLIであることを活かしたGitHub Copilot CLI活用術 / GitHub Copilot CLI Pro Tips & Tricks
nao_mk2
1
1.2k
Migrations : C'est une question d'hygiène !
vinceamstoutz
0
2.9k
Oxcを導入して開発体験が向上した話
yug1224
4
280
New "Type" system on PicoRuby
pocke
1
430
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
2
220
Featured
See All Featured
Google's AI Overviews - The New Search
badams
0
1k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
470
Believing is Seeing
oripsolob
1
140
Code Review Best Practice
trishagee
74
20k
Optimizing for Happiness
mojombo
378
71k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
570
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
190
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
The browser strikes back
jonoalderson
0
1.1k
How STYLIGHT went responsive
nonsquared
100
6.2k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
190
Transcript
ෆ҆ఆͳςετछྨ͋ΜͶΜ #ios_test_night 2023.10 y.imajo
ࣗݾհ
ࣗݾհ • ৬ྺ • ৽ଔ: ಋମϝʔΧʔͰςετΤϯδχΞ • WebϞόΠϧΞϓϦ։ൃͷιϑτΣΞΤϯδχΞ • Now!:
ϓϩαφʔ݉ιϑτΣΞΤϯδχΞ
ຊ ෆ҆ఆͳςετͷݪҼΛཧ ͠ղܾ͢ΔҊ
ࠓճࢲ͕͢ʮෆ҆ఆͳςετʯͷఆٛ • ςετίʔυʹΑΓςετࣗಈԽ͞Εɺͦͷ݁Ռ͕ޭͨ͠Γࣦഊ͠ ͨΓͱෆ҆ఆͳ୯ମςετͷ͜ͱ • ඞͣޭ͢Δ୯ମςετෆ҆ఆͰͳ͍ • ඞࣦͣഊ͢Δ୯ମςετෆ҆ఆͰͳ͍
ࢲͷʮෆ҆ఆͳςετʯͷఆٛ ޭ ࣦഊ ෆ҆ఆͳςετ ʢݪҼ͍͍ͩͨݟΔਓʹΑͬͯʣ200छྨ͋ΜͶΜ
ݪҼͷྨ • େྨ1: ςετίʔυ·ͨςετରͷϓϩμΫγϣϯίʔυ͕ѱ͍ • ݪҼ1: ࣮ߦ࣌ʹܾఆ͢ΔಈతͳʹΑΔςετ࣮ߦ • ݪҼ2: OSSϑϨʔϜϫʔΫཧղෆ
• ݪҼ3: ςετέʔεͷ࣮ߦॱংґଘ • ݪҼ4: ϓϩάϥϛϯάݴޠ/SDKͷཧղෆ • ݪҼ ͦͷଞ • େྨ2: ςετ࣮ߦϚγϯཁҼʢڥʣ ࠓ͢͜ͱ
ݪҼ1: ࣮ߦ࣌ʹܾఆ͢ΔಈతͳʹΑΔςετ࣮ߦ • ྫ • Foundation.Date.init() / Foundation.Date.now • ݺͼग़͞Εͨࡍͷ࣌ؒΛऔಘ͢Δίʔυ
ྫ: Foundation.Date.init() / Foundation.Date.now • ͳͥෆ҆ఆ͞ʹͭͳ͕Δͷ͔ • ݻఆͰͳ͍ͨΊ • ݱࡏ࣌ࠁͷ༻ҙ͔Βར༻͢Δ·Ͱఆ֎ʹ͕࣌ؒܦա͢Δ
• ͲͷΑ͏ͳ߹ʹࠔΔͷ͔ • ςετରͷϝιουͰNඵҎͳͲͷॲཧ͕͋Δ • ςετίʔυࣗମͰͷൺֱʹ͏ͷ͔͍ͬ • Ͳ͏ղܾ͢Δͷ͔ • Date.init()/Date.nowʹΑΔݱࡏ࣌ࠁऔಘ෭࡞༻࣮ߦͱߟ͑ɺDIͨ͠ͷΛར༻͢Δ • Ͱ͖Δ͚ͩݻఆʢfixtureʣΛ͏ ෆ҆ఆͳݪҼ: ࣮ߦ࣌ʹܾఆ͢ΔಈతͳʹΑΔςετ࣮ߦ
ิ: ࣮ߦ࣌ʹಈతʹ͕ܾఆ͢ΔͭΒ • Foundation.Date / Calendar / Locale / UUID
• Swift Standard LibraryͷContinuousClock • ͳͲͷཚੜ ෆ҆ఆͳݪҼ: ࣮ߦ࣌ʹܾఆ͢ΔಈతͳʹΑΔςετ࣮ߦ https://github.com/pointfreeco/swift-dependencies/tree/main/ Sources/Dependencies/DependencyValues ࢀߟ: pointfreeco/swift-dependencies
ݪҼ2: OSSϑϨʔϜϫʔΫཧղෆ • ྫ • QuickͰDate()ΛbeforeEachΘͣݺͼग़ͯ͠͠·͏ • RxSwiftͰεέδϡʔϥΛDIͤͣʹςετͯ͠͠·͏ • OHHTTPStubs
/ AutoMockable • ࠓճআ֎
ྫ: QuickͰDate()ΛbeforeEachΘͣݺͼग़ͯ͠͠·͏ • ͳͥෆ҆ఆ͞ʹͭͳ͕Δͷ͔ • Quickςετ࣮ߦͷͨΊʹ·ͣ༻ҙͯ͋͠ΔQuickSpecશ͕ͯॳ ظԽ͞ΕɺςετରΛूΊͨ͋ͱʹςετΛॱ࣮࣍ߦ͢Δ • ݱࡏ࣌ࠁͷ༻ҙ͔Βར༻͢Δ·Ͱఆ֎ʹ͕࣌ؒܦա͢Δ •
Ͳ͏ղܾ͢Δͷ͔ • beforeEach͏ ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ
class SpecA: QuickSpec { override class func spec() { describe("A")
{ print("A.describe") let date = Date() // beforeEachΛΘͳ͍ྫ it("actionϝιου1ඵҎʹ࣮ߦ͕ྃ͢Δ") { print("A.it") let subject = ... subject.action() expect(date - Date()) < 1.0 // ྫ } } } } class SpecB: QuickSpec { override class func spec() { describe(“B") { print("B.describe") it(“…”) { print("B.it") } } } } A.describe B.describe A.it B.it it࠷ޙʹ࣮ߦ͞ΕΔ • SpecAͷspecϝιου͕࣮ߦ͞ΕΔ • SpecAͷdescribe͕࣮ߦ͞ΕΔ • SpecBͷspecϝιου͕࣮ߦ͞ΕΔ • SpecBͷdescribe͕࣮ߦ͞ΕΔ • SpecAͷit͕࣮ߦ͞ΕΔ • SpecBͷit͕࣮ߦ͞ΕΔ print ݁Ռ
ͳΜͰ͜Μͳ͜ͱʹ…?
XcodeͰͷςετ࣮ߦ (xcodebuild test ܥ) ϑΣʔζͱͯ͠ ςετͷ ొ ͱ ࣮ߦ
ͷ2ϑΣʔζʹ ͔Ε͍ͯͯ Quick ͦΕΛར༻͍ͯ͠Δɻ ςετొϑΣʔζ ςετ࣮ߦϑΣʔζ XCTestCaseͷdefaultTestSuite XCTestCaseͷtestInvocations ิBQQMFTXJGUUFTUJOHͰݱঢ়ςετొͯ͠ΔΓํͱҧ͏
XCTest Quick ϑΣʔζ1. XcodeXCTestCaseΛܧঝͨ͠Πϯελϯε͔ΒdefaultTestSuiteΛ࣮ߦ͢Δ ϑΣʔζ1_1. defaultTestSuite͔ΒgatherExamplesNeeded()͕ݺͼग़͞ΕΔ ϑΣʔζ1_2. gatherExamplesNeeded()spec()Λ࣮ߦ ϑΣʔζ1_3. spec()ʹ͋ΔbeforeEach()/it()ΛWorldʹappend͍ͯ͘͠
ϑΣʔζ2. XcodeXCTestCaseܧঝ͍ͯ͠ΔΠϯελϯε͔ΒtestInvocationsΛ࣮ߦ͢Δ ϑΣʔζ2_1.testInvocationsWorldʹappend͞ΕͯΔͷ͔ΒऔΓग़͠itΛςετͱ࣮ͯ͠ߦ beforeEach/it४උϑΣʔζͰอ࣋͞Ε ࣮ߦϑΣʔζͰ࣮ߦ͞ΕΔ
͓·͚: ৗʹࣦഊ͢Δྫ Presenter͕ղ์͞Ε݁Ռࣦഊɻ itΫϩʔδϟ͕࣮ߦ͞ΕΔ࣌ ϑΣʔζ2Ͱ͋ΓɺϑΣʔζ1Ͱ࡞ ͞ΕͨPresenterࢀর͞Εͳ͍ͷ Ͱʢେʣղ์͞ΕΔ͜ͱͰdeinit ͕࣮ߦ͞Ε݁Ռ͕มΘΔɻ class SpecC:
QuickSpec { override class func spec() { describe(“Presenter.viewDidLoad࣮ߦ”) { // ·ͨbeforeEach͠ͳ͍ let spy = View() let presenter = Presenter() // SUT presenter.view = spy // Action४උϑΣʔζͰͬͯ͠·͏ presenter.viewDidLoad() ɹɹ it("Viewͷ͕ViewDidLoadͰtrueʹͳͬͯΔ") { expect(spy.flag).to(beTrue()) // ݁Ռʁ } } } } class Presenter { var view: View? deinit { view?.flag = false } func viewDidLoad() { view?.flag = true } }
ྫ: RxSwiftͰεέδϡʔϥΛDIͤͣςετͯ͠͠·͏ • ͳͥෆ҆ఆ͞ʹͭͳ͕Δͷ͔ • MainSchedulerಉظతʹΛྲྀ͢ͱ͖͋Δ͠ඇಉظతʹΛྲྀ ͢͜ͱ͋Δ • Ͳ͏ղܾ͢Δͷ͔ •
εέδϡʔϥΛDI͠ςετεέδϡʔϥΛ͏ • RxSwiftͷίʔυΛಡΉ͔RxSwiftݚڀಡຊΛങ͏ ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ
ݪҼ3: ςετέʔεͷ࣮ߦॱংґଘ • ྫ • ςετରΛෳςετέʔεͰ͍ճ͠ϓϩύςΟΛηοτ
ྫ: ςετରΛෳςετέʔεͰ͍ճ͠ϓϩύςΟΛηοτ • ͳͥෆ҆ఆ͞ʹͭͳ͕Δͷ͔ • ςετରTest Doubleʹ͏ΦϒδΣΫτΛෳςετͰ͍ ճͯ͠͠·͏ͱͦͷϓϩύςΟΛηοτ͠ΕͨΓϦηοτ͠Ε Δ •
Ͳ͏ղܾ͢Δͷ͔ • ෳςετέʔεͰ͍·Θ͞ͳ͍ ෆ҆ఆͳݪҼ:ςετέʔεͷ࣮ߦॱংґଘ
class SpecD: QuickSpec { override class func spec() { describe(“ະఆ͢Δ")
{ var subject = … var user = TestDouble.User() context(“ॴ͕ຊͰ10ࡀ”) { beforeEach { user.age = 10 subject.user = user } it("ະఆ͞ΕΔ") { subject.action() expect(subject.ະ).to(beTruth)) } } context(“ॴҟੈքͰ10ࡀ”) { beforeEach { user.age = 10 subject.user = user subject.location = .ҟੈք } it(“ະఆ͞Εͳ͍") { subject.action() expect(subject.ະ).to(beFalse)) } } ྫ: ্ͷຊcontextͰlocationΛ ઃఆ͍ͯ͠ͳ͍͕σϑΥϧτͷ locationͰઌʹςετޭ͠ɺ࣍ ʹԼͷҟੈքcontextͰ໌ࣔͯ͠ ςετޭ͍ͯ͠Δͱ͢Δɻ ઌʹԼͷҟੈքcontext͕࣮ߦ͞Ε Δͱ…?
Quick ͕ѱ͍Θ͚͡Όͳ͘ɺςετରӨڹ Λٴ΅͢ΦϒδΣΫτ͕ςετ֎͔ΒӨڹΛड ͚Δ͜ͱΛՄೳʹͯ͠͠·͍ͬͯΕɺςετ ݁Ռҙਤͤͣෆ҆ఆʹͳΓ͍͢ɻ
ͨͩͪΐͬͱ͜Εʹؔͯ͠QuickΛͬͯͳ ͍ͱ͍͚ͳ͍͜ͱ͕ଟ͍͔ͳͱࢥ͏
ઌड़ͷ௨Γɺ QuickͰbeforeEach४උϑΣʔζͰͳ࣮͘ߦϑΣʔζͰ͋ ΓɺมͷॳظԽΒ࠶ઃఆʢArrangeʣͰ࣮֬ʹΘ͍ͤͨΜ ͚ͩͲɺbeforeEachΫϩʔδϟͳͷͰArrangeͷνΣοΫΛ͢ ΔΑ͏ͳػೳͳ͍ɻ
ܕͷػೳΛར༻ͯ͠ܕ҆৺ͳςετͷArrange͕Ͱ͖Εͬͱ ྑ͍ͱࢥ͑Δ…ɻ XCTestCaseࣗମͰؤுΕͰ͖Δɻ apple/swift-testingͬͨΒ͞Βʹදݱྗߴ͘Ͱ͖ͦ͏…ɻ
ݪҼ4: ϓϩάϥϛϯάݴޠ/SDKͷཧղෆ • ྫ • ϝΠϯλʔήοτͰͷςετϗετΞϓϦέʔγϣϯ͕ىಈͯ͠ ͠·͏ • XCTestCase.waitϝιουʹΑΓεϨου͕σουϩοΫ͢Δͱ͖ ͕͋Δ
ྫ: ϝΠϯλʔήοτͰͷςετϗετΞϓϦέʔγϣϯ͕ىಈͯ͠͠·͏ • ͳͥෆ҆ఆ͞ʹͭͳ͕Δͷ͔ • ઃఆͱͯ͠ɺςετϞδϡʔϧΛϗετΞϓϦຊମͱࢦఆ͢Δͱςετ࣌ʹΞϓϦ͕ ىಈ͠ɺͦ͜Ͱ෭࡞༻Λ࣮ߦ͠ςετʹӨڹΛ༩͑Δ͜ͱ͕͋Δ • Ͳ͏ղܾ͢Δͷ͔ •
AppDelegateΛMockͯ͠Կ͠ͳ͍ʢor Mockͤͣݕ͠ذͯ͠Կ͠ͳ͍ʣ • ϗετΞϓϦͷϞδϡʔϧSwiftUI.AppAppDelegateͷΈ࣋ͪɺͦΕҎ֎ͷॲཧ Ϟδϡʔϧͱׂ͠ɺͦΕʹରͯ͠ςετλʔήοτϞδϡʔϧΛ࡞Δ • XcodeઃఆͰHost Application = Noneʹͯ͠ςετ࣮ߦͰ͖Ε͍͍ ෆ҆ఆͳݪҼ:ϓϩάϥϛϯάݴޠ/SDKͷཧղෆ
ϝΠϯλʔήοτ ϝΠϯλʔήοτͷ ςετλʔήοτ )PTU"QQMJDBUJPOϝΠϯλʔήοτ ςετ࣮ߦͰ)PTU"QQMJDBUJPO͋ΔΜͰ ΞϓϦέʔγϣϯ͕ىಈ࣮ߦ͢Δɻ JNQPSU 1BDLBHFͷ ςετλʔήοτ λʔήοτ
JNQPSU 1BDLBHFTXJGU JNQPSU YDXPSLTQBDF ิYDPEFCVJMEͰͷςετͰ͋ΓTXJGUUFTU࣮ߦͰͷςετʹ͍ͭͯͷͰ͋Γ·ͤΜ &NCFEEFE'SBNFXPSL λʔήοτ &NCFEEFE.PEVMFͷ ςετλʔήοτ )PTU"QQMJDBUJPO/POF ςετ࣮ߦͰ)PTU"QQMJDBUJPO͕ͳ͍ͳΒɺ ΞϓϦέʔγϣϯىಈ࣮ߦ͠ͳ͍ɻ JNQPSU
ྫ: XCTestCase.waitϝιουʹΑΓεϨου͕σουϩοΫ͢Δͱ͖͕͋Δ • ͳͥෆ҆ఆ͞ʹͭͳ͕Δͷ͔ • wait Ͱ Task { @MainActor
in } ͷ݁ՌΛͭςετͳ߹ɺwaitϏδʔΣΠτ͞ ͤͯϝΠϯεϨουΛࢭΊ͍ͯΔͨΊɺTaskͷϝΠϯεϨου࣮ߦΛ્͢Δͪ ͔ͨͱͳΓσουϩοΫ • Ͳ͏ղܾ͢Δͷ͔ • ͦͦXcode 14.3ܥͰܯࠂ͞ΕΔ • Swift ConcurrencyΛ͏߹ fulfillment(of:timeout:…) ϝιου͕༻ҙ͞Ε͍ͯΔ ෆ҆ఆͳݪҼ:ϓϩάϥϛϯάݴޠ/SDKͷཧղෆ
XCTest.framwork ͷίʔυGitHubͰެ։͞Ε͍ͯΔͷͰಡΊΘ͔Δͣ
͓ΘΓʹ
͜ͷൃදͰ ؒҧ͍͕͋ͬͨΓ ҙݟ͕͋ͬͨΒͰ͖Δ͚ͩͳΔ͘ Θ͔Γ͘͢ࢦఠ࣭ͯ͠Β͑Δͱ ༗ҙٛͳΓͱΓ͕Ͱ͖ΔͣͰ͢