Инъекция зависимости для лося классов - PullRequest
9 голосов
/ 19 августа 2011

У меня есть класс Moose, который должен отправлять запросы типа Foo::Request.Мне нужно сделать эту зависимость доступной извне, чтобы я мог легко обмениваться реализацией запроса в тестах.Я придумал следующий атрибут:

has request_builder => (
    is => 'rw',
    isa => 'CodeRef',
    default => sub {
        sub { Foo::Request->new(@_) }
    }
);

А потом в коде:

my $self = shift;
my $request = $self->request_builder->(path => …);

И в тестах:

my $tested_class = …;
my $request = Test::MockObject->new;
$request->mock(…);
$tested_class->request_builder(sub { $request });

Есть ли более простой /более идиоматическое решение?

Ответы [ 4 ]

2 голосов
/ 20 августа 2011

Как насчет динамического применения роли в ваших тестах с Moose :: Util :: apply_all_roles?Я давно хотел этим воспользоваться, но пока не нашел оправдания.Вот как я думаю, это будет работать.

Сначала немного измените исходный атрибут:

package MyClientThing;
has request => (
    is      => 'rw',
    isa     => 'Foo::Request',
    builder => '_build_request',
);
sub _build_request { Foo::Request->new };
....

Затем создайте роль Test :: RequestBuilder:

package Test::RequestBuilder;
use Moose::Role;
use Test::Foo::Request; # this module could inherit from Foo::Request I guess?
sub _build_request { return Test::Foo::Request->new }; 

Тем временем в 't / my_client_thing.t'Вы могли бы написать что-то вроде этого:

use MyClientThing;
use Moose::Util qw( apply_all_roles );
use Test::More;

my $client  = MyClientThing->new;
apply_all_roles( $client, 'Test::RequestBuilder' );  

isa_ok $client->request, 'Test::Foo::Request';

См. Moose :: Manual :: Roles для получения дополнительной информации.

2 голосов
/ 21 августа 2011

Мое предложение, следующее за моделью в статье chromatic (комментарий Майка выше):

В вашем классе:

has request => (
    is => 'ro',
    isa => 'CodeRef',
    default => sub {
        Foo::Request->new(@_)
    }
);

В вашем тесте:

my $request = Test::MockObject->new;
$request->mock(…);
my $tested_class = MyClass->new(request => $request, ...);

делает именно то, что делает ваш код, со следующими уточнениями:

  1. сделать атрибут доступным только для чтения и установить его в конструкторе, если это возможно, для лучшей инкапсуляции.
  2. ваш атрибут request является готовым объектом; нет необходимости разыменовывать суб-ref
1 голос
/ 20 августа 2011

Рассмотрим этот подход:

В вашем классе Moose определите «абстрактный» метод с именем make_request. Затем определите две роли, которые реализуют make_request - одну, которая вызывает Foo::Request->new, а другую - Test::MockObject->new.

.

Пример:

Ваш основной класс и две роли:

package MainMooseClass;
use Moose;
...
# Note: this class requires a role that
# provides an implementation of 'make_request'


package MakeRequestWithFoo;
use Moose::Role;
use Foo::Request; # or require it
sub make_request { Foo::Request->new(...) }

package MakeRequestWithMock;
use Moose::Role;
use Test::MockRequest;  # or require it
sub make_request { Test::MockRequest->new(...) }

Если вы хотите проверить свой основной класс, смешайте его с ролью «MakeRequestWithMock»:

package TestVersionOfMainMooseClass;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithMock';

package main;
my $test_object = TestVersionOfMainMooseClass->new(...);

Если вы хотите использовать его с реализацией Foo make_request, смешайте его с ролью MakeRequestWithFoo.

Некоторые преимущества:

Вы будете загружать только те модули, которые вам нужны. Например, класс TestVersionOfMainMooseClass будет не загружать модуль Foo::Request.

Вы можете добавить данные, которые являются релевантными / требующимися вашей реализацией make_request, в качестве членов экземпляра вашего нового класса. Например, ваш оригинальный подход с использованием CODEREF может быть реализован с помощью этой роли:

package MakeRequestWithCodeRef;
use Moose::Role;
has request_builder => (
  is => 'rw',
  isa => 'CodeRef',
  required => 1,
);
sub make_request { my $self = shift; $self->request_builder->(@_) };

Для использования этого класса вам необходимо предоставить инициализатор для request_builder, например ::

package Example;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithCodeRef';

package main;
my $object = Example->new(request_builder => sub { ... });

В качестве заключительного замечания, роли, которые вы пишете, могут быть использованы с другими классами.

0 голосов
/ 05 мая 2015

Я знаю, что этот пост немного устарел, но для любого, кто обращается к этому вопросу, теперь запрашивающая сторона может использовать фреймворк, такой как Bread :: Board .

...