初めてのJetpack Compose UIテスト実践〜UI要素テストからページ遷移のテストまで〜
そもそも、なぜ、テストをしたいか(個人開発のモチベーション)
簡単なアプリであれば、テストも何も考えずに出来るかと思いますが、ユーザーとのインタラクションが多いアプリだと、どうしてもテストを書く必要性が出てきます。特にAndroidは多くの端末を対象とするため、画面の大きさやOSの種類によって正しくUIが表示されるかは確認しなくてはいけません。
そして、何より、個人開発では基本的に一人で実装をしテストをしてリリースをします。人的リソースが最小なのに、大きな改修をする必要が途中で出て来たときの絶望感と言ったら、半端ではないです。そこで、先人たちは「テストなるものをすれば、完全ではないけどリスクを抑えられるよ」と言っているため、テストを導入したいと思います。
私の個人的なモチベーションとして、なぜ、テストをしたいか、それは品質の担保ということも大きいですが、それ以上に「後戻りを出来る限り減らし、効率よくアプリを開発するため」です(品質の議論をする前に、挫折しないためです)。品質の担保という側面では、後に紹介する資料でも多く取り上げられているので、そちらを参考にしてください。
開発途中に行うテストは大きく単体テスト、結合テスト、UIテストに分けられると思います[1]。今回はこの中でも、UIテストに焦点を当てています(Unitテスト、結合テストは別記事にします)。しかし、単体テストについてはテストパターンの作り方などが紹介されている[2]一方で、UIテストについては具体的なテストパターンなど(所謂How to)が紹介されている記事が見つけられませんでした。そこで、後述しますが本記事は我流で考えてHow toを作ってしまおうという記事です。
この記事の目的
現在、開発しているアプリのUIテストを行おうと思い、基本的なツールの使い方は勉強しました[3]。ところが、どの開発段階でUIテストを導入して、そのときに、具体的に何の項目を検証するべきかは探したがなかなか記事が見つかりません。
例えば小井土亨さんのスライド[4]や園さん(サイボウズ)のスライド[1:1]、岡住和樹さん(mixi GROUP)のスライド[5]が見つかりました。どれも、「なぜテストをするのか」や「ブラウザから入力してエラーが出ることを確認」などざっくりとした概念にしか触れられていないです(一番近いのは小井土さんのスライド[2:1]pp.33〜)。今回、この記事で明らかにしたいのは、具体的に、どの段階で何をテストするのか、テスト項目またはパターンをどうやって設定するのかということです(つまり、UIテストのHow toということです)。
先に紹介した過去の記事ではUIテストは最後の最後で統合して行うというイメージなのですが、今回のUIテストでは統合前のViewの要素テストをします。この「要素」というのは「Composeレイアウトのテスト」[6]の中で、「単一のコンポーザブルから画面全体まで、あらゆるものを指します。」と示されているように、入力ボックス一つからアプリに描画される1ページまでのことを指します。今回は最後の最後で操作を検証するという意味合いではなく、ModelやRepositoryを実装する前に「メールアドレスか判定して表示する」といったロジックが組まれる入力ボックスなどの要素に対しても、単体でテストしようという姿勢です。つまり、今回のUIテストの流れは以下の図のようになります。
加えて、これまでに挙げた過去の資料とは違うところは、先の章でも触れたように、あくまで「個人開発」のためのテストとなっています。
裏の「この記事の目的」
テストの必要性は、かなり前から知ってはいたのですが、面倒でなかなか腰を上げられませんでした。しかし、今開発しているアプリが、設計と実装の間の不整合で作り直しになり(もう、数カ所を直せば動くようなミスではなかったのです)、テストを入れたほうが良いかもというのがテストを導入したきっかけです。そして、テストを導入するのは良いものの「設計」とか「テストパターンの書き出し」とか、意外と一人では「一人だし、文字や図にしなくてもいいでしょ」とサボりたがることが多いのです(もちろん、サボるとテストの意味がないですね)。そこで、今回、この記事を書きながら実装することで、ちゃんと、最低限のテストをしようという自分のサボり抑制が本来の目的だったりします。
開発にUIテストを取り入れるフェーズ
私が個人的に開発をするときに、ざっくりと以下のフェーズを踏んで開発しています。
- 作りたいアプリを妄想する
- 必要な機能をざっくり書き出す
- 出来そうかどうかを試す(モックを作ったり、使ったことのない技術やAPIを試しに使ってみる)
- 3で「出来そう」となれば設計をする
- 機能やUIを実装する
- 動かしてみる、使ってみる
- 4->5->6を繰り返す
- 全体的に実際に使ってみる
- リリースする
今まで、テストを入れたことがなかったのですが、色々と読んでいると4から5のところでテストを入れたほうが良さそうです。また、今回はModelとViewを別々に実装していくため、以下のような流れで開発を進めます。イメージとしては、描画する箱(UI/View)を先に用意しておいて、後でデータを流し込めるようなModelを作るというイメージです。
- 作りたいアプリを妄想する
- 必要な機能をざっくり書き出す
- 出来そうかどうかを試す(モックを作ったり、使ったことのない技術やAPIを試しに使ってみる)
- 3で「出来そう」となればポンチ絵を描く
- UI設計をする
- UIテストを設計して実装する
- UIを実装する
- UIテストをする(使ってみる、動かしてみる)
- 6->7->8を繰り返す
- Modelを設計する
- Unitテストを設計して実装する
- Modelを実装する
- Unitテストをする
- 11->12->13を繰り返す(使ってみる、動かしてみる)
- UIとModelを統合する
- 統合テストをする(使ってみる、動かしてみる)
- システムテストをする(使ってみる、動かしてみる)
- リリースする
この記事は5から9の部分について書きます。
現実は上のようにきれいに出来ず、どこかで戻る部分や改修する部分がありますが、記事では省きます。また、出来る限り広く使われている(だろう)技法で図を書きますが、あくまで個人開発のレベルですのでご承知おきください。
UI設計
ワイヤーフレームを作成
私はUI設計をするに当たって、まずはじめにワイヤーフレームを作ります。実装していけば、レイアウトやデザインは変わっていきますが、表示項目、画面遷移などは大きく変わりません。これらをイメージするために、ワイヤーフレームを作ります。私はワイヤーフレームを作るのに、かれこれ7年ほどNulabが提供しているCacooを愛用しています。
図1: ワイヤーフレーム
各ページで表示項目(出力)と入力項目(入力)を書き出す
ワイヤーフレームをもとに、各ページの表示項目とユーザーの入力項目を書き出します。
ここでは、長くなるので一部しか記載しません。ハイライトしているのは、殆ど全てのページで使われるテンプレート的なものです。
図2: 各ページの入出力項目
Viewを構成する各要素と、その入出力を書き出す
今回はJetpack Composeを使います。基本的に実装はJetpackのアプリ アーキテクチャガイド[7]に則り、奥澤さんの本[8]を参考に実装します。Viewを構成する各要素を最小単位(テスト可能なぐらい小さく)分けて、具体的な入出力を書き出します。後々、ViewModelとの関係を付け加えていきますが、後々の話です。とりあえず、View上の動作だけ確認します。つまり、ちゃんとView関数に引数を入れたときに表示されるか、入力されたデータに応じた表示がされるかなどをテストするということです。後々、ViewModelやRepositoryなどの依存関係を入れたときにも、このUIテストを使って、正常に動けばOKのはずです。
先の説明では入力ボックスと書いていて恐縮なのですが、紹介するコードの長さの関係上、今回はMenuBarを例に作ります。
以下の図に示すのが各要素の入出力関係です。左からはユーザーからの入力、右からはViewより下層のモジュールからの入力を示します。図は完全に我流なので、見づらくてすみません(正規の書き方があれば、ぜひコメントで教えていただければ幸いです)。
図3: メニューバーの各要素(各Composable Function)の入出力
最小要素のUI Testを書く(テキストボックスなど最小単位)
では、UIテストを書いていきます。まずはじめに、メニューバー1つの要素テストを書きます。一般的にはUIテストは最後の最後で行うようですが[9]、Viewに上手く表示されなかったときに、どこで躓いているのか分かりやすいように、繰り返しになりますが各Viewを構成する最小単位(最小要素)、それをまとめ表示する各ページ単位(中規模要素テスト)、ページ遷移テストでテストできるようにします。
まず、最小要素のUIテストする項目について書きます。
表1: メニューバーのUIテスト項目
Function | 入力 | 出力 | 説明 |
---|---|---|---|
TemplateView | なし | タイトル(INBOX) | 初期の読み込み表示 |
TemplateView | メニューボタンクリック | ユーザ名(Akira)とメールアドレス(akira@test.cat) | メニューボタンを押した時にメニューバーが展開されるか |
ここでは、TemplateView(メニューバーのこと)の実装コードとテストコードを貼り付けます。 | |||
分かりやすいように実装コードとテストコードを併記していますが、本来はテストコードを書いた後に実装コードを書くようです(私も一部例外を除いて、そうしています)。 |
実装コード
package ...(略)
import ...(略)
@Preview
@Composable
fun TemplateView(
@PreviewParameter(ParameterTemplatePreviewParameterProvider::class) paraView: ParameterTemplateView
) {
val scope = rememberCoroutineScope()
val drawerState = rememberScaffoldState()
Scaffold(
scaffoldState = drawerState,
topBar = {
TopAppBar(
modifier = Modifier.semantics { contentDescription = "AppBar" },
title = { Text(text = paraView.boxName) },
navigationIcon = {
IconButton(onClick = {
scope.launch {
drawerState.drawerState.open()
}
}) {
Icon(Icons.Filled.Menu, contentDescription = "Menu button")
}
}
)
},
drawerContent = {
Text(
text = paraView.userName,
fontSize = 20.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = paraView.userEmail,
fontSize = 15.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
) {
}
}
data class ParameterTemplateView(
val boxName: String,
val userName: String,
val userEmail: String,
)
class ParameterTemplatePreviewParameterProvider: PreviewParameterProvider<ParameterTemplateView> {
override val values: Sequence<ParameterTemplateView>
get() = sequenceOf(
ParameterTemplateView(boxName = "test", userName = "nyao", userEmail = "halo")
)
}
テストコード
package email.whoto.whoto.view
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
class TemplateViewKtTest {
private val dummyData = ParameterTemplateView(
boxName = "INBOX",
userName = "Akira",
userEmail = "akira@test.cat"
)
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Before
fun setup() {
composeTestRule.setContent {
TemplateView(paraView = dummyData)
}
}
//初期の読み込み表示テスト
@Test
fun initialTemplateLoadTest() {
composeTestRule
.onNode(hasContentDescription("AppBar") and hasAnyChild(hasText(dummyData.boxName)))
.assertIsDisplayed()
composeTestRule
.onNodeWithText(dummyData.userName)
.assertIsNotDisplayed()
composeTestRule
.onNodeWithText(dummyData.userEmail)
.assertIsNotDisplayed()
}
//メニューボタンを押した時にメニューバーが展開されるかテスト
@Test
fun openDrawerContentTest() {
composeTestRule.onNodeWithContentDescription("Menu button").performClick()
composeTestRule.onNodeWithText(dummyData.userName).assertIsDisplayed()
composeTestRule.onNodeWithText(dummyData.userEmail).assertIsDisplayed()
}
}
テストの実行結果
図4: UI Unit Testの実行結果
図5: UI Unit Testの実行画面
以上のことを、全ての最小要素のComposable Functionに行います。
今回は、たかが7つぐらいしか画面遷移がないのに、気が遠くなりそうですね…
しかし、実用レベルでは全てをテストする必要はないので、例えばTextをClickableにしたときに、ちゃんと動いているか(後々のコード編集で誤って消しそうとかいうもの)を確認するなど、リスクのあるものにとどめておけばいいと思います。私も全てのComposable Functionにテストを書いているわけではないです。私の基準については、以下のアコーディオン(トグル)に記載していますので、興味のある方は是非参照ください。
UIテストのUnit Testをどこまで書くべきか〜書く書かないの個人的な基準〜
今回はテストを書いた上の例は、テストを書くか微妙なラインなのですが、UIの中にif文やfor文などが入っていれば、書くようにしています。例えば、単純にButtonのフォントやカラーを変えたようなものであればテストはしません。Previewで予想通りのレイアウトになっていれば、OKとしています。一方で名前を入力したら、候補のメールアドレスが出るようなTextFieldは「メールアドレスであるかどうかを判断して、状況に応じてalertを出す」や「メールアドレス補完して入力する」など多くの条件分岐を持つViewになるので、単体テストをするようにしています。
各ページのUIテストコードを書く(各ページ単位)
先程、作った最小単位のComposable Functionを統合した各ページのUIテストを書きます。つまり、図1で設計した各ページをテストしていくことになります。このテストの目的は、各要素を組み合わせた時に予期した描画や動作が行われるかをテストすることです。
テスト項目の書き出し
テスト項目は基本的に図2なのですが、入出力が対応して分かりやすいように、Helpページを例に以下に描き直したいと思います。
図6: Helpページの入出力関係(テスト項目)
図2より、こちらのほうが断然分かりやすいような気がします。少しこの図について解説します。
このページには以下に示す3つのComposable Functionが使われています。
- MainView
- TemplateView(メニューバー)
- HelpView(入れるコンテンツ、図6の白い部分)
- TemplateView(メニューバー)
MainViewの下にメニューバーを表示するTemplateViewがあり、その中にコンテンツを表示するHelpViewがあるという具合です。MainViewは表示するページを束ねる様な役割をしていて、ViewModelにある状態を見てTemplateViewの中に入れるコンテンツを制御しています。
このページをテストするためには、TemplateViewとHelpViewが描画されていることを検証する必要があり、それぞれで表示される「Help」という文字と「About this app」という文字が同時に表示されるかを検証すればいいということになります(これを仮に「初期画面確認項目」と呼びます)。
したがって、HelpページのUIテスト項目は以下のようになります。
表2: Helpページのテスト項目
ページ名 | 入力 | 出力 | 説明 |
---|---|---|---|
Helpページ | なし | ページタイトル(Help)、コンテンツ(About this app) | Helpページが読み込まれた時に、コンテンツが全て表示されるか確認 |
これに従って書いたテストコードが以下になります。
テストコード
package ...略
import ...略
@ExperimentalComposeUiApi
class HelpViewKtTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Before
fun setup() {
composeTestRule.setContent {
MainView(
mainViewModel = composeTestRule.activity.viewModels<MainViewModel>().value,
"help"
)
}
}
@Test
fun viewHelpPageTest() {
//メニューバーのタイトルを確認
composeTestRule
.onNode(
hasContentDescription("AppBar")
and hasAnyChild(hasText("Help"))
)
.assertIsDisplayed()
//Helpページのコンテンツを確認
composeTestRule
.onNodeWithText("About this app")
.assertIsDisplayed()
}
}
実装コードは、そのまま載せると膨大になるので、簡略化して以下に示します。
実装コード
...略
@Composable
fun MainView(mainViewModel: MainViewModel, startPage: String) {
...略
//メニューバーの表示
TemplateView(
paraView = parameterTemplateView
)
val parameterTemplateView = ParameterTemplateView(
...略,
content = {
NavHost(navController = navController, startDestination = startPage) {
composable("help"){
HelpView() //ここでHelpページのコンテンツを入れる
}
}
}
}
メニューボタンの動きなど、各要素については前章でテストしており、動作が保証されているという前提でテストコードを書いています。メニューボタンは次の章で行うページを跨いだUIテストで使うので、もしも統合時になにか不具合があり動かないとなれば、そこで分かるようになっています。
ページを跨いだUIテストコードを書く(アプリ全体、Modelなし)
各ページに対して、前章の各要素の統合テストが完了したら、次はページ遷移が予期したとおりに出来るかをテストします。このテストではページが戻るとか、ページが移るとか、ページの遷移があるため、ページ遷移で変わる値、例えばメニューバーのタイトルなどについて検証します。
以下にメニューバーの入出力項目について示します。
図7: Mainページの入出力(ページ遷移)関係
これらをテストすれば良いわけです。ページが遷移したことを確認するのは、基本的は遷移先のメニューバーのタイトルの文字とコンテンツの一部で確認します。コンテンツの一部が表示されていれば、それ以外の部分は前章で描画されているということが保証されているという前提です。
したがって、Mainページからのメニューで行うページ遷移のテスト項目は以下です(注)。
注:起点のページ
メニューは全てのページに付いているという特性上、Mainページを起点にしています。一番最初の章の段階で、メニューバーを単体で呼んで、その項目に各ページを紐付けて、そのページが表示されるかをテストしてもいいと思います。
テスト項目の書き出し
表3: Mainページ遷移テスト項目
入力項目 | 遷移先 | 説明(遷移判断) |
---|---|---|
アカウントの切り替え | アカウント切替ページ | メニューバータイトルの「Switch Account」とコンテンツの「Account List」で判断 |
設定 | 設定ページ | メニューバータイトルの「Setting」とコンテンツの「通知」で判断 |
ヘルプ | ヘルプページ | メニューバータイトルの「Help」とコンテンツの「About this app」で判断 |
戻るボタン | Mainページ | 上の各遷移先から元のMainページに戻るかを確認する。メニューバーの「INBOX」とコンテンツのメッセージリストの一部のタイトル名で判断 |
全てのテストコードを書くのは厳しいので、この中でもヘルプページへの遷移の部分を示します。
テストコード(MainページからHelpページへの遷移)
...中略
@Test
fun clickHelpButtonTest() {
clickHelpButtonAssert()
}
fun clickHelpButtonAssert() {
composeTestRule
.onNodeWithContentDescription("Menu button")
.performClick() //メニューボタンを押す
composeTestRule
.onNodeWithContentDescription("Help")
.performClick() //メニューのHelpを押す
composeTestRule
.onNode(hasContentDescription("AppBar")
and hasAnyChild(hasText("Help")))
.assertIsDisplayed() //遷移先のHelpが表示されているか確認する
composeTestRule
.onNodeWithText("About this app")
.assertIsDisplayed() //遷移先のコンテンツが表示されているか確認する
}
...略
このテストを全ページで行えば、理論上は繋がります。最後に、ランダムの順番でページ遷移が出来るかをテストするテストコードを書いてテストします。このテストを何回か行って、エラーが出なければViewの完成としていいだろうということです。
ランダムなページ遷移が出来るか検証する
最後に、全体をつなげたページ遷移のテストをします。Mainページが一番初めに読み込まれ、そこからランダムにページを読み出し、ページ遷移が行えるかをテストします。ここで、主にテストしたいのは例えば
Main -> Help -> Back(戻る) -> Main -> Setting
のように、戻るの後にページを呼んでも大丈夫か、ということであったり連続してページ遷移した時に、最初の起点であるMainページに戻れるのかなど、前章ではテストできなかった、ページ遷移の流れについてテストします。
このテストをするに当たって、各ページ遷移のテストコードを以下のようなブロック(関数)に分けます。
- 遷移イベント(メニューのSettingボタンやメッセージリストのメッセージを押すまで)
- 遷移確認(遷移先の確認、メニューバータイトルとコンテンツの一部)
このように2つに分けることで、戻るを押したときの検証をしやすくします。
いくつかページを読み込んだ後、戻るボタンを押して起点のページに戻ることをこれからします。
テストコード
実際に用いたテストコードは以下のようになります。(長いですごめんなさい)
C言語で探索アルゴリズムを書いたことがあり、それを真似しようとしたら非同期処理で上手くいかない…となり、こんな感じで書いたので、もしも、他に良い書き方があれば教えていただけると幸いです。
@Test
fun pageTransitionJourneyTest() {
//ランダムのリストを作る
val numberList = listOf<Int>(0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6)
val order = numberList.shuffled()
//順序に条件があって、その条件を満たすList用
val orderList = mutableListOf<Int>()
//一番最初に「戻る」があるとListの外(-1)を取るので、回避する
if (order[0] == 6) {
orderList.add(listOf(0,1,2,3,4,5).shuffled()[0])
}
//2(メッセージを開く)の前は必ずメッセージの一覧のページにいないといけないので、0を追加
for (i in order) {
if (i == 2) {
orderList.add(0)
}
orderList.add(i)
}
//初期画面を読み込む
initialLoadAssert()
//再帰関数でリストを順にたどり逆に戻っていく(戻るで初期まで戻るかも検証できる)
followList(numberList = orderList, 0)
}
fun followList(numberList: MutableList<Int>, pointer: Int) {
runBlocking {
val number = numberList[pointer] //今から読むページ番号(actionFunction参照)
actionFunction(number = number) //ページに移動
//リストが無くなるまで以下のif文を読む
if (numberList.size > pointer + 1) {
followList(numberList = numberList, pointer+1) //順方向に辿る
//順方向にたどりきったら、以下が呼ばれる
//戻るボタン、戻るボタンを呼ぶ1つ前のnodeは飛ばす(履歴に含まれていないから)
if (numberList[pointer] != 6 && numberList[pointer+1] != 6) {
onView(isRoot()).perform(ViewActions.pressBack()) //戻るボタンを押す
assertFun(number = number) //戻った先のページが読まれているか確認
}
}
}
}
//順方向にListをたどる時に使われる
fun actionFunction(number: Int) {
when (number) {
0 -> {
clickInboxButton() //INBOXボタンをクリック
validInboxButton() //Main画面が表示されているかを確認する
}
1 -> {
clickSwitchAccountButton() //アカウントの切り替えボタンをクリック
validSwitchAccountPage() //アカウントの切り替え画面が表示されているか確認
}
2 -> {
clickMessage() //メッセージを開く
validMessageDisplay() //メッセージの画面が出ているか確認
}
3 -> {
clickMakeNewMessageButton() //新しいメッセージを作成
validMakeNewMessage() //新しいメッセージを作成する画面にいるか確認
}
4 -> {
clickHelpButton() //ヘルプページへ飛ぶボタンを押す
validHelpPage() //ヘルプページが表示されているか確認
}
5 -> {
clickSettingButton() //設定ボタンを押す
validSettingPage() //設定画面が表示されているか確認
}
6 -> {
onView(isRoot()).perform(ViewActions.pressBack()) //戻るボタン
}
}
}
//逆方向にListを辿る(戻るボタンのみ)時に使われる
//呼び出している関数はactionFunction()を参照
fun assertFun(number: Int) {
when (number) {
0 -> {
validInboxButton()
}
1 -> {
validSwitchAccountPage()
}
2 -> {
validMessageDisplay()
}
3 -> {
validMakeNewMessage()
}
4 -> {
validHelpPage()
}
5 -> {
validSettingPage()
}
}
}
実行結果
これで、後はRepositoryとModelを実装すればいいですね。Repositoryの出力を、書くViewの引数に合わせれば良いはずです。
今後
重ねてになりますが、ここまで出来れば、後はViewに流すものを用意すれば良いはずなので、ModelやRepositoryの設計をして、Unitテストや統合テストを回し、実装していきたいなと思います。
こちらも出来たら、記事にしていきたいと思います。
問い合わせ
もしも、至らぬ点などがあれば、コメントにてご指摘いただければ幸いです。質問などございましたら、コメントにてお願い致します。ちょっと長文で、記事用に書いたコードもあり前後で用語や変数名が統一されていなかったり、文章が分かりにくい所ありましたら、指摘いただけると幸いです。よろしくお願い致します。
コード中に「これ、映すとまずいコード入ってるよ」など、コメントに書きにくい指摘などございましたら、お手数おかけし恐縮ですが、akira.kashihara[at]hotmail.comまでメールをお願い致します([at]をアットマークに変えてください)。
変更履歴
2022-01-08)新規作成
-
テスト自動化 / 園 https://speakerdeck.com/cybozuinsideout/test-automation-b98fc9a4-3cca-4090-8550-0aaa636368e2 (2021-12-30閲覧) ↩︎ ↩︎
-
テストコードの書き方はわかったけどテストケースってどうやって挙げたらいいの? / @tan3_sugarless https://qiita.com/tan3_sugarless/items/7bd89c92bfaac1e852e0 ↩︎ ↩︎
-
Android Jetpack ComposeでUI Testを体験してみた / Akira Kashihara https://zenn.dev/akira_kashihara/articles/d3e979d8714522 (2021-12-30閲覧) ↩︎
-
自動テストの品質 / 小井土亨 https://www.slideshare.net/koido1961/ss-84374271 (2021-12-30閲覧) ↩︎
-
2021新卒テスト研修資料 / 岡住和樹 https://speakerdeck.com/zumin/2021xin-zu-tesutoyan-xiu-zi-liao (2021-12-30閲覧) ↩︎
-
Compose レイアウトのテスト / Android https://developer.android.com/jetpack/compose/testing?hl=ja (2022-01-08閲覧) ↩︎
-
アプリ アーキテクチャ ガイド / Android https://developer.android.com/jetpack/guide?hl=ja ↩︎
-
奥澤 俊樹, "Jetpack Compose による Android MVVM アーキテクチャ入門", 株式会社インプレス R&D, 2021, ISBN978-4-295-60053-4 ↩︎
-
テスト自動化について、調べてみた / yutachaos on Qiita https://qiita.com/yutachaos/items/857472c7d3c65d3cf316 (2021-12-31閲覧) ↩︎
Discussion