Flutterでテストコードだけが外部参照できるWidgetを組みたかった
TL;DR
ただし追加するだけだと警告のみなので,analyzerの機能を使ってエラーに昇格するといいかも
サジェスト汚染は防げないかも…
最近ちょっとテストコードの書き方を覚えてきていい感じになっているところであります.
さて,Flutterにおいてテストコードを書く場合,Widgetに対するテストとして Widget Test というものを書くことができます.他の環境でいうUIテストに近いですが,UIテストと言うとE2Eテストみたいな印象(個人の感想)があるのに対してWidgetテストはコンポーネント単位でできるので,うまくWidget切り出しを行っていればそれらで単体テストが可能です.VueとかReactもそういうのがあるのかな?
ここではWidget Testの話を前提として,その細かい書き方などは割愛します(本質ではないため).
今回自分が当たった障壁について書いていきます.
早速ですが,状況としては以下のような感じです.
code:dart
/// 渡したリストの長さ分(あるいは内部ロジックで計算した分,この例では数を制限している)だけ同じWidgetを描画し,その個数が意図したものと一致しているかをテストしたい
/// コンポーネント内部
@override
Widget build(BuildContext context) {
final items = imageUrls.length > 4 ? imageUrls.sublist(0, 4) : imageUrls;
...
Row(
children: <Widget>[
for (final item in items) ...[
FixedSizeItemImage(imageUrl: item),
const SizedBox(width: 8),
],
],
),
...
}
}
/// 基本的にWidget切り出しをする場合はprivateにするが,テストコードから直接参照するためにpublic
class FixedSizeItemImage extends StatelessWidget {
const FixedSizeItemImage({Key key, this.imageUrl}) : super(key: key);
final String imageUrl;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 64,
height: 64,
child: CachedNetworkImage(imageUrl: imageUrl),
);
}
}
/// テストコード
...
/// 型でWidgetを見つけ,その個数をモックで注入した分指定する
expect(find.byType(FixedSizeItemImage), findsNWidgets(3));
...
日本語にすると,imageUrlのリストを使って,そのURLの画像を全て表示するというシチュエーションですね.UIのスタイルに合わせて全く同じ要素を反復的に描画したいです.
その描画自体はコード上でやっているように collection for などを使えば問題なくいけますが,問題はこれをテストしたい時.
buildメソッドの始めで,imageUrlsのサイズを制限しています.もしimageUrlsのサイズが5以上だった場合,描画するときは4に制限されます.
本来これはView以外のところでやるべきという意見はさておき,この状況のテストとしては FixedSizeItemImageの数がいくつ描画されたか で挙動を把握するしかありません.
Widget Testでは,特定のWidgetをテストコードから取得する際にkeyを使うことができます.アプリコード内にグローバルで参照できるValueKeyなどを宣言しておき,それをテストコードから参照するといった流れです.
この場合,ValueKeyの値をリスト内の要素にして,テストのときはexpectをfor文で回せばいいだけです.しかし,そうなるとアプリコードと同じロジック(今回で言う数の制限)をテストコードで適用しないといけないので,テストとして微妙な記述になるかと思います.
という感じで,一応テストしたい要項そのものは満たせるのですが,一点だけ問題があります.FixedSizeItemImageがpublicになっていることです.つまりこのWidgetはアプリコード内のどこからでも参照できてしまいます.
複数ページで使えるコンポーネントでもないのに,グローバルに参照できるのはちょっと……
というわけで色々調べた結果,こんな便利なものが.
@visibleForTestingアノテーションをつけると,なんと自身のファイル及びテストコード以外から参照しようとすると警告をだしてくれます.
https://gyazo.com/53269c8faac0eb0bd6a1fbd1bef306cc
https://gyazo.com/d0fa7ba4c7fc09080d70c2026159bb45
(画像は参考用)
便利ですね.しかし,これだけでは警告を出してくれるにすぎないので,無視できてしまいます.そこで,Dart Analysisの機能を活用していきましょう.
手順はシンプルで,プロジェクトルートに analysis_options.yamlを追加,中身にこれを記述するだけです.
code:analysis_options.yaml
analyzer:
errors:
invalid_use_of_visible_for_testing_member: error
するとさっきまで警告だったものがコンパイルエラーになります.
https://gyazo.com/7dbb3e3b0777bb2ef738a0c1a7a9ed5f
これでテストコード以外からの不必要な参照を避けたWidgetを書くことができ,うっかりミスを防止できます.
とはいえ,ここまでやったとしても一つ問題が残っており…
そう,参照できないとは言っても,そのWidgetはエディター上でサジェストに出てきてしまいます.
これは…どうすればいいか検討つかないので一旦ヨシ!
flutter-intellij側でなんとかなるんですかね?