Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scalaでもコードとテストを同じディレクトリに配置できる(テストのコロケーション)

社のSlackでこういう発言を見た。

Go みたいにプログラムとテストを同じ dir に入れたい。

そういえば、テストとコードを同じ場所に配置する言語をよく見かける。こうした、テストとコードを同じ位置に置くという思想を(テストの)コロケーションと呼ぶようだ。

www.mizdra.net

この考え方は特に最近の言語やフレームワークでよく見る気がする(一長一短あると思うのでその是非についてはいったん措く)。

さて、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を利用するとその内部でmaintestを認識してしまうためだろう。

サンプル

実際にこの手法で素朴なプロジェクトを作成してみた。srcディレクトリのストラクチャは以下の通りだ:

src
└── dev
    └── capslock
        └── collocationexample
            ├── Main.scala
            └── util
                ├── Fib.scala
                └── Fib.test.scala

github.com

特に問題なくsbt runsbt testに成功したし、VSCodeでは正しくコードを認識した(IntelliJではソースからテストを呼べそうに見えてしまうことがあるようだ)。

この先コロケーションさせたいかどうかはまだわからないが、とにかくやろうと思えばできることが分かった。Scala.jsなどとのクロスビルドへの影響は確認していないことに注意されたい(例えば、js固有の処理を記述したい場合など)。

リソースについて

ちなみにこの手法を採用してもリソースは従来通りのsrc/main/resourcesから読まれる。この設定も変更可能だ。

Compile / resourceDirectory := baseDirectory.value / "resources"
Test / resourceDirectory := baseDirectory.value / "testResources"

こうするとリソースはプロジェクトルートのresourcestestResourcesから読まれるようになる(詳しくはリポジトリを参照)。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?