お手伝いの yuki です。今日はクレートを使った小ネタです。claim という、アサーション関連の便利なマクロを提供するクレートがあります。意外と知られていない気もするので紹介しておこうと思います。
claim
assert_matches!
や Result の結果を判定するのに便利な assert_ok!
、Option の結果を判定するのに便利な assert_some!
、非同期処理周りで用いられる Poll の準備完了判定を行える assert_ready!
などの便利なアサーションに関連するマクロを提供するクレートです。シンプルながらも強力なクレートです。
下記から利用することができます。
今回はテスト用として説明しますが、もちろん本流の処理にも利用できます。リリースビルド時には挿入されない debug_*
で始まるマクロも、すべてのマクロに対して提供されています。
加えて #![no_std]
環境でも動きます。
使ってみる
下記で依存を追加できます。
$ cargo add --dev claim
ないしは、 Cargo.toml
に下記を追加します。
[dev-dependencies] claim = "0.5.0"
今回は適当なサンプルプロジェクトを立ち上げて試してみます。
$ cargo new claim-example --lib
適当に、ある団体のメンバー管理のデータをたくさん実装します。例示のためにいくつかのパターンを用意しておきます。
use std::collections::HashMap; #[derive(Debug, PartialEq, Eq)] pub struct Member { name: MemberName, } #[derive(Debug, PartialEq, Eq)] pub struct MemberName(String); impl TryFrom<String> for MemberName { type Error = MemberError; fn try_from(name: String) -> Result<Self, Self::Error> { if name.len() == 0 { Err(MemberError::NameShouldNotBeEmpty) } else if name.len() > 24 { Err(MemberError::NameShouldLessThanEqual24) } else { Ok(Self(name)) } } } #[derive(Debug)] pub enum MemberError { NameShouldNotBeEmpty, NameShouldLessThanEqual24, } pub struct Cache(HashMap<String, Member>);
今回検証しつつ claim の使い方を確認したいのは下記のパターンです。
Result
型に対する判定。- パターンマッチングが必要なものに対する判定。
Option
型に対する判定。
Result
型に対する判定
MemberName
には意図的にバリデーションチェックを付与してあります。空の文字列を入れようとした場合と、入れた文字列が24文字以下でなかった場合にエラーを返すようになっています。
たとえば空の文字列に対するテストを書いてみましょう。通常であれば次のように書かれるはずです。
#[test] fn raise_error_if_name_is_empty() { let name = "".to_string(); let result = MemberName::try_from(name); assert!(result.is_err()); }
これでも悪くはないのですが、claim を利用すると下記のように書くことができます。
#[test] fn raise_error_if_name_is_empty() { let name = "".to_string(); let result = MemberName::try_from(name); assert_err!(result); }
パターンマッチングが必要なものに対する判定
一番便利さが顕著な例は、返されるエラーの値を検証したい場合でしょうか。通常であれば matches!
マクロなどを利用して迂回しながら書く必要がありますが[*1]、assert_matches!
マクロ一つで済みます。
#[test] fn raise_specific_error_if_name_len_is_greater_than_equal_25() { let name = "a".repeat(25); let result = MemberName::try_from(name); assert_matches!(result, Err(MemberError::NameShouldBeLessThanEqual24)); }
便利ですね。
Option
型に対する判定
Result
型と同様に Option
型もテスト時にチェックしたいことは多いです。やはり同様に確認することができます。
assert_some!
を利用すると、結果が Some
かどうかを判定できます。None
も assert_none!
マクロで確認可能です。assert_some_eq!
で、Some
かつその結果が期待したものと一致するかを確認できます。
#[test] fn cache_works() { let dummy = HashMap::from_iter(vec![ ( "id-1".to_string(), Member { name: MemberName::try_from("Socrates".to_string()).unwrap(), }, ), ( "id-2".to_string(), Member { name: MemberName::try_from("Plato".to_string()).unwrap(), }, ), ( "id-3".to_string(), Member { name: MemberName::try_from("Aristotle".to_string()).unwrap(), }, ), ]); let cache = Cache(dummy); assert_some!(cache.0.get("id-1")); assert_some_eq!( cache.0.get("id-2"), &Member { name: MemberName::try_from("Plato".to_string()).unwrap(), } ); assert_none!(cache.0.get("id-4")); }
まとめ
手軽に入れられて記述量を減らせるのでいいマクロだと思います。みなさんもぜひ!
*1:ちなみにですが、assert_matches! マクロ自体は提案はされているものの現状 unstable な機能です: https://github.com/rust-lang/rust/issues/82775