はじめに
ユニットテストをうまく使って、良いプログラムを作りたい@tomoyuki-nakabayashiです。
QtのC++側は、googletest使ったり、QSignalSpy使ったり、[Qtのユニットテストを参照したり]
(https://github.com/qt/qtbase/tree/5.11/tests/auto)、して多少苦労しながらも、やりたいユニットテストが書けるようになりました(Qtは参照できるユニットテストが大量にあるのでありがいたいです)。
お次は、QML(画面)の番だ!ということで、Qt Quickのユニットテストについて勉強したことを振り返ります。実験的な側面が強いので、YWT(やったこと、わかったこと、次やること)でざっくりいきます。
まだまだ始めたばかりですので、マサカリ絶賛募集中です。
コードはこちら
やったこと
私自身が普段、あまりQt Quickを使わないため、Qt Quickの勉強も兼ねることにしました。
Qt QuickではじめるクロスプラットフォームUIプログラミングの第5章『Qt Quick実践編~音楽プレーヤー~』を写経しながら、ユニットテストを書きました。
余談
上記書籍のはじめに、で言及されていますが、QMLはVerilogなどのハードウェア記述言語(HDL)に似た部分が多かったです。コンポーネントを作って、各コンポーネントを繋ぎ合わせて(関係性を記述して)全体を作っていくようなイメージでした。
わかったこと
- 書けるやん、画面の自動テスト
- テスト容易性を考えることで良い設計を導けそう
- コンポーネント化超大事
- 座標のテストはよく考えよう
- 期待値の計算が難しくなったり、座標値を即値で指定するテストばかりになったりする
- 少しコンポーネント化をサボると、すぐテストできなくなる
- タイミング系でflakyなテストができてしまった…
書けるやん、画面の自動テスト
例えば、マウスで操作するボタンのテストは次のようになりました。
(QiitaってQMLのシンタックスハイライトもあるんだなぁ)
3つのことをテストしています。
- ボタンがクリックされたこと(rootにフラグを用意しましたが、SignalSpyでやった方が良かったですね)
- マウスポインタがボタン上でホバーしているときに、ボタンの色が変わること
- マウスが押されている(press状態の)間は、ボタンの色が変わること
import QtQuick 2.9
import QtTest 1.1
import "../../qgallery/qml/components"
Rectangle {
id: root
width: 640
height:480
property bool imageButtonClicked: false
ImageButton {
id: imageButton
anchors.centerIn: parent
width: 70
height: 50
backImage: "../../qgallery/qml/resources/button_border.png"
foreImage: "../../qgallery/qml/resources/button_play.png"
onClicked: {
root.imageButtonClicked = true
}
}
TestCase {
name: "ImageButton"
when: windowShown
function test_can_handle_on_clicked_event() {
compare(root.imageButtonClicked, false)
mouseClick(imageButton)
compare(root.imageButtonClicked, true)
}
function test_change_color_when_mouse_is_hovering() {
mouseMove(imageButton, imageButton.width/2, imageButton.height/2)
compare(imageButton.filterColor, imageButton.hoverColor)
mouseMove(imageButton, -1, -1)
compare(imageButton.filterColor, "#00000000")
}
function test_change_color_when_mouse_is_pressing() {
mousePress(imageButton)
compare(imageButton.filterColor, imageButton.pressedColor)
mouseRelease(imageButton)
compare(imageButton.filterColor, imageButton.hoverColor)
}
}
}
テスト容易性を考えることで良い設計を導けそう
QMLでは、外部から直接アクセスできるpropertyは、rootエレメントのpropertyだけとなります。
子エレメントのプロパティはprivateなので、子エレメントの変化を外部からテストすることができません。
そのため、テストのために、エレメント内に複雑な子エレメントを作らずに、独立した部品化することを考えます。
部品化した後は、そのエレメントをユニットテストで動作確認しておくことで、安心して、子エレメントとして使うことができます。
テストできるようにすることで、部品化が進みそうです。
座標のテストはよく考えよう
期待値の計算が難しくなったり、座標の期待値を即値で指定するテストばかりになり困っています。
座標値を親エレメントの相対値で指定するのが基本ですが、『えーっと、rootエレメントの左端から、5スペースがあって、rootエレメントのwidth*0.9の1/2だから…』とかこんな感じです。
タイミング系でflakyなテストができてしまった…
音楽ファイルを1秒再生すると、表示が更新される機能があります。
このテストをしようと、実際に音楽ファイルを1秒再生させて、更新するプロパティをテストしました。
しかし、タイミングによって、成功したり失敗したりするテストになってしまいました…
難しい。
次やること
- TDD
- C++側からのテストとの組み合わせ
TDD
TDDでQt Quickを書いてみます。
現状、それがうまくいくのかどうか、がわかっていない状態です。
QML Snapshot Testing with TDDといった記事もあるので、単純にTDDをやるのは難しい or 非効率的なのかもしれません。
Snapshot Testingも使ってみたいところです。
C++側からのテストとの組み合わせ
Qt Declarativeのユニットテストで行われているように、C++側からQMLをテストすることができます。こちらも少し手を出していて、勉強中です。
C++側からであれば、QMLのツリーをたどって、Qt Quickのコンポーネントとしては公開していない子オブジェクトもテストできそうです。
Qtのユニットテストを参考にしながら、Qt QuickのユニットテストとC++側からのテストとの使い分け方、を明確にしていきたいです。