今日はChiselのためのScalaのお勉強ネタを。調べるのはScalaのテストハーネスの一種であるScalaTestについて。
事の発端はSCR1というRISC-Vの実装をChiselで書いてみようとしていて、その際にriscv-
testsの各試験をChiselのテストハーネスであるChiselFlatSpec
を使ってどのように実装すれば良いのかがわからなかったからで少し調べたところChiselFlatSpec
を理解するためにはベースになってるScalaTestのFlatSpec
を理解しておかないと駄目な気がした、という話。
なお本記事は以下のQiitaの記事をなぞるところからスタートします。
Step単位で説明してくれていて分かりやすかったです。ありがとうございますm(_ _)m
※ScalaTestのバージョンが上がった関係でパッケージが移動したりしてたので、それは新しいものに合わせて直しました。
またScalaTestの説明も含めてドワンゴさんのScalaの研修テキストも併せて参考にさせていただきました。 こちらも分かりやすい資料を公開していただき、ありがとうございますm(_ _)m
Chisel-templateの例で気になっていたこと
とりあえず知りたかったのは以下3点。これを含めて調べて内容を以降に記載していきます。
- Chisel-templateに記載されている
ChiselFlatSpec
のテストを実行する際のコマンドsbt 'testOnly gcd.GCDTester -- -z Basic'
の意味 ChiselFlatSpec
上のテストの単位- 特定の試験のみを実行するにはどうすればいいのか
ScalaTest
ということでScalaTestの説明から。とりあえずドワンゴさんの説明を引用します。
ScalaTestは、テスティングフレームワークの中でも振舞駆動開発(BDD :Behavior Driven Development)をサポートしているフレームワークです。 BDDでは、テスト内にそのプログラムに与えられた機能的な外部仕様を記述させることで、テストが本質的に何をテストしようとしているのかをわかりやすくする手法となります。
最近になって知ったBDDという開発手法ベースでテストが書けるフレームワークとのこと。 ScalaTestでは複数のテストのスタイルに対応するためのtraitを持っていて、使用者がScalaTest使用間に使っていたフレームワークからの移行の敷居を下げているような感じ。
以下は上記のページでScalaTestのスタイルとして紹介されているものから幾つか抜粋。
- FunSite : xUnit使ってた人向け
- FlasSpec : xUnit使っててBDDに移行したい人向け
- FunSpec : RubyのRSpec使ってた人向け
- WordSpec : Scalaのspecs/specs2を使ってた人向け
- FreeSpec : BDDの経験が合ってガイダンスなしで仕様をテストに落とし込みたい人向け
- PropSpec : プロパティチェック使ってテストを実施したい人向け
この記事はあくまでもChiselFlatSpec
を使うための前段階でScalaTestを学ぶというのが本旨なので、上記については「ほうほう、なんか色々あるのね、、。」程度のもので済ませます。
FlatSpec
使ったテストの例
ここからは冒頭で紹介したQiitaの記事に沿って、基本的な使い方を見ていきます。 ということで、色々準備。 なおQiitaの記事ではeclipse + activater使ってますが、本記事ではIntelliJ IDEA + sbtプラグインを使っていきます。
プロジェクトの準備
本記事ではIntelliJ IDEAではsbtプラグインを入れておくと、プロジェクト作成時にsbtプロジェクトが作成可能なのでそれを使って"Hello"プロジェクトを作成します。
プロジェクト作成すると以下のように"build.sbt"やソースコード用のディレクトリが生成されます。
$ tree . ├── build.sbt ├── project │ └── build.properties └── src ├── main │ └── scala └── test └── scala
テストの作成
Qiitaの記事ではプロジェクト作成したので次は動くプログラムを作成、となっていますが、それはすっ飛ばしてFlasSpec
を使ったテストの作成を行います。
まずはScalaTestを使うために"build.sbt"に依存関係を追加。
// 下記の1行をbuild.sbtに追加する libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test"
上記を追加後にテストを実装したファイル"HelloSpec.scala"を作成。
// src/test/scala/HelloSpec.scala // 以下はQiitaの記事のコードを元にScalaTestの2019/03/19時点の最新版である"3.0.5"に合わせたもの。 // 変更点は以下 // - scalatest.matchers.ShouldMatchersがscalatest.Matchersに移動 // - be === がdeprecated扱いになり、代わりにbe / equal / === を使うことが推奨になった // - "be ==="使うとテストがFAILになる import org.scalatest.{FlatSpec, Matchers} class HelloSpec extends FlatSpec with Matchers { "Hello" should "have tests" in { true should equal (true) } }
そして実行。
test [info] Compiling 1 Scala source to F:\StudyScalaTest\target\scala-2.12\test-classes ... [info] Done compiling. [info] HelloSpec: [info] Hello [info] - should have tests [info] Run completed in 494 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 0, canceled 0, ignored 1, pending 0 [info] All tests passed. [success] Total time: 4 s, completed 2019/03/19 20:04:58
テストの本体(in
以下のブロック式)は読み下すと「"true"は"true"に等しくあるべき」なので当然PASSします。
この読み下せるのがBDDのBehaviorの部分を端的に表しているものっぽいです。
とりあえずある意味最小のテストはこれで書けたことになるので、もう少しいろいろ試してみます。
テストの追加と実行
テストの追加に関しては最初のテストケースと同様に「"xxx" should "specs" in {}」の宣言を用意してブロック式中にテストを記載していく感じにすればいいみたい。
it
を使うと直前の"xxx"の部分をそのまま引き継ぐ感じにしれてくれる。
例えば以下のように2つ目のテストケースを追加する(テストケースは超適当。。)とit == "Hello"
として実行されます。
class HelloSpec extends FlatSpec with Matchers { "Hello" should "have tests" in { true should === (true) } it should "have another test" in { true should equal (true) } }
- 実行結果のログ
[info] Compiling 1 Scala source to F:\StudyScalaTest\target\scala-2.12\test-classes ... [info] Done compiling. [info] HelloSpec: [info] Hello [info] - should have tests [info] - should have another test
因みにど頭のテストケースからit
を使うとクラス名が使用されてました。
特定のテストの実行
本記事の冒頭に書いたとおりこれがこの記事をまとめようと思ったきっかけの一つでした。
もともとはChisel-templateで使われているChiselFlatSpec
を実行する際に以下のように実行するべし!!と書いてあって、そのように実行すると確かに実行できるのだが、どういう意味やねん!!!ってなったのよね。
sbt 'testOnly gcd.GCDTester -- -z Basic'
調べてみたところ上記のコマンドは大きく2つの要素から成っているようです。
- sbtのコマンド部分 →
sbt 'testOnly gcd.GCDTester --
- 上記で"src/test/scala"以下にある
gcd.GCDTester
を実行することになる(詳細はここ(sbtのtestOnlyの説明))
- 上記で"src/test/scala"以下にある
--
はScalaTest側の引数を追加する際のデリミタ(詳細はここ(sbtのtestOnlyのオプションの説明))ScalaTestの引数 →
-z Basic
-z
はオプション指定でGCDTester
の中のテストケースから後続の文字列が含まれるテストが実行されます。
その他のオプションについてはScalaTestのユーザーガイドの以下のページを参照。
重ねてではあるが筆者の元々の目的は
ChiselFlatSpec
を使ってriscv-tests
の各命令の試験を単体で実行したい
というものなのでそれに該当するオプションが無いかを上記のオプションのページで探してみたところ-t <test name>
というオプションがありました(まあ無いわけないよね。)
早速試してみよう、、と思ったところで-t <test name>
の<test name>
って何??という状態なことに気づき、悩んだ末にとりあえずそれっぽいものを打ち込んで試してみることに。
$ sbt 'testOnly HelloSpec -- -t "have tests"' [info] HelloSpec: [info] Hello [info] Run completed in 426 milliseconds. [info] Total number of tests run: 0 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0 [info] No tests were executed. [success] Total time: 2 s, completed 2019/03/19 20:30:53
無いって言われた。。。
良くわからないのでデバッガ使ってトレースしてみたところどうも<test name> = "Hello should have tests"
が完全なテスト名になっているっぽいのでコレでトライ。
$ sbt 'testOnly HelloSpec -- -t "Hello should have tests"' [info] HelloSpec: [info] Hello [info] - should have tests [info] Run completed in 3 seconds, 782 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [success] Total time: 6 s, completed 2019/03/19 20:37:44
出来た!!
ということなのでFlasSpec
上のテスト名は
"xxx" should "yyy" in {<テスト本体>}
で考えた時にin
より前の文が全部("xxx" should "yyy")ということになる模様。
ScalaTestの-t
の説明部分に記載がありますが、ユーザーレベルでも-t
を使ったテストの実行は可能ではるものの、テスト名は振る舞いを記述する関係上長い文になるので、-z
を使ってパターンマッチで実行するのがより実践的とのこと。
まだまだ色々機能はあるみたいだけど、とりあえずChiselFlatSpec
使って試験環境を作る上で最低限知りたいことは分かったので、次回は簡単なChiselのモジュールを書いてそれを試験する環境を作ってみようと思います。興味があればご覧ください。