こんにちは、株式会社CyberZの森です。
モバイルアプリケーションは年々複雑化しており、増加していくUIのバリエーションに苦労されている方も多いのではないでしょうか?
UIにまつわる課題には、以下のようなものがあると考えています。
ビジネスロジックの複雑化に伴い、それを表現するUIも複数のケースがあり複雑化してきている。何か表示を追加すると、以前の表示が崩れる心配があるが、1個1個の状態に対して手動でテストするのは限界がある。
特定のユーザの特定の状態のみで表示されるUIがある。それを再現するための手順は多く、毎回手動でチェックするのは苦痛である。
様々なところで共通で使われているコンポーネントがある。ここに改修を入れたいが全ての箇所で表示が崩れないか不安である。
これらを解決するために、UIに対する自動テストを行うことは一つの選択肢でしょう。
最近はVisual Regression Testといわれる、スクリーンショットをピクセル単位で比較し、差分を検知する取り組みもあります。
一方で、UIを含む自動テストを作成するのは、十分にコードが整理されてる必要がありますし、環境構築の難易度も高く、メンテナンスにも手間がかかります。
今回は、UI Catalogという選択肢と、作成したAndroid向けUI Catalog Libraryを紹介します。
目次
UI Catalog Library – Katalog
今回、上記の問題を解決するためにAndroid向けUI Catalog Library – Katalogを開発しました。
Katalogを使うことで、本体のアプリケーションとは別に、コンポーネントやページを登録するアプリケーションを作成できます。
これにより、細かい状態の再現が可能になり、手動テストがしやすくなります。
また、コンポーネントを整理・管理することでUIの再利用にも役に立つでしょう。
KatalogはJetpack Composeで作られており、以下のようなDSLでComposableを登録することで、簡単にコンポーネントをプレビューすることが出来ます。
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
registerKatalog(
title = "My App Catalog"
) {
compose("UI Component") {
Text(text = "Hello, World")
}
}
}
}
Applicationで予めコンポーネントを登録しておき、デバッグメニュー等からKatalogActivityを起動します。
KatalogActivity.start(activity)
Android View / DataBinding / ViewBinding / Fragmentにも対応しています。
// Android View
view(
name = "TextView"
) {
TextView(context).apply {
text = "Hello, World"
}
}
// DataBinding / ViewBinding
binding(
factory = SampleBinding::inflate,
name = "Binding Sample"
)
// Fragment
fragment {
SampleFragment()
}
コンポーネントはDSLを使ってグルーピングすることが出来ます。
registerKatalog(
title = "My App Catalog"
) {
group("Group 1") {
compose("UI Component") {
/* ... */
}
}
group("Group 2") {
/* ... */
}
}
グループは変数にも割り当てることができ、ファイル分割が可能です。
// file1
val group1 = group("Group 1") {
/* ... */
}
// file2
val group2 = group("Group 2") {
/* ... */
}
// application file
registerKatalog {
title = "My App Catalog"
group(group1, group2)
}
詳しい使い方は ドキュメント を確認してください。
Katalogを開発するにあたって、以下のライブラリに強く影響を受けています。
Katalogの活用法
複雑化するコンポーネントに対し、網羅的なダミーのdataを用意することで、一覧で全てのケースを確認することが出来ます。
compose("UserRow") {
UserRow(
user = User(
id = 0,
name = "Bob"
)
)
}
compose("UserRow - long named") {
UserRow(
user = User(
id = 0,
name = "very very very long name"
)
)
}
これにより、更にケースを追加したときに以前のケースで問題がないか即座に確認ができ、安心して変更を加えられるようになるでしょう。
ダミーのViewModelを用意することで、画面全体のプレビューを用意することも出来ます。
binding(
factory = DetailBinding::inflate,
name = "Detail"
) {
it.viewModel = DummyDetailViewModel()
it.lifecycleOwner = lifecycleOwner
}
プレビューするスコープを広げれば広げるほど表示されてる内容の信頼度が増しますが、同時にメンテナンスコストも上がるため、細かいUIの出し分けについては各コンポーネントで確認するのが良いと考えています。
これは、Unit TestとIntegration Testの関係性によく似ています。
他にも、Katalogを使うことで細かい仕様が決まりきっていない場合でも小さいコンポーネントから実装を開始することが可能になります。
用意したUI Catalogのプレビューに対して、スクリーンショット等を用いたVisual Regression Testを導入するのも有効でしょう。
是非、より良い活用方法を探してみてください。
エクステンション機能とTheme
Katalogでは様々な要求を実現するため、エクステンションという考え方を採用しています。
例えば、AndroidのThemeはエクステンションを用いて以下のように指定します。
registerKatalog(
title = "Android Sample",
extensions = listOf(
AndroidThemeExt(R.style.MyTheme)
)
) {
/* */
}
また、ComposeのThemeも以下のように指定します。
registerKatalog(
title = "Android Sample",
extensions = listOf(
ThemeExt { content ->
MaterialTheme(
colors = MaterialTheme.colors.copy(
background = Color.Red
),
content = content
)
}
)
) {
/* */
}
今後も追加機能はエクステンションとして提供予定です。
また、エクステンションは自作することが可能です。詳しくは以下のページを御覧ください。
詳細:Create a Extension | Katalog
今後の展望
Katalogは今後も以下のような機能を追加予定です。
- 検索機能
- スクリーンショットテストとの連携
- さらなるエクステンションの提供
- Compose Multiplatformの対応
ぜひ気になる所や欲しい機能があれば Issue で教えて下さい。
OPENREC.tvのAndroidアプリチームではKatalogを使ったコンポーネント開発の他、開発効率の向上やデリバリー品質の改善に取り組んでいます。
少しでも興味を持っていただけたら、Meetyでカジュアルにお話できればと思います!