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
良いテストコードのために悪いテストコードを理解する - 不安定なテスト編: iOSアプリ開発...
Search
yimajo
June 24, 2024
Programming
6.7k
22
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
良いテストコードのために悪いテストコードを理解する - 不安定なテスト編: iOSアプリ開発ユニットテストの場合
「モバイルアプリ開発における良いテストコードの考え方」の発表資料です。
https://trident-qa.connpass.com/event/320151/
yimajo
June 24, 2024
More Decks by yimajo
See All by yimajo
OSSとなったswift-buildで Xcodeのビルドを差し替えられるため 自分でXcodeを直せる時代になっている ダイアモンド問題編
yimajo
3
870
いま中途半端なSwift 6対応をするより、Default ActorやApproachable Concurrencyを有効にしてからでいいんじゃない?
yimajo
2
680
11年かかって やっとVibe Codingに 時代が追いつきましたね
yimajo
1
380
iOSアプリ開発で 関数型プログラミングを実現する The Composable Architectureの紹介
yimajo
3
320
TCAの Shared Stateって どういう仕組みになってんの?
yimajo
0
2.7k
Swift 5.9 からの Observation はiOS17 未満 からも使えて struct の変更検知もできるんすかね?
yimajo
2
1.2k
不安定なテストは200種類あんねん
yimajo
3
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
スマートグラスで並列バイブコーディング
hyshu
0
260
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
14
5.8k
The ROI of Quarkus for Spring Boot Applications
hollycummins
0
140
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
200
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.5k
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
320
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
6.5k
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
290
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
210
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
2
670
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
1
300
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
360
Featured
See All Featured
The Language of Interfaces
destraynor
162
27k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
Code Review Best Practice
trishagee
74
20k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
540
Crafting Experiences
bethany
1
190
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.9k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.3k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.5k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Scaling GitHub
holman
464
140k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
200
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.9k
Transcript
ྑ͍ςετίʔυͷͨΊʹ ѱ͍ςετίʔυΛཧղ͢Δ 2024.6.24 גࣜձࣾΩϡϦΦγςΟιϑτΣΞ @yimajo #ྑ͍ςετίʔυ ෆ҆ఆͳςετฤJ04ΞϓϦ։ൃϢχοτςετͷ߹ 🐲 🤖 ϤϩγΫ
͓ئ͍͠·͢
͔ΜͨΜͳ ࣗݾհ
• ৬ྺ • ৽ଔ: ಋମϝʔΧʔͰLSIΛςετ͢ΔΤϯδχΞ • WebϞόΠϧΞϓϦ։ൃͷιϑτΣΞΤϯδχΞ • Now!: ϓϩαφʔ݉ιϑτΣΞΤϯδχΞ
ຊͷલʹ ࠓճ͢Ϣχοτςετʹ͍ͭͯͷఆٛ ྑ͍ςετͱԿ͔ʹ͍ͭͯ ͳͥςετΛॻ͘ͷ͔
Ҿ༻: Google Testing Blog / Test Size https://testing.googleblog.com/2010/12/test-sizes.html ಛ Small
Medium Large ωοτϫʔΫΞΫηε ͍͍͑ ϩʔΧϧϗετͷΈ ͍ σʔλϕʔε ͍͍͑ ͍ ͍ ϑΝΠϧγεςϜͷΞΫηε ͍͍͑ ͍ ͍ ֎෦γεςϜΛ༻͢Δ ͍͍͑ ਪ͞Εͳ͍ ͍ ෳͷεϨου ͍͍͑ ͍ ͍ Sleep໋ྩ ͍͍͑ ͍ ͍ γεςϜϓϩύςΟ ͍͍͑ ͍ ͍ ੍ݶ࣌ؒ(ඵ) 60 300 900 ఆٛ: Small, Medium, Large Testͷఆٛ 🐲
Ҿ༻: Google Testing Blog / Test Size https://testing.googleblog.com/2010/12/test-sizes.html ಛ Small
Medium Large ωοτϫʔΫΞΫηε ͍͍͑ ϩʔΧϧϗετͷΈ ͍ σʔλϕʔε ͍͍͑ ͍ ͍ ϑΝΠϧγεςϜͷΞΫηε ͍͍͑ ͍ ͍ ֎෦γεςϜΛ༻͢Δ ͍͍͑ ਪ͞Εͳ͍ ͍ ෳͷεϨου ͍͍͑ ͍ ͍ Sleep໋ྩ ͍͍͑ ͍ ͍ γεςϜϓϩύςΟ ͍͍͑ ͍ ͍ ੍ݶ࣌ؒ(ඵ) 60 300 900 ఆٛ: Small, Medium, Large Testͷఆٛ 🐲
Ҿ༻: Google Testing Blog / Test Size https://testing.googleblog.com/2010/12/test-sizes.html ಛ Small
Medium Large ωοτϫʔΫΞΫηε ͍͍͑ ϩʔΧϧϗετͷΈ ͍ σʔλϕʔε ͍͍͑ ͍ ͍ ϑΝΠϧγεςϜͷΞΫηε ͍͍͑ ͍ ͍ ֎෦γεςϜΛ༻͢Δ ͍͍͑ ਪ͞Εͳ͍ ͍ ෳͷεϨου ͍͍͑ ͍ ͍ Sleep໋ྩ ͍͍͑ ͍ ͍ γεςϜϓϩύςΟ ͍͍͑ ͍ ͍ ੍ݶ࣌ؒ(ඵ) 60 300 900 ఆٛ: Small, Medium, Large Testͷఆٛ ʮ͜ͷࢿྉͷ Small/Meidum ʹ͍ͭͯͷςετͷͰ͢ʯ 🐲
ఆٛ: ྑ͍ςετͱʁ ιϑτΣΞͷ࣭Λ ʢ্͛Δͱ͍͏͔ʣམͱ͞ͳ͍ͨΊʹ ։ൃऀͷʹཱͭࣗಈԽ 🐲
ఆٛ: ྑ͍ςετͱʁ ιϑτΣΞͷ࣭Λ ʢ্͛Δͱ͍͏͔ʣམͱ͞ͳ͍ͨΊʹ ։ൃऀͷʹཱͭࣗಈԽ ʮͪΖΜؒతʹΞϓϦΛͬͯ͘ΕΔϢʔβʔͷʹཱͭͱ͍͏͜ͱͰ͢ʯ 🐲
ςετͷ۩ମతͳϝϦοτ Ҿ༻: ॻ੶ʮGoogle ͷιϑτΣΞΤϯδχΞϦϯάʯ • σόοά࣌ؒͷݮগ • มߋͷ৴པͷ૿େ • υΩϡϝϯςʔγϣϯͷվળ
• ϨϏϡʔͷ୯७Խ • ࢥྀʹΉઃܭ • ߴͰߴ࣭ͳϦϦʔε
ςετͷ۩ମతͳϝϦοτ Ҿ༻: ॻ੶ʮ୯ମςετͷߟ͑ํ/͍ํʯ • ςετΛॻ͘ͱઃܭ͕ྑ͘ͳΔ • ʢϓϩμΫτΛʣ࣋ଓՄೳͳͷʹ͢Δ
ςετͷ۩ମతͳϝϦοτ • ςετΛॻ͘ͱઃܭ͕ྑ͘ͳΔ • ʢϓϩμΫτΛʣ࣋ଓՄೳͳͷʹ͢Δ • σόοά࣌ؒͷݮগ • มߋͷ৴པͷ૿େ •
υΩϡϝϯςʔγϣϯͷվળ • ϨϏϡʔͷ୯७Խ • ࢥྀʹΉઃܭ • ߴͰߴ࣭ͳϦϦʔε 🤖
ςετͷ۩ମతͳϝϦοτ • ςετΛॻ͘ͱઃܭ͕ྑ͘ͳΔ • ʢϓϩμΫτΛʣ࣋ଓՄೳͳͷʹ͢Δ • σόοά࣌ؒͷݮগ • มߋͷ৴པͷ૿େ •
υΩϡϝϯςʔγϣϯͷվળ • ϨϏϡʔͷ୯७Խ • ࢥྀʹΉઃܭ • ߴͰߴ࣭ͳϦϦʔε ʮςετΛॻ࣮࣌ؒ͘ߦ࣌ؒͷσϝϦοτແࢹͳͷʁʯ 🤖
ςετ͋Γͳ͠ͷଛӹذ͕͋Δ ࢀߟ: ॻ੶ʮ୯ମςετͷߟ͑ํ/͍ํʯ ʮ࠷ॳςετΛॻ͕͔͔͍࣌ؒͬͯͯ͘ɺ ɹɹ࣍ୈʹͦΕ͕ଛͰͳ͘ʹඞਢʹʯ 🐲
ίετ͔͔Δ͕ςετΛॻ͘ཧ༝͕͋Δ
ͦͦiOSΞϓϦ֎෦࿈ܞ͔Γ ࣗͨͪͷαʔόʔ ·ͨ8FC"1*Λެ։ͯ͘͠ΕͯΔ8FCαʔϏε ·ͨ'JSFCBTFͳͲͷαʔόʔϨεͳͲ "QQMFͷϑϨʔϜϫʔΫ ࣗͨͪͷιʔείʔυ 044ͷϑϨʔϜϫʔΫ Ӭଓσʔλʔ ʢ'JMFͱ͔%#ʣ ֤छ
ηϯαʔ 🤖 🐲
ͦͦiOSΞϓϦ֎෦࿈ܞ͔Γ ࣗͨͪͷαʔόʔ ·ͨ8FC"1*Λެ։ͯ͘͠ΕͯΔ8FCαʔϏε ·ͨ'JSFCBTFͳͲͷαʔόʔϨεͳͲ "QQMFͷϑϨʔϜϫʔΫ ࣗͨͪͷιʔείʔυ 044ͷϑϨʔϜϫʔΫ ʮͤΊͯࣗͷॻ͍ͨίʔυݫີʹৼΔ͏Α͏ʹ͍ͨ͠ʯ Ӭଓσʔλʔ ʢ'JMFͱ͔%#ʣ
֤छ ηϯαʔ 🤖 🐲
ͦͦiOSΞϓϦ֎෦࿈ܞ͔Γ ࣗͨͪͷαʔόʔ ·ͨ8FC"1*Λެ։ͯ͘͠ΕͯΔ8FCαʔϏε ·ͨ'JSFCBTFͳͲͷαʔόʔϨεͳͲ "QQMFͷϑϨʔϜϫʔΫ ࣗͨͪͷιʔείʔυ 044ͷϑϨʔϜϫʔΫ ʮͤΊͯࣗͷॻ͍ͨίʔυݫີʹৼΔ͏Α͏ʹ͍ͨ͠ʯ Ӭଓσʔλʔ ʢ'JMFͱ͔%#ʣ
֤छ ηϯαʔ ʮUIͷૢ࡞͕લఏͳΒࢮ͵΄Ͳखಈςετͨ͠ΒΧόʔͰ͖Δͷʹ…ʯ 🤖 🐲
ͪΖΜ ͲΜͳςετͰॻ͚ྑ͍Θ͚Ͱͳ͘ ѱ͍ςετΛॻ͍͍͚ͯͳ͍
ྑ͍ςετͷରଆʹ͋Δѱ͍ςετ • ཧղͮ͠Β͍ςετίʔυ • ඞཁҎ্ʹ͍ςετίʔυ • յΕ͍͢ςετ • ςετ݁Ռ͕ෆ҆ఆͳςετ 🐲
ྑ͍ςετͷରଆʹ͋Δѱ͍ςετ • ཧղͮ͠Β͍ςετίʔυ • ඞཁҎ্ʹ͍ςετίʔυ • յΕ͍͢ςετ • ςετ݁Ռ͕ෆ҆ఆͳςετ ʮෆ҆ఆͳςετ݁ՌʹͳΔͱ։ൃऀͷʹཱͨͳ͍͕େ͖͍ʯ
🐲
ຊ ෆ҆ఆͳςετͷݪҼΛཧ͠ղܾ͢Δ
ࢲͷʮෆ҆ఆͳςετʯͷఆٛ 100%ޭ 100%ࣦഊ ಉҰͷςετίʔυͰ ޭͨ͠Γࣦഊͨ͠Γ ݁Ռ͕ෆ҆ఆ 🤖 🐲
ࢲͷʮෆ҆ఆͳςετʯͷఆٛ 100%ޭ 100%ࣦഊ ಉҰͷςετίʔυͰ ޭͨ͠Γࣦഊͨ͠Γ ݁Ռ͕ෆ҆ఆ ʮFlakyʢ৴པͰ͖ͳ͍ʣςετͬͯݴ͍͍͑ͷʹ…ʯ 🤖 🐲
ࢲͷʮෆ҆ఆͳςετʯͷఆٛ 100%ޭ 100%ࣦഊ ಉҰͷςετίʔυͰ ޭͨ͠Γࣦഊͨ͠Γ ݁Ռ͕ෆ҆ఆ ʮFlakyʢ৴པͰ͖ͳ͍ʣςετͬͯݴ͍͍͑ͷʹ…ʯ ʮ݁Ռ͕ෆ҆ఆͱ͍͏ํ͕తͰ͖ʯ 🤖 🐲
ࠓճ͢ෆ҆ఆͳςετͷݪҼྨ • ݪҼ • ݪҼ1:࣮ߦॱংʹґଘ͢Δςετ • ݪҼ2: ࣮ߦ࣌·Ͱ͕ܾఆ͠ͳ͍࠶ݱੑͷͳ͍ςετ • ݪҼ3:
ϑϨʔϜϫʔΫཧղෆ • ݪҼ͕ද໘Խ͍͢͠: • CIͳͲͷςετ࣮ߦϚγϯͷϦιʔεෆ Swift 5.10Ͱݕূ
ݪҼ1: ࣮ߦॱংʹґଘ͢Δςετ • ྫ • ςετίʔυؒͰڞ༗ͷ/ࢀরΛ࣋ͬͯ͠·͏
ෆ҆ఆͳݪҼ: ࣮ߦॱংʹґଘ͢Δςετ class Singleton { static let shared = Singleton()
private init() {} var value: Int = 0 var unit: String = "g" } ———————————————————————-———————————————————————- final class SampleTests: XCTestCase { func test_value͕1000Ͱunit͕g_1000gͱදࣔ͞ΕΔ() { Singleton.shared.value = 1000 ɹɹɹɹ // ͜͜Ͱೖ͠Θ͢Ε͍ͯΔ͕ॳظ͕Χόʔͯ͠͠·ͬͯΔ XCTAssertEqual(Singleton.shared.value, 1000) XCTAssertEqual(Singleton.shared.unit, “g") // ԼͷςετΑΓઌʹ࣮ߦ͞ΕΔͱॳظ͕ΘΕςετޭ } func test_value1Ͱunit͕kg_1kgͱදࣔ͞ΕΔ() { Singleton.shared.value = 1 Singleton.shared.unit = "kg" XCTAssertEqual(Singleton.shared.value, 1) XCTAssertEqual(Singleton.shared.unit, "kg") } } 🐲 🤖
ෆ҆ఆͳݪҼ: ࣮ߦॱংʹґଘ͢Δςετ class Singleton { static let shared = Singleton()
private init() {} var value: Int = 0 var unit: String = "g" } ———————————————————————-———————————————————————- final class SampleTests: XCTestCase { func test_value͕1000Ͱunit͕g_1000gͱදࣔ͞ΕΔ() { Singleton.shared.value = 1000 ɹɹɹɹ // ͜͜Ͱೖ͠Θ͢Ε͍ͯΔ͕ॳظ͕Χόʔͯ͠͠·ͬͯΔ XCTAssertEqual(Singleton.shared.value, 1000) XCTAssertEqual(Singleton.shared.unit, “g") // ԼͷςετΑΓઌʹ࣮ߦ͞ΕΔͱॳظ͕ΘΕςετޭ } func test_value1Ͱunit͕kg_1kgͱදࣔ͞ΕΔ() { Singleton.shared.value = 1 Singleton.shared.unit = "kg" XCTAssertEqual(Singleton.shared.value, 1) XCTAssertEqual(Singleton.shared.unit, "kg") } } ʮγϯάϧτϯͳΜͯ Θͳ͚Ε͍͍ͷʹ…ʯ 🐲 🤖
ෆ҆ఆͳݪҼ: ࣮ߦॱংʹґଘ͢Δςετ class Singleton { static let shared = Singleton()
private init() {} var value: Int = 0 var unit: String = "g" } ———————————————————————-———————————————————————- final class SampleTests: XCTestCase { func test_value͕1000Ͱunit͕g_1000gͱදࣔ͞ΕΔ() { Singleton.shared.value = 1000 ɹɹɹɹ // ͜͜Ͱೖ͠Θ͢Ε͍ͯΔ͕ॳظ͕Χόʔͯ͠͠·ͬͯΔ XCTAssertEqual(Singleton.shared.value, 1000) XCTAssertEqual(Singleton.shared.unit, “g") // ԼͷςετΑΓઌʹ࣮ߦ͞ΕΔͱॳظ͕ΘΕςετޭ } func test_value1Ͱunit͕kg_1kgͱදࣔ͞ΕΔ() { Singleton.shared.value = 1 Singleton.shared.unit = "kg" XCTAssertEqual(Singleton.shared.value, 1) XCTAssertEqual(Singleton.shared.unit, "kg") } } ʮγϯάϧτϯͳΜͯ Θͳ͚Ε͍͍ͷʹ…ʯ ʮ͜͏͍͏߹setUp() ΛͬͯॳظԽʯ 🐲 🤖
ෆ҆ఆͳݪҼ: ࣮ߦॱংʹґଘ͢Δςετ 🐲 ʮͪͳΈʹɺXCTestCaseͷϓϩύςΟࣗಈͰॳظԽ͞Ε·͢ʯ 🤖 🐲
ෆ҆ఆͳݪҼ: ࣮ߦॱংʹґଘ͢Δςετ 🐲 ʮͪͳΈʹɺXCTestCaseͷϓϩύςΟࣗಈͰॳظԽ͞Ε·͢ʯ ʮclassͳͷͰ༧͔ͭͳ͍ ͷͰ͓͢͢ΊͰ͖ͳ͍…ʯ 🤖 🐲
ෆ҆ఆͳݪҼ: ࣮ߦॱংʹґଘ͢Δςετ 🐲 ʮͪͳΈʹɺXCTestCaseͷϓϩύςΟࣗಈͰॳظԽ͞Ε·͢ʯ ʮclassͳͷͰ༧͔ͭͳ͍ ͷͰ͓͢͢ΊͰ͖ͳ͍…ʯ 🤖 ʮ࣍ੈͷSwift Testing Ͱ
ͨΓલʹͳΓ·͢Αʯ 🐲
ݪҼ2: ࣮ߦ࣌ʹ͕ܾఆ͢Δಈతͳॲཧͷςετ • ྫ • Date() / Date.init() / Date.now
• ݺͼग़͞Εͨࡍͷ࣌ؒΛऔಘ͢Δίʔυ
class TimeUtils { // ࠓճͷςετର // ݱࡏ࣌ࠁ͔ΒҾͷDateͱͷࠩΛTimeintervalͰฦ͢ϝιου func timeElapsedSince(_ date:
Date) -> TimeInterval { Date().timeIntervalSince(date) } } ———————————————————————-———————————————————————————————————————- final class TimeUtilsTests: XCTestCase { let timeUtils = TimeUtils() func test_TimeElapsedSince() { let date = Date(timeIntervalSinceNow: -6) // 6ඵલͷDate // Action …….. ͜͜Βล͕ΊͪΌͪ͘Ό͔͔࣌ؒͬͨΒఆ͔ΒͣΕΔ let elapsed = timeUtils.timeElapsedSince(date) // Assertion XCTAssertEqual( elapsed, 6, accuracy: 0.1 // ڐ༰͢Δޡࠩൣғ ) } } ෆ҆ఆͳݪҼ: ࣮ߦ࣌ʹ͕ܾఆ͢Δಈతͳॲཧͷςετ 🤖
class TimeUtils { // ࠓճͷςετର // ݱࡏ࣌ࠁ͔ΒҾͷDateͱͷࠩΛTimeintervalͰฦ͢ϝιου func timeElapsedSince(_ date:
Date) -> TimeInterval { Date().timeIntervalSince(date) } } ———————————————————————-———————————————————————————————————————- final class TimeUtilsTests: XCTestCase { let timeUtils = TimeUtils() func test_TimeElapsedSince() { let date = Date(timeIntervalSinceNow: -6) // 6ඵલͷDate // Action …….. ͜͜Βล͕ΊͪΌͪ͘Ό͔͔࣌ؒͬͨΒఆ͔ΒͣΕΔ let elapsed = timeUtils.timeElapsedSince(date) // Assertion XCTAssertEqual( elapsed, 6, accuracy: 0.1 // ڐ༰͢Δޡࠩൣғ ) } } ෆ҆ఆͳݪҼ: ࣮ߦ࣌ʹ͕ܾఆ͢Δಈతͳॲཧͷςετ ʮݱࡏ࣌ࠁʹཔΔͱςετ ɹ͢ΔCIͷϚγϯύϫʔʹ ɹґଘ͢Δͷʹ…ʯ 🤖
class TimeUtils { func timeElapsedSince(date: Date, currentDate: Date = Date())
-> TimeInterval { // ςετ͢Δରͷॲཧ࣮ߦ࣌ʹܾ·Βͳ͍Α͏ʹ͠࠶ݱੑΛߴΊΔ currentDate.timeIntervalSince(date) } } ———————————————————————-———————————————————————-———————————————————————- final class TimeUtilsTests: XCTestCase { let timeUtils = TimeUtils() // ࠓճͷςετର func test_TimeElapsedSince2() { let currentDate = Date(timeIntervalSince1970: 0) // 1970/1/1ͷ0࣌ let date = Date(timeIntervalSince1970: -6) // 1970/1/1ͷ0͔࣌Β6ඵલ // Action let elapsed = timeUtils.timeElapsedSince( date: date, currentDate: currentDate ) // Assertion ɹ XCTAssertEqual( elapsed, 6, accuracy: 0.1 // ڐ༰͢Δޡࠩൣғ ) } } ෆ҆ఆͳݪҼ: ࣮ߦ࣌ʹ͕ܾఆ͢Δಈతͳॲཧͷςετ
ิ: ࣮ߦ࣌ʹಈతʹ͕ܾఆ͢ΔͭΒ • Foundation.Date / Calendar / Locale / UUID
• ͳͲͷཚੜ https://github.com/pointfreeco/swift-dependencies/tree/main/ Sources/Dependencies/DependencyValues ࢀߟ: pointfreeco/swift-dependencies ෆ҆ఆͳݪҼ: ࣮ߦ࣌ʹ͕ܾఆ͢Δಈతͳॲཧͷςετ
ݪҼ3: OSSϑϨʔϜϫʔΫཧղෆ • ྫ • ҙਤͤͣRealm͕εϨουؒΛӽ͑ͯ͠·͏ • ผεϨουͰRealmͷ݁ՌΛrefreshͤͣݕূͯ͠͠·͏
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations جૅ: XCTestͷglobalActorࢦఆ༗ແͷڍಈͷҧ͍ 🤖 🐲 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations ʮMainActorࢦఆ ɹϝΠϯεϨουʯ جૅ: XCTestͷglobalActorࢦఆ༗ແͷڍಈͷҧ͍ 🤖 🐲 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations ʮMainActorࢦఆ ɹϝΠϯεϨουʯ ʮແࢦఆϝΠϯ ɹεϨουͰͳ͍ʯ جૅ: XCTestͷglobalActorࢦఆ༗ແͷڍಈͷҧ͍ 🤖 🐲
🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations ʮMainActorࢦఆ ɹϝΠϯεϨουʯ ʮແࢦఆϝΠϯ ɹεϨουͰͳ͍ʯ جૅ: XCTestͷglobalActorࢦఆ༗ແͷڍಈͷҧ͍ ʮެࣜυΩϡϝϯτʹॻ͔ΕͯΔͷʹ…ʯ 🤖
🐲 🐲
@globalActor actor DBActor { static let shared = DBActor() }
class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) } } ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ جૅ: globalActorࢦఆແ͠ͰActorݺͼग़͢ͱ࣮ߦ εϨουঢ়گʹࠨӈ͞ΕΔʢεϨουϓʔϧ͔ΒબΕΔʣ 🐲
@globalActor actor DBActor { static let shared = DBActor() }
class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) } } ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ جૅ: globalActorࢦఆແ͠ͰActorݺͼग़͢ͱ࣮ߦ εϨουঢ়گʹࠨӈ͞ΕΔʢεϨουϓʔϧ͔ΒબΕΔʣ 🐲
@globalActor actor DBActor { static let shared = DBActor() }
class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) } } ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ εϨουA,B,CͷՄೳੑ A == B, B== C || A == B, B!= C || A != B, B== C || A == C, C !== B || جૅ: globalActorࢦఆແ͠ͰActorݺͼग़͢ͱ࣮ߦ εϨουঢ়گʹࠨӈ͞ΕΔʢεϨουϓʔϧ͔ΒબΕΔʣ 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ ྫ: ҙਤͤͣRealm͕εϨουؒΛӽ͑ͯ͠·͏ 🐲 @globalActor actor DBActor { static let
shared = DBActor() } class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let otherRealm = RLMRealm.default() let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) XCTAssertNotNil( RealmObject.object(in: otherRealm, forPrimaryKey: "1") ) } } 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ ྫ: ҙਤͤͣRealm͕εϨουؒΛӽ͑ͯ͠·͏ 🐲 @globalActor actor DBActor { static let
shared = DBActor() } class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let otherRealm = RLMRealm.default() let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) XCTAssertNotNil( RealmObject.object(in: otherRealm, forPrimaryKey: "1") ) } } 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ ྫ: ҙਤͤͣRealm͕εϨουؒΛӽ͑ͯ͠·͏ 🐲 @globalActor actor DBActor { static let
shared = DBActor() } class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let otherRealm = RLMRealm.default() let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) XCTAssertNotNil( RealmObject.object(in: otherRealm, forPrimaryKey: "1") ) } } 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ ྫ: ҙਤͤͣRealm͕εϨουؒΛӽ͑ͯ͠·͏ 🐲 @globalActor actor DBActor { static let
shared = DBActor() } class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let otherRealm = RLMRealm.default() let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) XCTAssertNotNil( RealmObject.object(in: otherRealm, forPrimaryKey: "1") ) } } 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ εϨουA==Cͷ߹ RealmಉҰεϨουͰར༻ ✅ ޭ ྫ: ҙਤͤͣRealm͕εϨουؒΛӽ͑ͯ͠·͏ 🐲 @globalActor actor
DBActor { static let shared = DBActor() } class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let otherRealm = RLMRealm.default() let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) XCTAssertNotNil( RealmObject.object(in: otherRealm, forPrimaryKey: "1") ) } } 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ εϨουA==Cͷ߹ RealmಉҰεϨουͰར༻ ✅ ޭ ྫ: ҙਤͤͣRealm͕εϨουؒΛӽ͑ͯ͠·͏ 🐲 @globalActor actor
DBActor { static let shared = DBActor() } class ThreadTests2: XCTestCase { // MainActorʢglobalActorࢦఆʣແ͠ func test_MainActorͰͳ͘async() async { print("A:", Thread.current) let otherRealm = RLMRealm.default() let task = Task { @DBActor in try! await Task.sleep(nanoseconds: 1_000_000_000 * 5) print("B:", Thread.current) } await task.value print("C:", Thread.current) XCTAssertNotNil( RealmObject.object(in: otherRealm, forPrimaryKey: "1") ) } } εϨουA != Cͷ߹ Realm͕ผεϨουͰར༻͞ΕΔ 🚫 ࣦഊ 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ ྫ: ผεϨουͷRealmͷॻ͖ࠐΈΛ͚ͭͩͰ ·ͩಉظ͍ྃͯ͠ͳ͍ ಉظλΠϛϯά खಈSFGSFTI ·ͨ࣌ؒܦա ·ͨ BVUPSFGSFTIʢϝΠϯεϨουͷσϑΥϧτʣ ·ͨ
͓ͦΒͦ͘ͷଞ͍Ζ͍ΖͳλΠϛϯά ͦΕͱผ εϨουͷ Realm ผεϨουͷ Realm 🐲
ෆ҆ఆͳݪҼ:OSSϑϨʔϜϫʔΫཧղෆ ྫ: ผεϨουͷRealmͷॻ͖ࠐΈΛ͚ͭͩͰ ·ͩಉظ͍ྃͯ͠ͳ͍ ಉظλΠϛϯά खಈSFGSFTI ·ͨ࣌ؒܦա ·ͨ BVUPSFGSFTIʢϝΠϯεϨουͷσϑΥϧτʣ ·ͨ
͓ͦΒͦ͘ͷଞ͍Ζ͍ΖͳλΠϛϯά ͦΕͱผ εϨουͷ Realm ผεϨουͷ Realm Realmͷॻ͖ࠐΈΛݕূ͢Δ݁Ռ͕ ෆ҆ఆͳͱ͖ ಈ࡞͍ͯ͠ΔεϨουΛௐࠪ͠ɺ RealmͷυΩϡϝϯτͰ realm.refresh()ΛௐͯΈ͍ͨɻ 🐲
·ͱΊ
ෆ҆ఆͳ݁Ռͷ࣌ʹؾ͕͖͍ͭͨ͜ͱ • ςετέʔεؒͰ/ࢀরΛ࠶ར༻ͯ͠ͳ͍͔ • ಈతʹܾ·ΔΛϩδοΫςετίʔυʹຒΊࠐΜͰ͍ͳ͍͔ • ࣮ߦεϨου͕ҙਤͤͣมΘͬͯ͠·͍ͬͯͳ͍͔
͓ΘΓʹ
ͦͷ΄͔ͷෆ҆ఆͷݪҼͱղܾͷ • ࠓճઆ໌লུʢผͷࢿྉ: ෆ҆ఆͳςετ200छྨ͋ΜͶΜʣ • https://speakerdeck.com/yimajo/bu-an-ding-natesutoha200zhong- lei-annen
͜ͷൃදͰ ؒҧ͍͕͋ͬͨΓ͝ҙݟ͋Εɺ ͳΔ͘ Θ͔Γ͘͢ࢦఠ࣭͚ͯ͠Δͱ ༗ҙٛͳΓͱΓ͕Ͱ͖ΔͣͰ͢ɻ ʢࢲͷࢥ͍ࠐΈͰղܾͨͭ͠Γʹͳ͍ͬͯΔ͜ͱ͋Γ·͢ʣ
͓ΘΓ 🐲 🤖 ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠