$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

ScalaTestのshouldBeとmustBeの違いは何か

ScalaTestのMatchers系にshouldBeとmustBeがあって、どういう違いがあるのだろうと気になったので調べてみたのでメモ。

http://www.scalatest.org/user_guide/using_matchers を読むと、

Trait MustMatchers is an alternative to Matchers that provides the exact same meaning, syntax, and behavior as Matchers, but uses the verb must instead of should. The two traits differ only in the English semantics of the verb: should is informal, making the code feel like conversation between the writer and the reader; must is more formal, making the code feel more like a written specification.

と書いていて、両方共機能的違いはまったくなく、単にshouldとmustというメソッド名が違うというだけだった。まじか...

【Scala】Emacsで現在編集している部分のテストを実行する

Scalaのテストを実行する時、sbtを使うと、特定のテストクラスだけの実行や、特定のテストケースだけの実行ができる。やり方はScalatest: 特定のテストケースだけ実行したい - Qiita で紹介されているとおり。

ただ、コードはテキストエディタで書いているので、このコマンドを使うとしても

  • 編集を終える
  • 今のファイルのテストクラス名とdescribeの文字列をコピーする
  • ターミナルを開いて、sbtでtestOnlyを使ってコピーした文字列を貼り付けて実行

のように、結構面倒という問題がある。

そこでEmacsで現在編集している部分のテストを実行するユーティリティを作り、編集 -> テスト -> 編集のループを回しやすくしてみた。今回はそのことについてご紹介。

今回のユーティリティで出来ること

以下のように、現在ファイルのテストクラスだけEmacs上で実行したり

さらに、現在のカーソルが存在するdescribeのテストだけ実行したりできる。以下のアニメーションではScalaTestExample#listだけ実行されている。

実現方法

現在編集中のバッファから、パッケージ名・テストクラス名・describeを抜き出し、それをsbt-modeに渡すことで実現できる。

sbt-mode というものを使うと、Emacs上でsbtを動かし、それに対してコマンドを送ることができる。まずこれをインストールする。

M-x package-install RET sbt-mode

後は以下のような実装を行うと、sbt/test-only-current-specで現在のバッファのテストクラスを実行でき、sbt/test-only-current-describeで現在カーソルがいるdescribeのテストだけを実行できる。

;;; 現在のバッファのテストクラスを実行する
(defun sbt/test-only-current-spec ()
  "Run test with current file."
  (interactive)
  (sbt-command
   (format "testOnly %s" (scala/find-spec-name-with-package-current-buffer))))

;;; 現在カーソルがいるdescribeのテストだけを実行する
(defun sbt/test-only-current-describe ()
  "Run current describe test"
  (interactive)
  (sbt-command
   (format "testOnly %s -- -z \"%s\""
           (scala/find-spec-name-with-package-current-buffer)
           (scala/find-nearest-spec-describe-current-buffer))))

;;; 現在のバッファからpackage名とテストクラス名を結合したものを返す
(defun scala/find-spec-name-with-package-current-buffer ()
  "Find spec name with package in current buffer."
  (interactive)
  (let* ((package-name (scala/find-package-name-current-buffer))
         (spec-name (scala/find-spec-name-current-buffer)))
    (if (string= package-name "")
        spec-name
      (format "%s.%s" package-name spec-name))))

;;; 現在のバッファからpackage名を抜き出す
(defun scala/find-package-name-current-buffer ()
  "Find package name in current buffer"
  (interactive)
  (let* ((matched-package ""))
    (save-excursion
      (when (re-search-backward "^package \\(.+\\)$" nil t)
        (setq matched-package (match-string 1))))
    matched-package))

;;; 現在のバッファからテストクラス名を抜き出す
(defun scala/find-spec-name-current-buffer ()
  "Find spec name of current buffer."
  (interactive)
  (let* ((matched-spec-name ""))
    (save-excursion
      (when (re-search-backward "^class \\([^ ]+Spec\\) " nil t)
        (setq matched-spec-name (match-string 1))))
    matched-spec-name))

;;; 現在カーソルがいるdescribe名を抜き出す
(defun scala/find-nearest-spec-describe-current-buffer ()
  (interactive)
  (let* ((matched-describe-name ""))
    (save-excursion
      (when (re-search-backward "\\bdescribe(\"\\([^\"]+\\\)\")" nil t)
        (setq matched-describe-name (match-string 1))))
    matched-describe-name))

後は好きなキーバインドを割り当てる。

(define-key scala-mode-map (kbd "C-c C-t") 'sbt/test-only-current-spec)
(define-key scala-mode-map (kbd "C-c t") 'sbt/test-only-current-describe)

また、僕は popwin で表示するのが好きなので、以下のようにsbt-modeのバッファはポップアップするように設定した。

(push '("\*sbt\*" :regexp t :height 0.5 :stick t) popwin:special-display-config)

技術的Tips

今回の実装は、「現在バッファの特定の文字列を抜き出す」ということが出来れば簡単に実装できる。やり方はこんな感じ。

(save-excursion
  (when (re-search-backward "^class \\([^ ]+Spec\\) " nil t)
    (setq matched-spec-name (match-string 1))))

まず、save-excursionでバッファの状態を保存しておく。これがないと文字列を探索した時にカーソルの位置が変わってしまう。

次にre-search-backwardを使って、現在カーソルより上の文字列を正規表現で探索する。抜き出したい部分を括弧を使って取り出せるようにしておく。

後はmatch-stringを使って、括弧で囲っていた部分を取り出す。1番目の括弧の中身を取り出したいので、1を指定している。

まとめ

今回はEmacsで現在編集している部分のScalaのテストを実行する方法について書いてみた。これによってテストクラス名とかをコピペする手間が省けるようになって便利。

Scalaスケーラブルプログラミングを再読した

最近ScalaやPlay Frameworkを触っていて、implicitを多用したコードや関数リテラルを使ったDSL的なコードなどが読めずに困ってきたので、もう一度Scalaスケーラブルプログラミングを読んだ。

Scalaスケーラブルプログラミング第3版

Scalaスケーラブルプログラミング第3版

前回読んだ時 はまだScalaの基本的な文法周りが面白かったけど、今回は8章 関数とクロージャ、21章 暗黙の型変換とパラメーター、26章 抽出子辺りが面白かった。

  • 8章 関数とクロージャや21章 暗黙の型変換とパラメーターを読むことで、フレームワークから提供されるDSLっぽいコードがどうやって作られているのかを理解することが出来た
  • 26章 抽出子を読むことで、val (a, b) = ("hoge", "fuga")でなぜaとbにそれぞれ代入できるのかや、パターンマッチでList(a, b, _*)でaとbになぜ束縛できるのかとかの中身を知ることが出来た

以前Scalaスケーラブルプログラミングを読んだ - $shibayu36->blog; で書いたとおり読みやすい本ではないのだけど、Scalaに慣れてきたら8章、21章あたりはきちっと読んでおくと良さそうと思った。あとは読めないコードが出てきたときにそれに関連する部分を読むとScalaに対する理解が深まりそう。

読書ノート

  • for (とfor {の違いはセミコロンの省略ができるかどうか 136
  • yieldはコレクションの再生成 138
  • アンダースコアはScalaではまったくわけのわからない値のためのプレースホルダとして使われる 142
  • 関数リテラルの省略形 157
  • 関数呼び出しの特殊な形態 164
    • 連続パラメーター
    • 名前付き引数
    • デフォルト値
  • 関数の部分適用を_でやるのって関数リテラルの省略形っぽい
  • http://www.ne.jp/asahi/hishidama/home/tech/scala/function.html#h_function_object
  • 複数引数の場合、_なしでカリー化できるのか? -> defで定義されたメソッドならできない
  • 引数が一つだけの場合、括弧を中括弧で書いても良いことになっているので、これを使って制御構造っぽく書くことができる 180
    • なので、withPrintWriter(file: File)(op: PrintWriter => Unit)みたいに、引数リストを分けることで、制御構造っぽくかける
  • withPrintWriter(file) { writer => … }
  • 名前渡しパラメータの役割 182
  • 空括弧メソッド 186
  • Scalaは定義のための名前空間を2つしか持たない 191
    • 値(フィールド、メソッド、パッケージ、シングルトンオブジェクト)
    • 型(クラス、トレイト)
  • Nothingはすべてのサブ型で、これがあることでthrowなどの返り値を定義できる 216
  • traitのextendsでミックスイン出来るクラスを制限できる 227
  • ミックスインは右から適用される 229
  • Scalaのimport文は 242
    • 任意の場所にかけて
    • パッケージもオブジェクトも参照できて
    • インポートメンバーの名前を変えたりできる
  • java.lang._、scala._、Predef._は暗黙にimportされている 244
  • 単なるprotected・privateは、自分と同じクラスの別インスタンスにアクセス可能だが、thisを付けると同一クラスであっても別インスタンスにはアクセスできなくなる。 246 http://www.ne.jp/asahi/hishidama/home/tech/scala/class.html#h_access_modifiers
  • クラスとコンパニオンオブジェクトはそれぞれがそれぞれの非公開メンバーにアクセスできる 249
  • package.scalaにパッケージオブジェクトを定義し、そのパッケージ全体からアクセス可能にできる 249
  • パターンマッチのいろんな書き方 268 http://qiita.com/techno-tanoC/items/3dd3ed63d161c53f2d89
  • シールドクラスでは、同じファイルで定義されているサブクラス以外は新しいサブクラスを追加できない。そのため、パターンマッチがすべて行われているか検出できる 279
  • new RationalTrait { クラス定義と同様の書き方 } で、無名クラスを作ってインスタンスを作れる 386
  • lazyキーワードを使うと、その変数が利用される時に初めて計算される 390
  • implicitの規則 410
    • マーキングルール:implicitによって修飾された定義だけが暗黙の型変換に使われる
    • スコープルール:挿入される暗黙の型変換は、単一の識別子としてスコープ内にあるか、変換のソース型やターゲット型として対応付けられていなければならない
    • 1度に1回ルール:暗黙の型変換は1度しか挿入されない
    • 明示的変換優先ルール:書かれたままの状態でコードが型チェックをパスするときには、暗黙の型変換は行われない
  • Map等に使われる->はscala.PredefのArrowAssocクラスのメソッドで、暗黙の型変換が使われている 416
  • 暗黙のパラメータでのimplicitの利用は、個別のパラメーターではなくパラメーターリスト全体に適用される 419
  • 暗黙の型変換がおかしかったら、デバッグのために型変換を明示的に書いてみると良い 427
  • 21章のimplicitの説明は必読。いろんなモジュールでimplicitを使いまくってるので、そのあたりのモジュールを読むのにこの辺の知識は必要そう
  • unapplyメソッドが定義されているオブジェクトは抽出子 533
  • unapplyはある一つのものを分解するメソッド
  • 26章の抽出子を学ぶと、val (a, b) = (“hoge”, “fuga”)でどうして変数束縛出来るのかとか、パターンマッチでなぜオブジェクトの中身を変数に束縛できるのかとかが理解できる