Unit testing 如何对特定实现进行单元测试

Unit testing 如何对特定实现进行单元测试,unit-testing,testing,tdd,software-design,software-quality,Unit Testing,Testing,Tdd,Software Design,Software Quality,我读了很多关于(单元)测试的书,我试着在我的日常工作流程中尽可能多地实现,但不知何故,我觉得我做错了什么 假设我有一个函数,它采用一个路径,并基于该路径的某些元素为新日志文件创建一个名称。路径可以是C:/my\u project/dir\u 1/message01,它应该将其转换为dir\u 1\u log\u 01.txt。函数名为convertPathToLogfileName 如果我想为此函数编写一个单元测试,它可能会如下所示: def test_convertPathToLogfileN

我读了很多关于(单元)测试的书,我试着在我的日常工作流程中尽可能多地实现,但不知何故,我觉得我做错了什么

假设我有一个函数,它采用一个路径,并基于该路径的某些元素为新日志文件创建一个名称。路径可以是
C:/my\u project/dir\u 1/message01
,它应该将其转换为
dir\u 1\u log\u 01.txt
。函数名为
convertPathToLogfileName

如果我想为此函数编写一个单元测试,它可能会如下所示:

def test_convertPathToLogfileName():
path=“C:/my\u project/dir\u 1/message01”
expected=“dir\u 1\u log\u 01.txt”
实际值=convertPathToLogFileName(路径)
资产质量(预期、实际)
现在我可以编写一系列这样的测试来检查所有不同类型的输入,如果输出是我所期望的

但是,如果在某一点上,我决定我选择的日志文件的命名约定不再是我想要的:我将更改函数,使其实现我的新需求,而所有测试都将失败

这只是一个简单的例子,但我觉得通常情况就是这样。当我在编程时,我想出了一种新的方法来做某事,然后我的测试失败了

这里有我遗漏的东西吗?我测试错了吗?如果是这样,你会如何处理这种情况?或者就是这样,我应该接受吗

这里有我遗漏的东西吗?我测试错了吗?如果是这样,你会如何处理这种情况?或者就是这样,我应该接受吗

不是真的。这是一种典型的“输入X导致输出Y”情况

你可能会改变的一件事是:也许在你完成了TDD之后,你得到了各种各样的小测试,这些测试都验证了你被测试函数的输入/输出契约的不同方面。。。你可以把它放到桌子上

意思:这里很可能不需要20种不同的测试方法(它们都做相同的事情)。为什么不使用一个简单的列表,其中包含成对的“X-in”应该导致“Y-out”数据点。然后,您有一个测试,它遍历该列表并测试这些对

但是回到您的主要问题:您没有测试实现。您的测试进行黑盒输入/输出测试:X进入,Y预计会出来

换句话说:您的测试验证的是契约,而不是实现。实现是一段代码,它以某种方式计算特定X的正确Y。如何做到这一点并不重要,无论是对您的“生产用户”,还是对您的测试用例都不重要

因此:如果您的合同(完全)发生变化,那么您当前的所有测试都将无效。是的,当你遵循TDD时,这可能意味着你(或多或少)从零开始

这里有我遗漏的东西吗?我测试错了吗?如果是这样,你会如何处理这种情况?或者就是这样,我应该接受吗

不是真的。这是一种典型的“输入X导致输出Y”情况

你可能会改变的一件事是:也许在你完成了TDD之后,你得到了各种各样的小测试,这些测试都验证了你被测试函数的输入/输出契约的不同方面。。。你可以把它放到桌子上

意思:这里很可能不需要20种不同的测试方法(它们都做相同的事情)。为什么不使用一个简单的列表,其中包含成对的“X-in”应该导致“Y-out”数据点。然后,您有一个测试,它遍历该列表并测试这些对

但是回到您的主要问题:您没有测试实现。您的测试进行黑盒输入/输出测试:X进入,Y预计会出来

换句话说:您的测试验证的是契约,而不是实现。实现是一段代码,它以某种方式计算特定X的正确Y。如何做到这一点并不重要,无论是对您的“生产用户”,还是对您的测试用例都不重要

因此:如果您的合同(完全)发生变化,那么您当前的所有测试都将无效。是的,当你遵循TDD时,这可能意味着你(或多或少)从零开始

这里有我遗漏的东西吗

有几件事

一个是,你不一定需要所有的测试来指定受试者的确切行为。断言两个表示形式彼此完全相等是一个很好的起点,这是可能有效的最简单的方法,但这不是唯一的选择。如果有一组测试,每个测试都建立了一些约束条件,并且这些约束条件都得到了满足,那么,当您对预期的行为进行一个小的更改时,您只需要在这些测试中进行一个小的更改就可以了

二是模块设计;见[Parnas 1971]。这里的基本思想是,每个模块都是基于一个决策建模的,如果我们更改了一个决策,我们将替换该模块。模块边界像舱壁一样进行更改

在您的示例中,可能至少有两个模块

path = "C:/my_project/dir_1/message01"
expected = "dir_1_log_01.txt"
这看起来很像您需要一个
parse
函数来从路径中提取感兴趣的信息,而一些
apply to template
函数则需要对提取的信息执行一些有趣的操作

这可能允许您编写一个断言,如

assertEquals(
    applyTemplate("dir_1", "01"),
    convertPathToLogFileName(path)
)
然后在其他地方你可能会有,像

assertEquals(
    "dir_1_log_01.txt",
    applyTemplate("dir_1", "01")
)
当您以后决定拼写应该更改时,您只需更改第二个断言。有关此想法的更多信息,请参见詹姆斯·肖尔

在测试驱动的世界中,经常会发生的事情是,在发现我们需要改变一些行为之后,我们将重构以围绕deci创建一个模块