社のSlackでこういう発言を見た。
Go みたいにプログラムとテストを同じ dir に入れたい。
そういえば、テストとコードを同じ場所に配置する言語をよく見かける。こうした、テストとコードを同じ位置に置くという思想を(テストの)コロケーションと呼ぶようだ。
この考え方は特に最近の言語やフレームワークでよく見る気がする(一長一短あると思うのでその是非についてはいったん措く)。
さて、Scalaではどうかというと、一般的にソースコードはsrc/main
に、テストはtest/main
に置く。厳密には、これを決めているのはScalaの仕様ではなく、デファクトなビルドツールsbtのデフォルト設定だ。
Scalaでテストのコロケーションを実現する
では、Scala(のsbt)ではこれができないのかというと、そんなことはない。筆者がScalaのDiscordで訊いてきたところ、特にトリッキーな方法を使わずにsbtの設定で実現できるとのことだった。以下にbuild.sbt
を示す:
// sbt 1.10.2 val scala3Version = "3.5.0" // Thanks to https://discord.com/channels/632150470000902164/922600050989875282/1285830933030633557 // make compile sources directory and test sources directory be the same directory Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src") Test / unmanagedSourceDirectories := (Compile / unmanagedSourceDirectories).value // `*.test.scala` are test sources; all other `*.scala` files are compile sources Compile / unmanagedSources / fileInputIncludeFilter := "**/*.scala" Test / unmanagedSources / fileInputIncludeFilter := "**/*.test.scala" Compile / unmanagedSources / fileInputExcludeFilter := (Test / unmanagedSources / fileInputIncludeFilter).value Test / unmanagedSources / fileInputExcludeFilter := sbt.nio.file.PathFilter .fromFileFilter(sbt.io.NothingFilter) lazy val root = project .in(file(".")) .settings( // ... )
このbuild.sbt
では、以下のような設定を行っている:
- プロジェクトルートから見た
src
ディレクトリはソースディレクトリであると指示する - プロジェクトルートから見た
src
ディレクトリはテストディレクトリであるとも指示する - ソースディレクトリのうち、
**/*.scala
にマッチするもののみがソースである - テストディレクトリ(ソースと同じ)のうち、
**/*.test.scala
にマッチするもののみがテストである - ソースディレクトリのうち、テストに合致するものはソースではない
- テストディレクトリは特にフィルタしない
ここでunmanagedSources
を利用しているのは、通常のscalaSource
を利用するとその内部でmain
やtest
を認識してしまうためだろう。
サンプル
実際にこの手法で素朴なプロジェクトを作成してみた。src
ディレクトリのストラクチャは以下の通りだ:
src └── dev └── capslock └── collocationexample ├── Main.scala └── util ├── Fib.scala └── Fib.test.scala
特に問題なくsbt run
やsbt test
に成功したし、VSCodeでは正しくコードを認識した(IntelliJではソースからテストを呼べそうに見えてしまうことがあるようだ)。
この先コロケーションさせたいかどうかはまだわからないが、とにかくやろうと思えばできることが分かった。Scala.jsなどとのクロスビルドへの影響は確認していないことに注意されたい(例えば、js固有の処理を記述したい場合など)。
リソースについて
ちなみにこの手法を採用してもリソースは従来通りのsrc/main/resources
から読まれる。この設定も変更可能だ。
Compile / resourceDirectory := baseDirectory.value / "resources" Test / resourceDirectory := baseDirectory.value / "testResources"
こうするとリソースはプロジェクトルートのresources
とtestResources
から読まれるようになる(詳しくはリポジトリを参照)。