【问题标题】:How to handle mocking roles in Moose?如何处理 Moose 中的模拟角色?
【发布时间】:2012-01-28 08:03:49
【问题描述】:

假设我有两个角色:Simple::Tax 和 Real::Tax。在测试情况下,我想使用 Simple::Tax,而在生产环境中,我想使用 Real::Tax。做这个的最好方式是什么?我的第一个想法是使用不同版本的new 方法来创建具有不同角色的对象:

#!/usr/bin/perl

use warnings;

{
    package Simple::Tax;
    use Moose::Role;

    requires 'price';

    sub calculate_tax {
        my $self = shift;
        return int($self->price * 0.05);
    }
}


{
    package A;
    use Moose;
    use Moose::Util qw( apply_all_roles );

    has price => ( is => "rw", isa => 'Int' ); #price in pennies

    sub new_with_simple_tax {
        my $class = shift;
        my $obj = $class->new(@_);
        apply_all_roles( $obj, "Simple::Tax" );
    }
}

my $o = A->new_with_simple_tax(price => 100);
print $o->calculate_tax, " cents\n";

我的第二个想法是在包体中使用 if 语句来使用不同的 with 语句:

#!/usr/bin/perl

use warnings;

{
    package Complex::Tax;
    use Moose::Role;

    requires 'price';

    sub calculate_tax {
        my $self = shift;
        #pretend this is more complex
        return int($self->price * 0.15);
    }
}

{
    package Simple::Tax;
    use Moose::Role;

    requires 'price';

    sub calculate_tax {
        my $self = shift;
        return int($self->price * 0.05);
    }
}


{
    package A;
    use Moose;

    has price => ( is => "rw", isa => 'Int' ); #price in pennies

    if ($ENV{TEST_A}) {
        with "Simple::Tax";
    } else {
        with "Complex::Tax";
    }
}

my $o = A->new(price => 100);
print $o->calculate_tax, " cents\n";

其中一个比另一个更好吗?它们中的任何一个有什么可怕的地方吗?有没有更好的方法我还没有想到。

【问题讨论】:

    标签: perl moose


    【解决方案1】:

    我的第一个建议是 MooseX::Traits,然后在创建对象时指定不同的角色:

    my $test = A->with_traits('Simple::Tax')->new(...);
    
    my $prod = A->with_traits('Complex::Tax')->new(...);
    

    但这为创建A 而不应用任何一个角色打开了大门。因此,进一步考虑,我认为您遇到了 X/Y 问题。如果 Simple::Tax 仅用于在测试环境中模拟 Complex::Tax,您可以做几件事来覆盖 Complex::Tax 实现。

    例如,您可以像这样定义 Simple::Tax:

    package Simple::Tax; 
    use Moose::Role;
    
    requires 'calculate_tax';
    around calculate_tax => sub { int($_[1]->price * 0.05) };
    

    然后始终使用 A compose Complex::Tax 并仅在测试期间对其应用 Simple::Tax(使用 apply_all_roles)。

    但是,如果您在生产中需要 Simple::Tax 和 Complex::Tax(而不仅仅是用于测试),您最好的选择是从组合关系 (does) 重构为委托关系 (has)。

     package TaxCalculator::API;
     use Moose::Role;
    
     requires qw(calculate_tax);
    
     package SimpleTax::Calculator;
     use Moose;
     with qw(TaxCalculator::API);
    
     sub calculate_tax { ... }
    
     package ComplexTax::Calculator;
     use Moose;
     with qw(TaxCalculator::API);
    
     sub calcuate_tax { ... }
    
    
     package A;
     use Moose;
    
     has tax_calculator => ( 
          does => 'TaxCalculator::API', 
          handles => 'TaxCalculator::API', 
          default => sub { ComplexTax::Calculator->new() },
     );
    

    如果你想覆盖它,你只需传入一个新的tax_calculator

    my $test = A->new(tax_calculator => SimpleTax::Calculator->new());
    
    my $prod = A->new(tax_calculator => ComplexTax::Calculator->new());
    

    因为handles 会将角色中的所有方法委托为新代理,这实际上与自己编写角色相同。

    【讨论】:

    • 那么,简单税务角色中的around calculate_tax 方法实际上从未调用对象上的calculate_tax 方法,对吧?我认为这是最干净的方式。
    • 没错,你完全覆盖了使用around替换它的父方法。
    猜你喜欢
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    • 2013-04-18
    • 2010-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    相关资源
    最近更新 更多