tkob
assertThat(account.getBalance(), equalTo(100));
マッチャーを使った文は英語に近い形で左から右へと自然に読み下せます。つまりこの例は「assert that the account balance is equal to 100(「口座の残高が100に等しいことを確認せよ」)」と読めます。
実践JUnit
assertThat(actual, is(expected));
(前略) JUnit 4のスタイルではMatcher APIを活用することで、検証コードが自然言語の表記に近く、「assert that actual is expected」(実測値が期待値であると表明する)と読むことができます。
JUnit実践入門
- assertとThatの間にスペースがないのはいいの?
- Tが大文字なのは?
- 何故そこが括弧でくくられているの?
- isの前にカンマがあるのは?
- ノイズが多い
Assert that actual is expected.
- 内部DSLだから Java 文法の制約を受ける
- 単体テストのための外部DSLを作ろう
https://github.com/tkob/yokohamaunit/
- 単体テストのための独自の文法を持った言語・テスティングフレームワーク
- ソースから直接(javacを経由せず)バイトコードを生成する
- スタックトレースに適切な行番号を残すため
- JUnit のテストランナーで実行される
- エコシステムにのっかる
transformation
+---------+ +----------+ +----+ +----+
|Test Code|-parse-->|Parse Tree|->|AST1|->|AST2|
+---------+ (ANTLR) +----------+ +----| +----+
| code generation
v (Apache BCEL)
+---------+
test execution (JUnit) <--|Byte Code|
+---------+
テストのためのDSLには「テストを記述するDSLに属する部分」と「テスト対象に属する部分」がある
Assert that `actual' is `expected'.
Assert
,that
,is
,.
→ テスト記述に属するactual
,expected
→ テスト対象に属する
テスト対象に属する部分はDSLにしない
account
がテスト対象であるときにaccount.getBalance()
を対象言語から遠い仕方で表現してもしょうがない
# Test: This is my first test
Assert that `Integer.valueOf("123")` is 123.
- バッククォートの中は Groovy の式であり、実行時に評価される
Assert that `StringUtils.toSnakeCase(null)` throws
an instance of `NullPointerException`.
Assert that `Integer.valueOf("123")` throws nothing.
# Test: Test cases for `toSnakeCase`
Assert that `StringUtils.toSnakeCase(input)` is `expected`
for all input and expected in Table [1].
| input | expected |
| --------------- | ----------------- |
| "" | "" |
| "aaa" | "aaa" |
| "HelloWorld" | "hello_world" |
| "practiceJunit" | "practice_junit" |
| "practiceJUnit" | "practice_j_unit" |
| "hello_world" | "hello_world" |
[1]
- 各行に対してテストメソッドを生成する(各1ケースとカウントされる)
# Test: Combination in where-clause
Assert that `a * x * y` is 0
where a is 0
and x is any of 1, 2 or 3
and y is any of 4, 5, 6.
- これもそれぞれに対してテストメソッドを生成する
- where は組み合わせテストでないときも変数束縛に使える
- Pairwise の組み合わせにするオプションも用意した(未リリース)
# Test: AtomicInteger.incrementAndGet increments the content
## Setup
Let i be `new AtomicInteger(0)`.
## Exercise
Do `i.incrementAndGet()`.
## Verify
Assert `i.get()` is 1.
- Teardown もある
# Test: Interpret anchor expression
Assert `"cheer\n".multiply(3).denormalize()` is [Three cheers].
### Three cheers
```
cheer
cheer
cheer
```
# Test: Regular Expression
Assert that `"hello"` matches re `^h.*o$`.
Assert that `"hello"` matches regex `\Ah.*o\z`.
Assert that `" hello "` matches regexp `h.*o`.
Assert that `" hello "` does not match regexp `\Ah.*o\z`.
# Test: resource without "as" is a URL
Assert `url` is an instance of `URL`
where url = resource "blahblah".
# Test: resource as InputStream
Assert `instream` is an instance of `InputStream`
where instream = resource "blahblah" as `InputStream`.
- ほかにも
as `File`
,as `URI`
が使える
# Test: create a temp file
## Setup
Let temp = a temp file.
## Verify
Assert `temp.exists()` is true.
- 自動的に
deleteOnExit
が呼ばれる
# Test: Collections.unmodifiableMap preserves lookup
Assert `unmodifiableMap.get("answer")` is 42
where map is a stub of `Map` such that
method `get(java.lang.Object)` returns 42
and unmodifiableMap is `Collections.unmodifiableMap(map)`.
- Mockito を使うようなバイトコードにコンパイルされる
- テストのために外部DSLを用いると良いこと (内部DSLではできない)
- ノイズのないDSL
- 自由にリテラルを作れる
- ホスト言語の構造に左右されない文法(e.g.例外)
- たぶんテストの全ての側面が「自然に読み下せ」る必要はないかも
- 最初のモチベーションの一部を否定
- モックを自然言語っぽく書いてもすっきりしない
- 本当はどう書けると嬉しいのか?
- 例えば表は自然言語ではないが読みやすい
- 式言語(Groovy)を動的に評価させることの限界