如何使用RSpec测试`rand()`?

时间:2022-12-08 20:45:48

I have a method that does something like this:

我有一个类似这样的方法:

def some_method
  chance = rand(4)
  if chance == 1 do
    # logic here
  else
    # another logic here
  end
end

When I use RSpec to test this method, rand(4) inside it always generates 0. I am not testing rand method of Rails, I am testing my method. What is a common practice to test my method?

当我使用RSpec来测试这个方法时,它内部的rand(4)总是生成0.我没有测试Rails的rand方法,我正在测试我的方法。测试我方法的常见做法是什么?

4 个解决方案

#1


32  

There are two approaches I would consider:

我会考虑两种方法:

Approach 1:

方法1:

Use a known value of seed in srand( seed ) in a before :each block:

在before:each块中的srand(seed)中使用已知的种子值:

before :each do
  srand(67809)
end

This works across Ruby versions, and gives you control in case you want to cover particular combinations. I use this approach a lot - thinking about it, that's because the code I was testing uses rand() primarily as a data source, and only secondarily (if at all) for branching. Also it gets called a lot, so exerting call-by-call control over returned values would be counter-productive, I would end up shovelling in lots of test data that "looked random", probably generating it in the first place by calling rand()!

这适用于Ruby版本,并在您想要涵盖特定组合的情况下为您提供控制。我经常使用这种方法 - 考虑一下,这是因为我测试的代码主要使用rand()作为数据源,而仅使用其次(如果有的话)进行分支。它也被调用很多,所以对返回值进行逐个调用控制会适得其反,我最终会铲掉许多“看起来随机”的测试数据,可能首先通过调用rand来生成它()!

You may wish to call your method multiple times in at least one test scenario to ensure you have reasonable coverage of combinations.

您可能希望在至少一个测试场景中多次调用您的方法,以确保您有合理的组合覆盖率。

Approach 2:

方法2:

If you have branch points due to values output from rand() and your assertions are of the type "if it chooses X, then Y should happen", then it is also reasonable in the same test suite to mock out rand( n ) with something that returns the values you want to make assertions about:

如果由于rand()输出的值而导致分支点,并且你的断言属于“如果它选择X,那么Y应该发生”,那么在同一个测试套件中模拟rand(n)也是合理的返回你想要断言的值的东西:

 require 'mocha/setup'

 Kernel.expects(:rand).with(4).returns(1)
 # Now run your test of specific branch

In essence these are both "white box" test approaches, they both require you to know that your routine uses rand() internally.

从本质上讲,这些都是“白盒子”测试方法,它们都要求您知道您的例程在内部使用rand()。

A "black box" test is much harder - you would need to assert that behaviour is statistically OK, and you would also need to accept a very wide range of possibilities since valid random behaviour could cause phantom test failures.

“黑匣子”测试要困难得多 - 您需要断言行为在统计上是正常的,并且您还需要接受非常广泛的可能性,因为有效的随机行为可能会导致幻像测试失败。

#2


13  

I'd extract the random number generation:

我将提取随机数生成:

def chance
  rand(4)
end

def some_method
  if chance == 1 do
    # logic here
  else
    # another logic here
  end
end

And stub it:

它存根:

your_instance.stub(:chance) { 1 }

This doesn't tie your test to the implementation details of rand and if you ever decide to use another random number generator, your test doesn't break.

这不会将您的测试与rand的实现细节联系起来,如果您决定使用另一个随机数生成器,那么您的测试不会中断。

#3


12  

It seems that best idea is to use stub, instead of real rand. This way you would be able to test all values that you are interested in. As rand is defined in Kernel module you should stub it using:

似乎最好的想法是使用存根,而不是真正的兰德。这样你就可以测试你感兴趣的所有值。由于rand是在内核模块中定义的,你应该使用它来存根:

Kernel.stub(:rand).with(anything) { randomized_value }

In particular contexts you can define randomized_value with let method.

在特定的上下文中,您可以使用let方法定义randomized_value。

#4


6  

I found that just stubbing rand ie. using Kernel.stub(:rand) as answered by Samuil did not initially work. My code to be tested called rand directly e.g

我发现只是短暂的兰德即。使用Kernel.stub(:rand)作为Samuil的回答并没有最初的工作。我要测试的代码直接称为rand,例如

random_number = rand

random_number = rand

However, if I changed the code to

但是,如果我将代码更改为

random_number = Kernel.rand

random_number = Kernel.rand

then the stubbing worked.

然后茬工作了。

#1


32  

There are two approaches I would consider:

我会考虑两种方法:

Approach 1:

方法1:

Use a known value of seed in srand( seed ) in a before :each block:

在before:each块中的srand(seed)中使用已知的种子值:

before :each do
  srand(67809)
end

This works across Ruby versions, and gives you control in case you want to cover particular combinations. I use this approach a lot - thinking about it, that's because the code I was testing uses rand() primarily as a data source, and only secondarily (if at all) for branching. Also it gets called a lot, so exerting call-by-call control over returned values would be counter-productive, I would end up shovelling in lots of test data that "looked random", probably generating it in the first place by calling rand()!

这适用于Ruby版本,并在您想要涵盖特定组合的情况下为您提供控制。我经常使用这种方法 - 考虑一下,这是因为我测试的代码主要使用rand()作为数据源,而仅使用其次(如果有的话)进行分支。它也被调用很多,所以对返回值进行逐个调用控制会适得其反,我最终会铲掉许多“看起来随机”的测试数据,可能首先通过调用rand来生成它()!

You may wish to call your method multiple times in at least one test scenario to ensure you have reasonable coverage of combinations.

您可能希望在至少一个测试场景中多次调用您的方法,以确保您有合理的组合覆盖率。

Approach 2:

方法2:

If you have branch points due to values output from rand() and your assertions are of the type "if it chooses X, then Y should happen", then it is also reasonable in the same test suite to mock out rand( n ) with something that returns the values you want to make assertions about:

如果由于rand()输出的值而导致分支点,并且你的断言属于“如果它选择X,那么Y应该发生”,那么在同一个测试套件中模拟rand(n)也是合理的返回你想要断言的值的东西:

 require 'mocha/setup'

 Kernel.expects(:rand).with(4).returns(1)
 # Now run your test of specific branch

In essence these are both "white box" test approaches, they both require you to know that your routine uses rand() internally.

从本质上讲,这些都是“白盒子”测试方法,它们都要求您知道您的例程在内部使用rand()。

A "black box" test is much harder - you would need to assert that behaviour is statistically OK, and you would also need to accept a very wide range of possibilities since valid random behaviour could cause phantom test failures.

“黑匣子”测试要困难得多 - 您需要断言行为在统计上是正常的,并且您还需要接受非常广泛的可能性,因为有效的随机行为可能会导致幻像测试失败。

#2


13  

I'd extract the random number generation:

我将提取随机数生成:

def chance
  rand(4)
end

def some_method
  if chance == 1 do
    # logic here
  else
    # another logic here
  end
end

And stub it:

它存根:

your_instance.stub(:chance) { 1 }

This doesn't tie your test to the implementation details of rand and if you ever decide to use another random number generator, your test doesn't break.

这不会将您的测试与rand的实现细节联系起来,如果您决定使用另一个随机数生成器,那么您的测试不会中断。

#3


12  

It seems that best idea is to use stub, instead of real rand. This way you would be able to test all values that you are interested in. As rand is defined in Kernel module you should stub it using:

似乎最好的想法是使用存根,而不是真正的兰德。这样你就可以测试你感兴趣的所有值。由于rand是在内核模块中定义的,你应该使用它来存根:

Kernel.stub(:rand).with(anything) { randomized_value }

In particular contexts you can define randomized_value with let method.

在特定的上下文中,您可以使用let方法定义randomized_value。

#4


6  

I found that just stubbing rand ie. using Kernel.stub(:rand) as answered by Samuil did not initially work. My code to be tested called rand directly e.g

我发现只是短暂的兰德即。使用Kernel.stub(:rand)作为Samuil的回答并没有最初的工作。我要测试的代码直接称为rand,例如

random_number = rand

random_number = rand

However, if I changed the code to

但是,如果我将代码更改为

random_number = Kernel.rand

random_number = Kernel.rand

then the stubbing worked.

然后茬工作了。