I'm a very beginner of Rustanceans(rust programmer).
This article is for my understanding how to write rust.
Let's write a fizzbuzz with simple tdd process in r/rust
ToC
- Introduction
- Create Project
- Unit Test: Numbers
- Unit Test: FizzBuzz
- Unit Test: Fizz and Buzz
- Integration Test: Hello World!
- Integration Test: FizzBuzz
- Build and Run
Introduction
What is FizzBuzz?
- display numbers from 1 to 100
- display "Fizz" when numbers are multiples of 3
- display "Buzz" when numbers are multiples of 5
- display "FizzBuzz" when numbers are multiples of 15
1, 2, Fizz, 4, Buzz, Fizz, 7 ......14, FizzBuzz, 16, 17...100
So, I try to make command fizzbazz
like below.
$ fizzbazz 1
1
$ fizzbazz 3
1, 2, Fizz
$ fizzbazz 15
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz
What is TDD?
Test Driven Development.
You write test first, then you see testing error. (I like to say it is "see red")
You modify your code, then you see testing success. (I like to say it is "see green")
Rust Versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)
$ rustup --version
rustup 1.21.1 (7832b2ebe 2019-12-20)
$ cargo --version
cargo 1.40.0 (bc8e4c8be 2019-11-22)
Create project
$ cd $rust_projects
$ cargo new fizzbuzz
$ cd fizzbuzz
Unit Test: Numbers
First test
The cargo new
command generated "hello world".
Let's add test code at bottom of main.rs
as below.
src/main.rs
fn main() {
println!("Hello, world!");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fizzbuzz_numbers() {
assert_eq!(1, fizzbuzz(1));
}
}
Let's confirm it.
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/fizzbuzz`
Hello, world!
$ cargo test
Compiling fizzbuzz v0.1.0 (/home/ryo/workspace/rust/fizzbuzz)
error[E0425]: cannot find function `fizzbuzz` in this scope
--> src/main.rs:9:23
|
9 | assert_eq!(1, fizzbuzz(1))
| ^^^^^^^^ not found in this scope
error: aborting due to previous error
# --snip--
The cargo run
command compiles and runs the rust code.
The cargo test
command compiles and runs the test code.
Let's implement fizzbuzz
function.
Add fn fizzbuzz
as below.
fn main() {
println!("Hello, world!");
}
pub fn fizzbuzz(n: u32) -> u32 {
n
}
// --snip--
Run cargo test
again you can see green.
Update unit test
Update test code as below
// --snip--
#[test]
fn fizzbuzz_numbers() {
for n in 1..101 {
assert_eq!(n, fizzbuzz(n));
}
}
// --snip--
Unit Test: FizzBuzz
let's see red by updating feature.
update test code as below.
#[test]
fn fizzbuzz_multipe_15() {
for n in 1..11 {
assert_eq!("FizzBuzz", fizzbuzz(n*15));
}
}
you can see red.
$ cargo test
Compiling fizzbuzz v0.1.0 (/home/ryo/workspace/rust/fizzbuzz)
error[E0277]: can't compare `&str` with `u32`
--> src/main.rs:24:13
|
24 | assert_eq!("FizzBuzz", fizzbuzz(n*15));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no implementation for `&str == u32`
|
let's make it green by updating fn fizzbazz
as below
// --snip--
pub fn fizzbuzz(n: u32) -> String {
if n%15 == 0 {
String::from("FizzBuzz")
} else {
n.to_string()
}
}
// --snip--
But still you see red because of test fizzbuzz_numbers
.
udpate it as below.
#[test]
fn fizzbuzz_numbers() {
for n in 1..101 {
if n%15 == 0 {
assert_ne!(n.to_string(), fizzbuzz(n));
} else {
assert_eq!(n.to_string(), fizzbuzz(n));
}
}
}
Unit Test: Fizz and Buzz
Add tests for the rest as below.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fizzbuzz_numbers() {
for n in 1..101 {
if n%15 == 0 || n%5 == 0 || n%3 == 0 {
assert_ne!(n.to_string(), fizzbuzz(n));
} else {
assert_eq!(n.to_string(), fizzbuzz(n));
}
}
}
#[test]
fn fizzbuzz_multipe_15() {
for n in 1..11 {
assert_eq!("FizzBuzz", fizzbuzz(n*15));
}
}
#[test]
fn fizzbuzz_multipe_5() {
for n in [1, 2, 4, 5, 7].iter() {
assert_eq!("Buzz", fizzbuzz(n*5));
}
}
#[test]
fn fizzbuzz_multipe_3() {
for n in [1, 2, 3, 4, 6].iter() {
assert_eq!("Fizz", fizzbuzz(n*3));
}
}
}
I cannot use 1..11
for multiple_5
and multiple_3
because this includes mupltipes of 15.
then, you can see red by running cargo test
.
$ cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running target/debug/deps/fizzbuzz-11b12517e9fe40f4
running 4 tests
test tests::fizzbuzz_multipe_15 ... ok
test tests::fizzbuzz_multipe_3 ... FAILED
test tests::fizzbuzz_multipe_5 ... FAILED
test tests::fizzbuzz_numbers ... FAILED
# --snip--
let's change it to green.
pub fn fizzbuzz(n: i32) -> String {
match n%15 {
0 => String::from("FizzBuzz"),
5|10 => String::from("Buzz"),
3|6|9|12 => String::from("Fizz"),
_ => n.to_string()
}
}
now you can see green
$ cargo test
Compiling fizzbuzz v0.1.0 (/home/ryo/workspace/rust/fizzbuzz)
Finished test [unoptimized + debuginfo] target(s) in 0.36s
Running target/debug/deps/fizzbuzz-11b12517e9fe40f4
running 4 tests
test tests::fizzbuzz_multipe_5 ... ok
test tests::fizzbuzz_multipe_3 ... ok
test tests::fizzbuzz_multipe_15 ... ok
test tests::fizzbuzz_numbers ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Integration Test: Hello World!
Now you have nice function fizzbuzz
, but result of cargo run
is still "Hello, World!". it is time to change it.
First, let's write integration tests.
You still have "Hello, World".
Let's test it as first step.
Preparation
Create directory for integration tests.
mkdir tests
touch tests/test.rs
Open Cargo.toml
then add below 3 lines
# --snip--
[dev-dependencies]
assert_cmd = "0.10"
predicates = "1"
Write first an integration test.
Open tests/test.rs
then add below lines.
use std::process::Command;
use assert_cmd::prelude::*;
use predicates::prelude::*;
#[test]
fn first_test() -> Result<(), Box<std::error::Error>> {
let mut cmd = Command::cargo_bin("fizzbuzz")?;
cmd.assert()
.success()
.stdout(predicate::str::contains("Hello, world!"));
Ok(())
}
Then run below command.
$ cargo test
After downloading several modules, you can see result of test as below
running 1 test
test first_test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Integration Test: FizzBuzz
Open tests/test.rs
then change the test name from first_test
to input_1
.
#[test]
fn input_1() -> Result<(), Box<std::error::Error>> {
let mut cmd = Command::cargo_bin("fizzbuzz")?;
cmd.arg("1")
cmd.assert();
.success()
.stdout(predicate::str::ends_with("1\n"));
Ok(())
}
Of course, you can see red.
Modify src/main.rs
as below.
use structopt::StructOpt;
#[derive(StructOpt)]
struct Cli {
number: String,
}
fn main() {
let args = Cli::from_args();
let input: i32 = match &args.number.trim().parse() {
Ok(input) => *input,
Err(_) => {
println!("fail");
0
}
};
for n in 0..input {
println!("{}", fizzbuzz(n+1));
};
}
And open Cargo.toml
then add structopt = "0.2.10"
in [dependencies]
section.
# --snip--
[dependencies]
structopt = "0.2.10"
# --snip--
Run test! You can see green.
Next add another test, input_3
. it will return "1, 2".
I want to add common test implementation in tests/test.rs
as below.
use std::process::Command;
use assert_cmd::prelude::*;
use predicates::prelude::*;
#[test]
fn input_1() -> Result<(), Box<dyn std::error::Error>> {
test_impl("1", "1\n")
}
#[test]
fn input_3() -> Result<(), Box<dyn std::error::Error>> {
test_impl("2", "1, 2\n")
}
fn test_impl(input: &str, output: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("fizzbuzz")?;
cmd.arg(input);
cmd.assert()
.success()
.stdout(predicate::str::ends_with(output));
Ok(())
}
Run test! You can see red.
Modify fn main
.
// --snip--
fn main() {
let args = Cli::from_args();
let input: i32 = match &args.number.trim().parse() {
Ok(input) => *input,
Err(_) => {
println!("fail");
0
}
};
for n in 0..input {
print!("{}", fizzbuzz(n+1));
if n+1 == input {
println!("");
} else {
print!(", ");
}
};
}
// --snip--
Run test! You can see green.
Next, add last tests.
#[test]
fn input_15() -> Result<(), Box<dyn std::error::Error>> {
test_impl("15", "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz\n")
}
#[test]
fn input_100() -> Result<(), Box<dyn std::error::Error>> {
test_impl("100", "94, Buzz, Fizz, 97, 98, Fizz, Buzz\n")
}
#[test]
fn input_null() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("fizzbuzz")?;
cmd.assert()
.failure()
.stderr(predicate::str::contains("The following required arguments were not provided"));
Ok(())
}
Run test! You can see green without any changes.
Build And Run
$ cargo build
$ ./target/debug/fizzbuzz 100
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26, Fizz, 28, 29, FizzBuzz, 31, 32, Fizz, 34, Buzz, Fizz, 37, 38, Fizz, Buzz, 41, Fizz, 43, 44, FizzBuzz, 46, 47, Fizz, 49, Buzz, Fizz, 52, 53, Fizz, Buzz, 56, Fizz, 58, 59, FizzBuzz, 61, 62, Fizz, 64, Buzz, Fizz, 67, 68, Fizz, Buzz, 71, Fizz, 73, 74, FizzBuzz, 76, 77, Fizz, 79, Buzz, Fizz, 82, 83, Fizz, Buzz, 86, Fizz, 88, 89, FizzBuzz, 91, 92, Fizz, 94, Buzz, Fizz, 97, 98, Fizz, Buzz
Congratulations!
Cover Photo by Bill Oxford on Unsplash
Top comments (0)