新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:Scala逆引きレシピ

第11章:sbt

sbtの使い方

セットアップ

sbt=Simple Build Toolは、Scalaベースのビルドツールで、Scala開発の標準的なツールである。
Scalaコンパイルユニットテストの実行、jarファイルやscaladocの生成などを自動化できる。
内部的にApache Ivyを使用したライブラリ管理機能も備えており、
設定ファイルにライブラリの情報を記述しておくと必要なjarファイルをネットワーク経由でダウンロードできる。

ダウンロードしたsbtのjarファイルsbt-launch.jarを適当なディレクトリに配置し、
同じディレクトリに以下の起動スクリプトを作成する。

#!/bin/sh
java-Xmx512M-jar `dirname $0`/sbt-launch.jar "$@"
sbtプロジェクトの構成

sbtでビルドを行うプロジェクトでは、以下のディレクトリ構成に従って配置する。

sbtプロジェクトの設定を行うには、シンプルな設定ファイルで記述するBasic Configurationと、
コードで複雑な設定を行うFull Configuration Scalaの2通りの方法がある。

Basic Configurationでは、プロジェクトルート以下の*.sbtというファイルで行う(慣例的にはbuild.sbt)。
最も簡単な例を以下に示す(各行の間に空行が必要)。

name := "MyProject"

version := "1.0"

scalaVersion := "2.9.2"

sbtプロジェクトを作成してビルドを行う手順は以下のとおり。

  1. 適当なディレクトリを作成し、build.sbtを作成
  2. src/main/scalaディレクトリを作成し、HelloWorld.scalaファイルを作成
  3. プロジェクトのルートに移動し、sbt compileでコンパイル
  4. sbt runで実行
ライブラリを使う

プロジェクトの設定ファイルにライブラリの依存関係を追加する場合、
"グループID" % "アーティファクトID" % "バージョン" % "スコープ" という指定を行う。
テスト時にJUnitを使用するには、build.sbtに以下の記述を追加する。

libraryDependencies += "junit" % "junit" % "4.8" % "test"

スコープはライブラリの有効範囲を示すもので、以下のいずれかを指定する。

スコープ 説明
compile コンパイル・実行時に必要なライブラリ(省略時のデフォルト)
provided コンパイル時に必要だが、実行時には実行環境で提供される(APサーバのライブラリなど)
runtime 実行時のみ必要なライブラリ
test テストケースのコンパイルおよびテスト実行時にのみ必要なライブラリ

第12章:テスト

ユニットテストの基本

Scalaのテスティングフレームワーク

JavaではJUnitが広く使われているが、Scalaにも同様のテスティングフレームワークが存在する。

  • ScalaTest:様々なスタイルでテストケースを記述できる。Specs2に比べシンプルで扱いやすい
  • Specs2:BDDスタイルのフレームワークアサーションのためのDSLが充実しており高機能
  • ScalaCheck:テストデータのパターンを自動生成することで網羅的なテストを簡単に行う
  • ScalaMock:モックのソースコードを自動生成する
  • Setak:Akkaのアクターを使ったプログラムのためのフレームワーク

ScalaTest

テストケースの作成

テストケースにミックスインするトレイトが複数用意されており、
それらを使い分けることで様々なスタイルでテストケースを記述できるという特徴がある。

  • org.scalatest.FunSuite:TDD(テスト起動開発)向き
  • org.scalatest.FunSpec:BDD(振舞駆動)向き
  • org.scalatest.FeatureSpec:BDD向けだが、受入テストなどシナリオベースのテストに特に向いている

ScalaTestを使うには、build.sbtに以下の依存関係を追加する。

libraryDependencies ++= Seq(
  "org.scalatest" %% "scalatest" % "1.8" % "test"
)

テストケースの作成例として、次のようなユーティリティメソッドに対するテストケースを考える。

object StringUtils {
  def isEmpty(value: String): Boolean =
    value == null || value.length ( ) == 0
}

テストケースは以下のようになる。

import org.scalatest.FunSuite

class StringUtilsSuite extends FunSuite {
  // 引数nullの場合にtrueを返すことを確認
  test("StringUtils#isEmpty returns true for null") {
    assert(StringUtils.isEmpty(null))
  }

  // 引数が空文字の場合にtrueを返すことを確認
  test("StringUtils#isEmpty returns true for empty string") {
    assert(StringUtils.isEmpty(""))
  }

  // 引数が任意の文字列の場合にfalseを返すことを確認
  test("StringUtils#isEmpty returns true for null") {
    assert(!StringUtils.isEmpty("a"))
  }
}

assertメソッドは結果がtrueであることを、expectメソッドは期待値と等しいことを書くにする。
また、interceptメソッドは例外が発生することを確認する。

assert(list.nonEmpty)
assert(list.size == 2)
// 失敗したときに表示されるメッセージを指定
assert(list.nonEmpty, "list is not Empty")
// ===を使うと失敗したときに右辺と左辺の値を表示してくれる
assert(list.size === 3) // => 2 dit not equal 3

// 期待値55と実行結果が等しいことを確認
// 実行結果を返す処理を関数を渡すこともできる
expect(55) {
  (1 to 10).foldLeft(0){(a, b) => a + b}
}

// 例外の発生を確認
intercept[NoSuchElementException] {
  val s = option.get
}
事前/事後処理を行う

BeforeAndAfterトレイトをミックスインし、事前/事後に行いたい処理をbefore/afterメソッドに記述する。

import org.scalatest.FunSuite
import org.scalatest.BeforeAndAfter

class HogeSuite extends FunSuite with BeforeAndAfter {
  // 各テストの前に呼び出される
  before {
    println(" ** before ** ")
  }
  // 各テストの後に呼び出される
  after {
    println(" ** after ** ")
  }
  ...
}

なお、BeforeAndAfterAllトレイトをミックスインし、beforeAll/afterAllメソッドをオーバーライドすることで、
テストケース全体の実行前/後に行いたい処理を記述することができる。

Specs2

テストケースの記述

BDDスタイルで記述でき、テストの目的に応じて以下のいずれかの記述スタイルを選択できる。

libraryDependencies ++= Seq(
  "org.specs2" %% "specs2" % "1.10" % "test"
)

単体テストスタイルのテストケースには、org.specs2.mutable.Specificationトレイトを使う。

import org.specs2.mutable._

class StringUtilsUnitSpec extends Specification {
  "StringUtils#isEmpty" should {
    "return true for null" in {
      StringUtils.isEmpty(null) must be True
    }
    
    "return false for any other strings" in {
      StringUtils.isEmpty("a") must be False
    }
  }
}