Rust で TDD Boot Camp （課題1:「閉区間」）struct, impl, テストコードの基礎


以下ページの転載になります。ご了承ください。

[Rustを0から学んでみた〜Part.1〜 struct, impl, テストコードの基礎編（TDD Boot Camp 課題1「閉区間」）](https://hongo.dev/blog/learn-rust-part1-struct-impl-test)

---

TDDBCの課題をRustで実施し、言語仕様を学んでいこうと思っています。

- [TDD Boot Camp(TDDBC) - TDDBC仙台03/課題](http://devtesting.jp/tddbc/?TDDBC%E4%BB%99%E5%8F%B003%2F%E8%AA%B2%E9%A1%8C)
- [TDD Boot Camp(TDDBC) - TDDBC仙台03/課題用語集](http://devtesting.jp/tddbc/?TDDBC%E4%BB%99%E5%8F%B003%2F%E8%AA%B2%E9%A1%8C%E7%94%A8%E8%AA%9E%E9%9B%86)

---

[<u>ひとことで言うとこんな記事</u>]

以下2点を学ぶ事ができます

- 他の言語でいうClass的なものを実現する方法（struct, impl）
- RustでのTest Code・Unit Testの基礎

---


[<u>こんなひとにおすすめ</u>]

- 「これからRustを勉強していきます！」という方
- 「Rustってclassないの？！」と驚いている方
- 初歩的なモジュールとそれに対するテストコードの書き方を知りたい方


---

[<u>目次</u>]

- 準備：cargo new コマンドのみ
- 課題：整数の閉区間（Closed Range）
- 学んだこと0：前提
- __学んだこと1：Rustでのオブジェクトの表現 〜オブジェクトはデータと振る舞いを含む〜__
- __学んだこと2：Rustのテストコード基礎__
- 今回の実装の Pull Request
- 次回：TDDBC「課題２：開区間」 実施予定

---

## 準備：cargo new コマンドのみ

インストール等の記事は、以下記事をご覧ください。


[![ learn-rust-part0-introduction-and-setup-thumb](//images.ctfassets.net/yyxwl1h0rr37/uJae4vr7kPYQhjYxuc819/af0b311d6e78289292d01b52c6a29b16/Rust.001.png?w=720&q=20)Rustを0から学んでみた 〜Part.0 準備/概要 編〜](https://hongo.dev/blog/learn-rust-part0-introduction-and-setup "Rustを0から学んでみた 〜Part.0 準備/概要 編〜")


準備

```bash
cargo new tddbc-rust-practice --bin
```



## 課題： 整数の閉区間（Closed Range）

今回ご紹介する課題の内容です。

### 課題1-1

- 下端点と上端点を与えて閉区間を生成
- 閉区間から下端、上端の取得
- 閉区間から文字列の取得

### 課題1-2

- 閉区間が指定した整数を含むか(contains)判定

### 課題1-3

- 閉区間が別の閉区間と等しいか(equals)判定
- 閉区間が別の閉区間と接続しているか(isConnectedTo)判定


## 学んだこと0：前提

### mod（モジュール）の準備方法

pushした私のリポジトリのtreeです。

```shell-session
.
├── Cargo.toml
├── README.md
├── src
│   ├── lib.rs       ←コレ
│   └── main.rs
└── tests
    └── lib.rs
```


`src/lib.rs`に準備することでいい感じになるそうです。


### 基本 Private （例外アリ）

> By default, everything in Rust is private, with two exceptions: Associated items in a pub Trait are public by default; Enum variants in a pub enum are also public by default. When an item is declared as pub, it can be thought of as being accessible to the outside world. For example:
> 
> ```rust
> // Declare a private struct
> struct Foo;
> 
> // Declare a public struct with a private field
> pub struct Bar {
>    field: i32,
> }
>
> // Declare a public enum with two public variants
> pub enum State {
>     PubliclyAccessibleState,
>     PubliclyAccessibleState2,
> }
> ```
> 
> 引用：[Visibility and Privacy - The Rust Reference](https://doc.rust-lang.org/reference/visibility-and-privacy.html)


`trait`と`enum`に関しては例外的にpublicがデフォルトだそうです。


## 学んだこと1：Rustでのオブジェクトの表現〜オブジェクトはデータと振る舞いを含む〜

Rustでもオブジェクト指向ができます。


> 参考：[オブジェクト指向言語の特徴 - The Rust Programming Language](https://doc.rust-jp.rs/book/second-edition/ch17-01-what-is-oo.html)



実際に書いてみたソースコードはこちらになります。（GitHubのページへ飛びます）


GitHub - h0ng0yut0 [tddbc-rust-practice/lib.rs](https://github.com/h0ng0yut0/tddbc-rust-practice/blob/2528d9393badb1b6eb9cb13e9a4503ecf30260da/src/lib.rs)


### struct（構造体）

メンバ変数定義のようなものですね。「閉区間は下限と上限を持つ」という例が以下です
。


```rust
// lib.rs

  #[derive(Clone)]
  pub struct ClosedRange {
    pub lower: i8,
    pub upper: i8,
  }
```


上記「基本 Private （例外アリ）」で Private Public の話をしましたが、 __structのメンバもデフォルトpublicでよくね？__ とか思うんだけどどうなんだろう。。。


また、`#[derive(Clone)]`を記述することにより、同じ値を持った構造体をcloneすることができる。（cloneしないで普通に代入すると、Rustのメモリ管理方法によりスコープ外とみなされますからね〜）


```rust
// main.rs
let sample_closed_range = closed_range::ClosedRange::new(1, 5);
let sample_closed_range_same = sample_closed_range.clone();
```


### impl（実装）

structの持つ振る舞いを定義しています。

```rust
#[derive(Clone)]
pub struct ClosedRange {
  pub lower: i8,
  pub upper: i8,
}

impl ClosedRange {

  pub fn new(lower: i8, upper: i8) -> ClosedRange {
    match (lower, upper) {
      (lower, upper) if lower > upper => Err("下限と上限の値が不正です".to_owned()),
      _ => Ok(ClosedRange { lower: lower, upper: upper }),
    }
    .unwrap() // 生成時にErrの場合panic!させちゃう
  }
  
  pub fn to_string(&self) -> String {
    format!("[{},{}]", self.lower, self.upper)
  }
  
}
```


今回 `new` と `to_string` を定義させていただきました。impl自体に`pub`を書かずに、fnにつけるらしいです。


## 学んだこと2：Rustのテストコード基礎

実際にこの課題で書いてみたソースコードは以下になります。

GitHub - h0ng0yut0 [tddbc-rust-practice/lib.rs](https://github.com/h0ng0yut0/tddbc-rust-practice/blob/2528d9393badb1b6eb9cb13e9a4503ecf30260da/tests/lib.rs)

### テストコードの記述"場所" 2種類

#### 1. 同じファイル内にテストを記述する

```shell-session
.
├── Cargo.toml
├── README.md
└── src
　   ├── lib.rs       ←ココ（本機能と同じ場所）
　   └── main.rs
```

以下のように記述できます。

```rust
pub mod closed_range {
  
  // 実際に定義したstructやimplを実装
  
  #[cfg(test)] // 【1】テストコード記載場所を示す
  mod tests {
    use super::*; // 【2】外部モジュールで定義したものを利用できるようにする
  
    #[test] // 【3】テストをfnで記述していく
    fn test1() {

    }

    #[test] // 【3】テストをfnで記述していく
    fn test2 (){
    
    }
  }
}
```

利点としては、 __実装箇所と同じ場所にテストコードを記述することで、小さいサイズの実装であれば見通しが良い__ というところでしょうか。


はじめこちらの場所で実装していたのですが、本機能の実装を見通しよくするために、別の場所でテストコードを管理することとしました。

#### 2. tests ディレクトリ内に記述する

```shell-session
.
├── Cargo.toml
├── README.md
├── src
│   ├── lib.rs 
│   └── main.rs
└── tests
    └── lib.rs ← コレ
```

以下のように記述できます。

```rust
// 【1】各々ライブラリ等をインポートする必要がある
extern crate tddbc_rust_practice;
use tddbc_rust_practice::closed_range::ClosedRange;

#[test] //【2】テストをfnで記述していく
fn test1(){

}

#[test] //【2】テストをfnで記述していく
fn test2(){

}
```

1個目との違いは以下の点です。

- __必要なライブラリはインポートする必要がある__ （【1】）
- testsディレクトリ内にあるソースコードはすべてテストのためにあると認識されるので、__`#[cfg(test)]` の記載は不要__


利点としては、同じ場所にテストコードを書かないので __本実装の見通しが良いこと__ でしょうか？？

### テストコードのアサーションと書き方

#### assert_eq!(left, right)

`left == right` になる。以上。（equal）

#### assert_ne!(left, right)

`left != right` になる。以上。（not equal）

#### assert!(hoge)

hogeは`true`になる。以上。

#### #[should_panic] でエラー確認

今回の例でいうと、下限と上限の入力値がおかしい場合にエラーを引き起こすように設計しているので、テストコードは以下のようになります。


実装


```rust
  impl ClosedRange {
    pub fn new(lower: i8, upper: i8) -> ClosedRange {
      match (lower, upper) {
        (lower, upper) if lower > upper => Err("下限と上限の値が不正です".to_owned()),
        _ => Ok(ClosedRange { lower: lower, upper: upper }),
      }
      .unwrap() // 生成時にErrの場合panic!させちゃう
    }
 }
```

テストコード

```rust
#[test]
#[should_panic(expected = "下限と上限の値が不正です")]
fn new_error() {
  // 下限、上限の入力が不正な場合
  let lower = 5;
  let upper = 1;
  ClosedRange::new(lower, upper);
}
```

以上のように書けば、どんな内容の`panic!`かもテストできますね。

## 今回の実装の Pull Request

[課題1:閉区間 by h0ng0yut0 · Pull Request #1 · h0ng0yut0/tddbc-rust-practice](https://github.com/h0ng0yut0/tddbc-rust-practice/pull/1)


---


[<u>これから読んでみたい本</u>]
https://amzn.to/34pexMw


---

最後までお読みいただき、誠にありがとうございます。様々なことを学んでいきたいと思っていますので、内容やソースコード等のアドバイスいただけると幸いです。