いよいよ「テスト駆動開発」に入っていこう。
まずは、P4の最初のテストだ。おもむろに存在しないDollerクラスをnewして、
ありもしないtimesメソッドやamountフィールドを参照している。テストファーストなのだ。
・・・Rustでデータ型を表現するのはどうするんだったかな・・・(本をめくる)・・・
構造体だ。ざっと眺めて要点を飲み込む。
こんな感じでいいんだろうか。
fn main() {
println!("Hello, world!");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multiplication() {
let five = Doller { amount: 5 };
five.times(2);
assert_eq!(10, five.amount);
}
}
まず、Rustにおいて関数のテストは同じファイル内に書く。
単に#[test]
と属性を付ければ、それがテストだと見なされる。
ただし、慣例としてtestsというモジュールを作り、その中に入れる。
そこに#[cfg(test)]
と属性を付けておけば、テストはtest実行以外の場合ではビルド対象から外される。
で、VScodeでテストを走らせるにはどうしたら良いのか。
コードで#[test]
と書くと、その下に Run test という表示が出て、押すとテストが走る。
これはRust(rls)拡張機能がやっているらしい。
押すと、ターミナルが開き、こんな感じのログが流れた。
> Executing task: cargo test -- --nocapture test_multiplication <
Compiling money v0.1.0 (/Users/tambara/study/tdd_rust/money)
error[E0422]: cannot find struct, variant or union type `Doller` in this scope
--> src/main.rs:11:20
|
11 | let five = Doller { amount: 5 };
| ^^^^^^ not found in this scope
warning: unused import: `super::*`
--> src/main.rs:7:9
|
7 | use super::*;
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error: aborting due to previous error
For more information about this error, try `rustc --explain E0422`.
error: could not compile `money`.
To learn more, run the command again with --verbose.
The terminal process terminated with exit code: 101
Terminal will be reused by tasks, press any key to close it.
要するに cargo で特定のテストだけ実行しているわけだ。
テストを全部流したかったらどうするのか・・・メニューのTerminal - Run Taskから出来るみたい。
> Executing task: cargo test <
Compiling money v0.1.0 (/Users/tambara/study/tdd_rust/money)
error[E0422]: cannot find struct, variant or union type `Doller` in this scope
--> src/main.rs:11:20
|
11 | let five = Doller { amount: 5 };
| ^^^^^^ not found in this scope
warning: unused import: `super::*`
--> src/main.rs:7:9
|
7 | use super::*;
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error: aborting due to previous error
For more information about this error, try `rustc --explain E0422`.
error: could not compile `money`.
To learn more, run the command again with --verbose.
The terminal process terminated with exit code: 101
Terminal will be reused by tasks, press any key to close it.
確かにcargo testをやってる。
で、エラーの方は、Dollerってなんやねんと言われている。それはそうだ。
P6 でやっているように、空の実装を足してやる。
fn main() {}
pub struct Doller {
pub amount: i32,
}
impl Doller {
pub fn times(multiplier: i32) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multiplication() {
let five = Doller { amount: 5 };
five.times(2);
assert_eq!(10, five.amount);
}
}
良さそうに思えるけど、まだコンパイルが通らない。
error[E0599]: no method named `times` found for struct `Doller` in the current scope
--> src/main.rs:19:14
|
4 | pub struct Doller {
| ----------------- method `times` not found for this
...
19 | five.times(2);
| -----^^^^^
| | |
| | this is an associated function, not a method
| help: use associated function syntax instead: `Doller::times`
|
= note: found the following associated functions; to be used as methods, functions must have a `self` parameter
note: the candidate is defined in an impl for the type `Doller`
--> src/main.rs:9:5
|
9 | pub fn times(multiplier: i32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Rustはコンパイルを通すのが凄く難しい言語だが、
エラーメッセージが驚くほど親切だ。
Dollerにはtimesなんてメソッドはないぞと怒られているんだけども、
「お前、selfをパラメータに入れ忘れてるんじゃね?」と教えてくれている。
そう、Rustで構造体のメソッドを定義したいのなら、1つ目のメソッドはselfにしないといけない。
こうだ。
impl Doller {
pub fn times(&mut self, multiplier: i32) {}
}
エラーが変わった。
--> src/main.rs:19:9
|
18 | let five = Doller { amount: 5 };
| ---- help: consider changing this to be mutable: `mut five`
19 | five.times(2);
| ^^^^ cannot borrow as mutable
fiveがmutableじゃないとダメと怒られた。それはそうだ。
fn test_multiplication() {
let mut five = Doller { amount: 5 };
five.times(2);
assert_eq!(10, five.amount);
}
こうなってないといけない。これでエラーが消えた。
> Executing task: cargo test <
warning: unused variable: `multiplier`
--> src/main.rs:9:29
|
9 | pub fn times(&mut self, multiplier: i32) {}
| ^^^^^^^^^^ help: consider prefixing with an underscore: `_multiplier`
|
= note: `#[warn(unused_variables)]` on by default
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running target/debug/deps/money-043e3ba9eebcce93
running 1 test
test tests::test_multiplication ... FAILED
failures:
---- tests::test_multiplication stdout ----
thread 'tests::test_multiplication' panicked at 'assertion failed: `(left == right)`
left: `10`,
right: `5`', src/main.rs:20:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::test_multiplication
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
error: test failed, to rerun pass '--bin money'
The terminal process terminated with exit code: 101
うん。ちゃんとテスト結果も出ている。FAILEDだ。
しかし、グリーンなのかレッドなのかわかりにくいな・・・。
では、グリーンにしておこうか。
fn main() {}
pub struct Doller {
pub amount: i32,
}
impl Doller {
pub fn times(&mut self, multiplier: i32) {
self.amount *= multiplier;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multiplication() {
let mut five = Doller { amount: 5 };
five.times(2);
assert_eq!(10, five.amount);
}
}
> Executing task: cargo test -- --nocapture test_multiplication <
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running target/debug/deps/money-043e3ba9eebcce93
running 1 test
test tests::test_multiplication ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
テストが通った。素晴らしい。これで、1章は終わりだ。