3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ユニークビジョン株式会社Advent Calendar 2022

Day 16

Rustで単体テストでDBのカラムが増えても楽にする方法

Last updated at Posted at 2022-12-15

目的

単体テストを書く時に大変なのは、DBのテーブルにカラムが追加した時に、テーブルを作成している部分にすべて修正が必要な大量に直さないといけないからです。

良い感じの共通メソッドを作ってみます。

キャンペーンというテーブルがあって、色々なパターンでテストをしていきたいです。ここでは2つのテストを書いています。
それぞれでキャンペーンのレコードを作成してから、処理を実行しています。

#[derive(Default)]
pub struct Campaigns {
    pub uuid: Uuid,
    pub campaign_code: String,
    pub campaign_start_at: DateTime<Utc>,
    pub campaign_end_at: DateTime<Utc>,
}

#[tokio::test]
async fn test_get_campaign1() -> Result<(), Error> {
    let conn = setup().await?;
    let campaign = Campaigns {
        campaign_code: "test_campaign".to_owned(),
        campaign_start_at: Utc::now() - Duration::days(3),
        campaign_end_at: Utc::now() + Duration::days(3),
        ..Default::default()
    };
    Campaigns::insert_record_from(conn, campaign).await

    let res = execute1(Some(campaign.uuid)).await?;
    // ...
}

async fn test_get_campaign2() -> Result<(), Error> {
    let conn = setup().await?;
    let campaign = Campaigns {
        campaign_code: "test_campaign".to_owned(),
        campaign_start_at: Utc::now() - Duration::days(3),
        campaign_end_at: Utc::now() + Duration::days(3),
        ..Default::default()
    };
    Campaigns::insert_record_from(conn, campaign).await

    let res = execute2(Some(campaign.uuid)).await?;
    // ...
}

データをつくる共通関数

しかしこれでは新しいカラムが追加されると、それぞれ修正する必要がでてきます。
これを防ぐために、テスト用のレコードを生成するメソッドを用意すると良いです。

pub async fn make_campaign(conn: &PgClient) -> Result<Campaigns, Error> {
    let campaign_code = "test_campaign";
    let campaign = Campaigns {
        campaign_code: campaign_code.to_owned(),
        campaign_start_at: Utc::now() - Duration::days(3),
        campaign_end_at: Utc::now() + Duration::days(3),
        ..Default::default()
    };
    Campaigns::insert_record_from(conn, campaign).await
}

利用する側はこんな感じです。

#[tokio::test]
async fn test_get_campaign1() -> Result<(), Error> {
    let conn = setup().await?;
    let campaign = make_campaign(&conn).await?;

    let res = execute1(Some(campaign.uuid)).await?;
    // ...
}

#[tokio::test]
async fn test_get_campaign2() -> Result<(), Error> {
    let conn = setup().await?;
    let campaign = make_campaign(&conn).await?;

    let res = execute2(Some(campaign.uuid)).await?;
    // ...
}

新しいカラムの追加

Campaignsに新しい区分が追加してみます。
たとえばcampaign_kbnが追加されると以下のように関数を修正すれば良いです。

pub async fn make_campaign(conn: &PgClient) -> Result<Campaigns, Error> {
    let campaign_code = "test_campaign";
    let campaign = Campaigns {
        campaign_code: campaign_code.to_owned(),
        campaign_start_at: Utc::now() - Duration::days(3),
        campaign_end_at: Utc::now() + Duration::days(3),
        campaign_kbn: CampaignKbn::Normal.to_string(),
        ..Default::default()
    };
    Campaigns::insert_record_from(conn, campaign).await
}

しかし課題もあります。campaign_kbnを別の値でテストしたい場合この関数が使えません。
引数に追加すればできますが、呼び出し側も修正する必要があるので大変です。

ビルダーパターンを使う

ビルダーパターンを使って外から設定できるようにしました。マクロの細かい説明は後でするので、ここでは利用するとどうなるのかだけ見ていきます。

#[derive(Builder, Default)]
#[builder(setter(into))]
#[builder(default)]
#[builder(field(public))]
pub struct Campaigns {
    pub uuid: Uuid,
    pub campaign_code: String,
    pub campaign_kbn: String,
    pub campaign_start_at: DateTime<Utc>,
    pub campaign_end_at: DateTime<Utc>,
}

pub fn get_campaign_builder() -> CampaignsBuilder {
    CampaignsBuilder::default()
}

pub async fn make_campaign(
    conn: &PgClient,
    builder: &mut CampaignsBuilder,
) -> Result<Campaigns, SepError> {
    let target = builder.build().unwrap();
    Campaigns::insert_record_from(conn, target).await
}

これだと、最初にあったテストにふさわしいデフォルト値が設定できません。ビルダーを構築した時にあらかじめ設定しておきます。

pub fn get_campaign_builder() -> CampaignsBuilder {
    let mut builder = CampaignsBuilder::default();
    builder.campaign_code("test_campaign");
    builder.campaign_start_at(Utc::now() - Duration::days(3));
    builder.campaign_end_at(Utc::now() + Duration::days(3));
    builder.campaign_kbn(CampaignKbn::Normal.to_string());
    builder
}

利用するコードは以下のようになります。

#[tokio::test]
async fn test_get_campaign() -> Result<(), Error> {
    let conn = setup().await?;
    let mut builder = CampaignsBuilder::default();
    builder.campaign_kbn(CampaignKbn::Special.to_string());
    let campaign = make_campaign(&conn, &mut builder).await?;

    let res = execute(Some(campaign.uuid)).await?;
    // ...
}

これによりテストに最適な値を設定できつつ、呼ぶ側が調節できます。

ビルダーの詳細

derive_builderを利用しました。
利用しているところを再掲します。

#[derive(Builder, Default)]
#[builder(setter(into))]
#[builder(default)]
#[builder(field(public))]
pub struct Campaigns {
    pub uuid: Uuid,
    pub campaign_code: String,
    pub campaign_kbn: String,
    pub campaign_start_at: DateTime<Utc>,
    pub campaign_end_at: DateTime<Utc>,
}

詳細は上記ドキュメントを参考にしてください。
ここで使用した以外にも色々あります。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?