【问题标题】:Testing private methods in Raku在 Raku 中测试私有方法
【发布时间】:2020-11-08 11:14:32
【问题描述】:

有没有办法在 Raku 中测试私有方法?

我知道理想情况下应该定义针对公共方法的测试,但是有没有办法以“错误的方式”做到这一点? :)

我最初考虑为继承自我想要测试的类的测试定义一个子类并在那里进行测试,但似乎没有继承私有方法。

然后我看到了'trusts' routine,但我不想在代码的任何类上引用测试类。

有没有类似通过自省来改变方法的“私有”属性?

调用/测试私有方法的最佳方式是什么?

【问题讨论】:

  • A search for "[raku] test private"。我将把你的问题标记为骗子,因为我希望有人可以提供@Kaiepi 答案的简短版本——希望只有几行代码——然后参考他们的 SO 作为详细解释一个人可能如何独立地得出这个解决方案。也许他们会回答你的问题;也许您可以通过阅读他们的 SO 并回答您自己的问题来得出简短的答案;也许其他人可以。 (我要睡觉了。:))
  • 谢谢@raiph!,我终于做到了。我创建了一个较短的版本,并添加了一个指向更详细说明的链接。
  • .oO(三个答案。有时(明显的)骗子真的很有用。)

标签: testing methods metaprogramming private raku


【解决方案1】:

这可以使用自省来完成。

考虑这是您要测试的类:

class SomeClass {
    has Int $!attribute;

    method set-value(Int $value) returns Nil {
        $!attribute = $value;
        return;
    }

    method get-value returns Int {
        return $!attribute;
    }

    # Private method
    method !increase-value-by(Int $extra) returns Nil {
        $!attribute += $extra;
        return;
    }
}

你可以像这样创建一个测试:

use Test;
use SomeClass;

plan 3;

my SomeClass $some-class = SomeClass.new;
my Method:D $increase-value = $some-class.^find_private_method: 'increase-value-by';

$some-class.set-value: 1;
$increase-value($some-class, 4);
is $some-class.get-value, 5, '1+4 = 5';

$increase-value($some-class, 5);
is $some-class.get-value, 10, '5+5 = 10';

my SomeClass $a-new-class = SomeClass.new;
$a-new-class.set-value: 0;
$increase-value($a-new-class, -1);
is $a-new-class.get-value, -1, '0+(-1) = -1; The method can be used on a new class';

done-testing;

您首先创建一个类的实例并使用^find_private_method 来获取它的私有Method。然后你可以通过传递一个类的实例作为第一个参数来调用Method

这个答案有更完整的解释:

How do you access private methods or attributes from outside the type they belong to?

【讨论】:

  • 您也可以将其作为方法调用。 $some-class.$increase-value(4)
【解决方案2】:

一杯新鲜的茶以及@Julio 和@JJ 的回答激发了以下灵感:

class SomeClass { method !private ($foo) { say $foo } }

use MONKEY-TYPING; augment class SomeClass { trusts GLOBAL }

my SomeClass $some-class = SomeClass.new;

$some-class!SomeClass::private(42); # 42

我的解决方案使用猴子输入来调整类。猴子打字通常是一件很狡猾的事情(因此是 LOUD pragma)。但它似乎是为这样的情况量身定做的。使用trusts GLOBAL 扩充课程,Bob 是你的叔叔。

Raku 需要SomeClass:: 资格才能使其工作。 (也许当 RakuAST 宏到达时,会有一个整洁的方法来解决这个问题。)我倾向于认为必须编写一个类限定是可以的,上面的解决方案比下面的解决方案要好得多,但是 YMMV ......

也许,而是:

use MONKEY-TYPING;
augment class SomeClass {
  multi method FALLBACK ($name where .starts-with('!!!'), |args) {
    .(self, |args) with $?CLASS.^find_private_method: $name.substr: 3
  }
}

然后:

$some-class.'!!!private'(42); # 42

我用过:

  • multiFALLBACK,并要求方法名字符串以!!!开头;

  • 常规方法调用(. 不是 !);

  • 通过名称的字符串版本调用方法。

multi!!! 是为了防止被测试的类已经声明了一个或多个 FALLBACK 方法。

添加!!! 的约定似乎或多或少可以保证测试代码永远不会干扰类的工作方式。 (特别是,如果有一些对不存在的私有方法的调用,并且存在现有的FALLBACK 处理,它会处理这种情况,而不会涉及这个猴子FALLBACK。)

它还应该提醒任何阅读测试代码的人发生了一些奇怪的事情,在极不可能的情况下确实开始发生奇怪的事情,要么是因为我错过了一些我看不到的东西,要么是因为一些 @ 987654338@ 类中的代码恰好使用相同的约定。

【讨论】:

  • 很好的答案!我认为您提出的第一种方法比第二种方法要干净得多。即使使用 MONKEY pragma,我仍然认为它是比使用内省 for this specific case(测试)更好的解决方案。此外,我关于内省的回答已经在另一个 SO 问题中以更详细的方式进行了介绍。所以我接受你的回答,它以不同的方式涵盖了这个问题,让我学习很酷的东西。谢谢!
  • “我认为你提出的第一种方法比第二种方法干净得多。” 我也是。我尝试了四种方法:自定义操作失败了,但让我想到了 mixin;那也是一场破产,但让我想到了augment + FALLBACK;这也是一种失败,但让我得到了终极的“啊哈!” augment + trusts GLOBAL。 (这导致了“需要资格”的皱纹,这足以让我将FALLBACK 作为后备。)Anyhoo,我很高兴你问了 Q,对我的回答(第一部分)感到满意,并且很高兴它显示了 TIMTOWDI 思维的价值。 :)
  • 我什至不知道你可以信任作用域,除了类。我不认为它在文档中,看到你想出这些东西我很困惑。脱帽(再次)
  • @jjmerelo 我培养了我内心的迷你苏格拉底(我知道我什么都不知道,除非我会死于别人对俏皮话的判断)。我从不相信任何我认为的东西,无论是私下还是其他。尤其不是因为阅读的东西。双倍尤其是单一来源。三重my interpretation 的东西。但我确实让它沉浸其中。我读到 [“trusts 是一个关键字,可以在包中使用以允许另一个包调用其私有方法”。 (谢谢 .@kaiepi。)它适用于 GLOBAL。 \o/
【解决方案3】:

除了使用自省之外,您还可以尝试使用外部助手角色来访问所有私有方法并直接调用它们。例如:

role Privateer {
    method test-private-method ( $method-name, |c  ) {
    self!"$method-name"(|c);
    }
}

class Privateed does Privateer {
    method !private() { return "⌣"  }
}

my $obj = Privateed.new;
say $obj.test-private-method( "private" );

这里的关键是按名称调用方法,您可以使用公共方法和私有方法来执行此操作,但对于私有方法,您需要使用它们的特殊语法self!

【讨论】:

  • .oO(IWBNI 否决者说他们为什么投反对票。)您的解决方案需要修改类的构造,@Julio 曾写道“我不想引用测试类在代码的任何类上。”我将其解释为反对您建议的解决方案。但正如我所见,我同意你回答的精神,即重新思考。加一杯茶,瞧。所以我赞成你的答案,因为如果没有你的答案,以及看待你所启发事物的友好方式,我怀疑我会想出我的答案。
  • 这是一个有趣的方法。我认为它比直接使用trusts 更干净。但它仍然有必须修改原始类的缺点。无论如何,这不是投反对票的理由。谢谢!
  • @raiph 没错,但“信任”也需要改变。我已经检查了“但是”和“确实”,它们不需要修改,但它们似乎无法访问隐私的东西。