【问题标题】:How to mock a class method when unittesting in Raku在 Raku 中进行单元测试时如何模拟类方法
【发布时间】:2024-04-29 02:15:03
【问题描述】:

假设我有这样的课程:

class MyClass {
    method data-is-valid {
        return self!get-data ~~ m{^From};
    }

    method !get-data {
        return 'From Internet';
    }
}

!get-data 方法从 Internet 获取一些数据。

是否可以模拟该方法,使其返回我自己的硬编码数据,这样我就可以在不连接到 Internet 的情况下测试模块?

理想情况下,解决方案不应以任何方式修改类的定义。

注意:存在关于模块的单元测试子例程的similar question

【问题讨论】:

    标签: unit-testing methods mocking raku


    【解决方案1】:

    我会首先重构,将获取逻辑拉出到不同的对象,并让MyClass 依赖它:

    class Downloader {
        method get-data {
            return 'From Internet';
        }
    }
    
    class MyClass {
        has Downloader $.downloader .= new;
    
        method data-is-valid {
            return $!downloader.get-data ~~ m{^From};
        }
    }
    

    这是一个依赖倒置的例子,它是一种使代码可测试的有用技术(并且也倾向于使其更容易以其他方式发展)。

    通过此更改,现在可以使用Test::Mock 模块来模拟Downloader

    use Test;
    use Test::Mock;
    
    subtest 'Is valid when contains From' => {
        my $downloader = mocked Downloader, returning => {
            get-data => 'From: blah'
        };
        my $test = MyClass.new(:$downloader);
        ok $test.data-is-valid;
        check-mock $downloader,
            *.called('get-data', :1times);
    }
    
    subtest 'Is not valid when response does not contain From' => {
        my $downloader = mocked Downloader, returning => {
            get-data => 'To: blah'
        };
        my $test = MyClass.new(:$downloader);
        nok $test.data-is-valid;
        check-mock $downloader,
            *.called('get-data', :1times);
    }
    

    【讨论】:

      【解决方案2】:

      您可能想看看Test::Mock。摘自其概要:

      use Test;
      use Test::Mock;
      
      plan 2;
      
      class Foo {
          method lol() { 'rofl' }
          method wtf() { 'oh ffs' }
      }
      
      my $x = mocked(Foo);
      
      $x.lol();
      $x.lol();
      
      check-mock($x,
          *.called('lol', times => 2),
          *.never-called('wtf'),
      );
      

      【讨论】:

        【解决方案3】:

        嗨@julio 我建议你看看例程的 wrap 函数,这应该可以满足你的需要...https://docs.raku.org/language/functions#Routines ...这包括使用软;防止内联的编译指示

        【讨论】:

          【解决方案4】:

          也许最好的办法是重构代码(见乔纳森的回答)

          但是,如果由于某种原因你不能,还有其他选择:

          如果方法是公共的,您可以简单地创建一个子类并覆盖该方法。

          例如:

          use Test;
          
          class MyClass {
              method data-is-valid {
                  return self.get-data ~~ m{^From};
              }
          
              method get-data {
                  return 'From Internet';
              }
          }
          
          class MyClassTester is MyClass {
              method get-data {
                  return 'Foobar';
              }
          }
          
          my MyClassTester $class = MyClassTester.new;
          
          nok $class.data-is-valid, 'Mocked class has invalid data';
          
          done-testing;
          

          如果该方法是私有的,您可以使用wrap,如 p6steve 的回答中所述。但是,您需要自省才能修改私有方法。

          可以这样做:

          use Test;
          
          class MyClass {
              method data-is-valid {
                  return self!get-data ~~ m{^From};
              }
          
              method !get-data {
                  return 'From Internet';
              }
          }
          
          my $class = MyClass.new;
          
          my Method:D $get-data = $class.^find_private_method: 'get-data';
          
          $get-data.wrap: { 'Foobar' };
          
          nok $class.data-is-valid, 'Mocked class has invalid data';
          
          done-testing;
          

          【讨论】:

            最近更新 更多