我正在尝试学习TDD,同时编写一个脚本,在一系列功能中转换输入数据。无论我是用python还是用R编写它,问题都是类似的。我猜它与TDD的理解更相关。
# Look of main in python
def main():
data = get_data()
data_a = transform_fun1(data)
data_b = transform_fun2(data_a)
data_c = transform_fun3(data_b)
....
return data_x
# Look of main in R
main <- function() {
data <- get_data() %>%
transform_fun1() %>%
transform_fun2() %>%
transform_fun3() %>%
...
data_x
}
为每个transform_fun
编写单元测试的最佳流程是什么,知道他们需要输入前一个transform_fun
的结果?
一开始它看起来相当干净,但随着我越来越远,我开始在每次测试中重现越来越多的main
,这并不好闻。重现main
过程的整个部分看起来与单元测试的想法相反。
# in python (pytest)
def test_transform_fun_n(data):
data_a = transform_fun1(data)
data_b = transform_fun2(data_a)
...
data_n = transform_fun_n(data_n-1)
assert data_n == blabla
# in R (testthat)
test_that("transform_fun_n do what I expect", {
data_a <- transform_fun1(data)
data_b <- transform_fun2(data_a)
...
data_n <- transform_fun_n(data_n-1)
expect_that(data_n, equals(blabla))
})
我还试图在每一步之间添加夹具(至少在python中),但它看起来也不理想。
- 编辑 - 试着勾勒出VoiceOfUnreason的答案会是什么样子。
def transformV1(data):
return data + x
def transformV2(data):
return transformV1(data) + y
def transformV3(data):
return transformV2(data) + z
def main():
data = get_data()
return transformV3(data)
答案 0 :(得分:0)
一开始它看起来相当干净,但随着我越走越远,我开始在每次测试中重现越来越多的主要内容,这种感觉并不好。重现主要过程的整个部分看起来与单元测试的想法相反。
是的,你是对的。代码试图告诉您,您的规范(和您的生产代码)是在错误的抽象级别编写的。
def test_transformV1(data, expected):
actual = transformV1(data)
assert actual == expected
def main():
data = getData()
return transformV1(data)
当需求发生变化时,您可以使用新规范
编写 new 测试def test_transformV2(data, expected):
actual = transformV2(data)
assert actual == expected
def test_transformV1(data, expected):
actual = transformV1(data)
assert actual == expected
def main():
data = getData()
return transformV2(data)
这里的关键思想是(a)你的单元测试你的生产代码提供的运动功能(b)新的需求意味着一个新功能 - 新功能可以用其他功能来实现,但测试只是检查新函数返回正确的结果。
如果main很难测试(an imperative shell的常见问题),那么您希望尽可能将其设为 thin 。
使它变得如此简单以至于显然没有缺陷
长链转换需要从shell重构到核心;给出一个名字,等等。
你的意思是代码应该写得更像我在问题末尾添加的内容
是的,这就是主意:命令式shell使用与其中一个测试相同的入口点来访问功能核心。
答案 1 :(得分:0)
由于您已经确定了一系列转换函数,因此逻辑行动方针是单独测试它们。另一方面,测试main()
只要它仍然是一个简单的调用序列就具有可疑价值。
因为每个函数都将前一个函数的结果作为输入,所以您可能会试图将它们链接到测试执行中,就像它们在主程序中链接一样。但是,这种方法会破坏单元测试方法,并且可能会让您注意到最终结果的快乐路径,从而使您无法在流程的每个步骤中探索可能存在问题的边缘情况。
相反,尝试依靠您的函数输入类型来提供一组具有不同输入值的测试,这些输入值反映了每个函数的各种场景。您甚至可以使用类似QuickCheck的基于属性的工具为您生成随机值。