我认为在@user983716 的答案之上还有更多内容可以构建,因为:
- 这个答案通过强制种子改变了源代码的工作方式,而真正的源是不确定的
- 该测试可以做更多的事情来记录预期的行为,而该测试是模糊的,可能会让大多数人感到困惑
真正的愿望不是测试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)
这是一个很好的开始测试,因为如果发生故障,它会提供清晰的输出:
AssertionError: 14 not greater than or equal to 15
AssertionError: 100 not less than or equal to 99
好的,但我们还想确保它是一个随机数,因此我们可以添加另一个测试,以确保我们按预期从randint 获取它:
@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)
我们在这里做了两件重要的事情:
-
random 对象(来自定义 gen_age 的文件)已修补,以便我们可以对其执行测试,而无需依赖实际实现
- 做出一个断言,确认预期的 15 和 99 参数已提供给
randint,因此给出了正确的范围
可以编写额外的测试来断言真实的返回值,确认给定的数字始终是随机的。这将提供该方法直接返回随机值的信心,因为可以想象该方法可以做更多的事情,甚至返回一些任意值,即使它在内部仍然进行randint 调用。
例如,假设有人将gen_age() 更改为如下:
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
哦,现在只有 randint 返回 99 的情况下,我们的第一个测试会失败,而第二个测试仍然会通过。这是一个等待发生的生产错误...
那么,一个简单但有效的确认结果的方法可能如下:
@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)
这里还有最后一个缺陷……如果返回的值变成这样会怎样:
def gen_age():
age = random.randint(15, 99)
return 27
现在所有的测试都通过了,但我们仍然没有得到我们真正想要的随机结果。为了解决这个问题,我们也需要随机化测试值......
事实证明,我们的源代码指向了答案 - 只需将实际实现用作第二次测试的一部分。为此,我们首先需要导入原始的random:
from package.file import get_age, random
然后我们将修改我们编写的最后一个测试,结果如下:
@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)
因此,我们最终会得到以下全套测试:
import unittest
from package.file import get_age, random
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)
@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)
@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)