Be Framework Blog

テストが知らないこと

deleteの後にgetを書く。書いていて、これ要るかと思う。

二度尋ねる

$repo->delete($id);
$this->assertNull($repo->get($id));

saveの後にfind。updateの後にget。書いた本人も、なぜ二度目が要るのか説明できない。

「念のため」と言います。「確認のため」と言います。

voidの沈黙

delete($id): void. 削除した。何件消えたかは言わない。

SQLは知っています。affected_rows = 1。けれど戻り値がvoidである限り、その数は関数の中で生まれて消えます。

外にいるテストは、内側で何が起きたかを知る術を持ちません。だから別の証人を呼ぶ。getに「無い」と答えさせる。

間接証拠です。直接証拠ではない。

観察者は外にいる

テストは外から見ます。観察者です。

観察者には選択肢が二つあります。

  • 内部の手応えを取り出して見えるようにする——カプセル化を壊す
  • 別の方法で確かめる——テストが二重になる

TDDが小さく抱えてきた矛盾。テストが外にいる限り、内で起きたことは伝聞でしか書けない。

内側に証拠を持つ

Be Frameworkでは、削除は変容の一段になります。

#[Be([ArticleDeleted::class])]
final readonly class ArticleDeleteRequest
{
    public function __construct(
        #[Input] public string $id,
    ) {}
}

final readonly class ArticleDeleted
{
    public function __construct(
        #[Input] string $id,
        #[Inject] ArticleRepository $repo,
    ) {
        $affectedRows = $repo->delete($id);
        $this->been->with(new DeletedContext(id: $id, affectedRows: $affectedRows->count));
    }
}

Ray.MediaQueryはインターフェースの戻り値をintにするだけで、affected_rowsを直接届けます。

外には晒しません。中で、affectedRows: 1という事実を$beenに書き留める。

$result = $becoming(new ArticleDeleteRequest($id));

$this->assertInstanceOf(ArticleDeleted::class, $result);
$this->assertSame(1, $result->been->last()->affectedRows);

getは呼びません。オブジェクト自身が、自分がArticleDeletedである根拠を持っている。

記録ではなく条件

ログは普通、起きたことを後から書き留めます。

$beenは違います。証跡がなければArticleDeletedは成立しない。affectedRows: 1は事後の記録ではなく、存在の条件です。

外から見るかぎり、二度尋ねるしかない。