I'm a very beginner of Rustaceans. Let's write FizzBuzz
Tags: beginners, rust
Published: 2020-01-20 13:49:02

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](#what-is-fizzbuzz)
- [Create Project](#create-project)
- [Unit Test: Numbers](#unit-test-numbers)
- [Unit Test: FizzBuzz](#unit-test-fizzbuzz)
- [Unit Test: Fizz and Buzz](#unit-test-fizz-and-buzz)
- [Integration Test: Hello World!](#integration-test-hello-world)
- [Integration Test: FizzBuzz](#integration-test-fizzbuzz)
- [Build and Run](#build-and-run)

# Introduction

#### What is FizzBuzz?

1. display numbers from 1 to 100
1. display "Fizz" when numbers are multiples of 3
1. display "Buzz" when numbers are multiples of 5
1. 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.
```bash
$ 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

```bash
$ 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

```bash
$ 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`

```rust
fn main() {
    println!("Hello, world!");
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn fizzbuzz_numbers() {
        assert_eq!(1, fizzbuzz(1));
    }
}
```

Let's confirm it.
```bash
$ 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.

```rust
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

```rust
// --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.
```rust
    #[test]
    fn fizzbuzz_multipe_15() {
        for n in 1..11 {
            assert_eq!("FizzBuzz", fizzbuzz(n*15));
        }
    }
```

you can see **red**.

```bash
$ 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

```rust
// --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.
```rust
    #[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.

```rust
#[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`.

```bash
$ 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**.

```rust
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**

```bash
$ 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.

```bash
mkdir tests
touch tests/test.rs
```

Open `Cargo.toml` then add below 3 lines
```toml
# --snip--


[dev-dependencies]
assert_cmd = "0.10"
predicates = "1"
```

## Write first an integration test.

Open `tests/test.rs` then add below lines.

```rust
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.

```bash
$ cargo test
```

After downloading several modules, you can see result of test as below

```bash
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`.

```rust
#[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.

```rust
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.

```toml
# --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.

```rust
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`.

```rust
// --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.

```rust
#[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

```bash
$ 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