如何使用随机值验证单元测试

Reg*_*lva 11 python random unit-testing

如何使用随机值验证单元测试?我需要保证gen_age返回15到99之间的整数,但此代码不正确.

import random
import unittest


def gen_age():
    # generate integer between 15 and 99
    return random.randint(15, 99)


class AgeTest(unittest.TestCase):

    def setUp(self):
        self.a = gen_age()

    def test_choice(self):
        element = random.choice(self.a)
        self.assertTrue(element in self.a)

    def test_sample(self):
        for element in random.sample(self.a, 98):
            self.assertTrue(element in self.a)

if __name__ == '__main__':
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

use*_*716 16

测试类似行为的最佳方法是将种子设置为Random对象.

随机包提供Random类.随机实例具有与随机包相同的方法; random(),randint(),sample(),...此外,Random接受种子.向Random添加种子使其输出确定性.例如,

from random import Random
random = Random(666)
assert random.randint(0, 1000) == 467  # will never break
Run Code Online (Sandbox Code Playgroud)

因此,您希望将您的功能测试为

from random import Random
import unittest

random = Random()

def gen_age():
    # generate integer between 15 and 99
    return random.randint(15, 99)


class AgeTest(unittest.TestCase):

    def setUp(self):
        global random
        random = Random(666)

    def test_gen_age(self):
        self.assertEqual(gen_age(), 53)

if __name__ == '__main__':
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

请注意,如果您的测试不在同一个文件中,则需要使用随机修补unittest.mock.patch.这样的事情应该有效

from random import Random
from package.file import gen_age
import unittest


class AgeTest(unittest.TestCase):

    def setUp(self):
        self.random = Random(666)

    @patch('package.file.random')
    def test_gen_age(self, random):
        random.randint._mock_side_effect = self.random.randint
        self.assertEqual(gen_age(), 53)
Run Code Online (Sandbox Code Playgroud)


小智 5

我认为在 @user983716 的答案之上还有更多内容可以构建,因为:

  1. 该答案通过强制种子改变源代码的工作方式,而真正的源是不确定的
  2. 该测试可以做更多的事情来记录预期的行为,而该测试是晦涩难懂的,可能会让大多数人感到困惑

真正的愿望不是测试 的功能Random.randint,而是输出是否正确。这是@rufanov 建议的,尽管该解决方案可以改进。

在整个答案中,我们假设实现package.file与测试是分开的。

让我们从以下内容开始:

import unittest

from package.file import get_age

class AgeTest(unittest.TestCase):

    def test_gen_age_generates_a_number_between_15_and_99(self):
        age = gen_age()
        self.assertGreaterEqual(age, 15)
        self.assertLessEqual(age, 99)
Run Code Online (Sandbox Code Playgroud)

这是一个很好的开始测试,因为如果发生故障它会提供清晰的输出:

AssertionError: 14 not greater than or equal to 15
AssertionError: 100 not less than or equal to 99
Run Code Online (Sandbox Code Playgroud)

好的,但我们还想确保它是一个随机数,因此我们可以添加另一个测试来确保我们按randint预期获得它:

AssertionError: 14 not greater than or equal to 15
AssertionError: 100 not less than or equal to 99
Run Code Online (Sandbox Code Playgroud)

我们在这里做了两件重要的事情:

  • 对象random(来自定义的文件gen_age)被修补,以便我们可以对其进行测试,而不必依赖于实际的实现
  • 做出单个断言,确认向 提供了预期的 15 和 99 参数randint,以便给出正确的范围

可以编写额外的测试来断言实际返回值,确认给定的数字始终是随机的。这将提供该方法直接返回随机值的信心,因为可以想象该方法可以执行更多操作,甚至返回一些任意值,即使它在内部仍然进行调用randint

例如,假设某人进行了gen_age()如下更改:

@unittest.mock.patch('package.file.random')
def test_gen_age_gets_a_random_integer_in_the_range(self, mock_random):
    gen_age()
    mock_random.randint.assert_called_with(15, 99)
Run Code Online (Sandbox Code Playgroud)

呃哦,现在只有那些randint返回 99 的情况我们的第一个测试才会失败,而第二个测试仍然会通过。这是一个等待发生的生产错误......

那么,确认结果的简单但有效的方法可能如下:

def gen_age():
    age = random.randint(15, 99) # still doing what we're looking for
    return age + 1               # but now we get a result of 16-100
Run Code Online (Sandbox Code Playgroud)

不过,这里还有最后一个缺陷......如果返回的值更改为:

@unittest.mock.patch('package.file.random')
def test_returns_age_as_generated(mock_random):
    mock_random.return_value = 27
    age = get_age()
    self.assertEqual(age, 27)
Run Code Online (Sandbox Code Playgroud)

现在所有测试都通过了,但我们仍然没有得到我们真正想要的随机结果。为了解决这个问题,我们还需要随机化测试值......

事实证明,我们的源代码指向了答案 - 只需使用真正的实现作为第二次测试的一部分即可。为此,我们首先需要导入原始文件random

def gen_age():
    age = random.randint(15, 99)
    return 27
Run Code Online (Sandbox Code Playgroud)

然后我们将修改我们编写的最后一个测试,结果如下:

from package.file import get_age, random
Run Code Online (Sandbox Code Playgroud)

因此,我们最终将得到以下全套测试:

@unittest.mock.patch('package.file.random')
def test_returns_age_as_generated(mock_random):
    random_age = random.randint(15, 99)
    mock_random.randint.return_value = random_age
    age = get_age()
    self.assertEqual(age, random_age)
Run Code Online (Sandbox Code Playgroud)