Perl で RSpec の expect ... change 構文を模倣する

expect.pl

sub expect (&@) {
  my ($effector, $comparator, $state) = @_;
  my ($before, undef, $after) = map { $_->() } ($state, $effector, $state);
  cmp_ok $before, $comparator, $after;
}

sub make_changes (&) {
  my ($actual) = @_;
  ("!=", $actual);
}

sub make_no_changes (&) {
  my ($actual) = @_;
  ("==", $actual);
}

use Test::More;

my $state = 0;
expect { $state = 1 } make_changes { $state };

done_testing;

手抜きだけどそれらしく見える。

Perl でこういう状態に対するテストを書こうと思ったら次のように書くと思う:

package Person {
  use strict;
  use warnings;

  sub new {
    my ($class) = @_;
    bless +{
      is_slept => 0,
    };
  }

  sub is_asleep {
    my ($self) = @_;
    !!$self->{is_slept};
  }

  sub is_awake {
    my ($self) = @_;
    !$self->{is_slept};
  }

  sub sleep :method {
    my ($self) = @_;
    $self->{is_slept} = 1;
    $self;
  }

  sub awake {
    my ($self) = @_;
    $self->{is_slept} = 0;
    $self;
  }
}
use Test::Name::FromLine;
use Test::More;

my $person = Person->new;
ok $person->is_awake;
$person->sleep;
ok $person->is_asleep;

done_testing;

この書き方だと、$person->sleep;ok $person->is_asleep; にどういう関係があるか、ということが示されていない。

事前条件としてたとえば setup のようなところに抽出するのも適切ではない。テストしたいのは状態の変化であって、たとえば $person->is_asleep の返り値そのものではないから。

コードで式の関係を表現できるという点で RSpec の expect ... change はなかなか優れていると思うし、世の RSpec クローンは表面的な構文を模倣するだけで、こういったコンセプトを体現できていない。