枚举只是有限类型,带有自定义(希望有意义)的名称。枚举可能只有一个值,像void
只包含null
(有些语言称此unit
,并且使用的名称void
与枚举没有元素!)。它可能有两个值,例如bool
has false
和true
。它有3个,像colourChannel
带red
,green
和blue
。等等。
如果两个枚举具有相同数量的值,则它们是“同构的”;也就是说,如果我们系统地切换所有名称,则可以使用一个名称代替另一个名称,并且程序的行为也不会有所不同。特别是我们的测试不会有任何不同!
例如,result
含win
/ lose
/ draw
是同构上面colourChannel
,因为我们可以代替例如colourChannel
用result
,red
用win
,green
用lose
而blue
用draw
,只要我们这样做随处可见(生产者和消费者,解析器和串行器,数据库条目,日志文件,等等。 ),那么我们的程序就不会有任何变化。colourChannel
即使没有,我们编写的任何“ 测试”仍会通过colourChannel
更多!
同样,如果一个枚举包含多个值,我们总是可以重新排列这些值以获得具有相同数量值的新枚举。由于值的数量没有改变,所以新的排列与旧的排列是同构的,因此我们可以切换出所有名称,并且测试仍然可以通过(请注意,我们不能只切换定义;我们必须仍要关闭所有使用网站)。
这意味着,就机器而言,枚举是“可区分的名称”,仅此而已。我们用枚举只能做的事情就是分支两个值是相同的(例如red
/ red
)还是不同的(例如red
/ blue
)。这就是“单元测试”唯一可以做的事情,例如
( red == red ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
( red != green) || throw TestFailure;
( red != blue ) || throw TestFailure;
...
正如@ jesm00所说,这样的测试是在检查语言实现而不是您的程序。这些测试绝不是一个好主意:即使您不信任语言实现,也应该从外部对其进行测试,因为不能信任它正确运行测试!
这就是理论;那练习呢?枚举的这种特征的主要问题在于,“现实世界”程序很少是独立的:我们拥有旧版,远程/嵌入式部署,历史数据,备份,实时数据库等,因此我们永远无法真正“转出”名称的所有出现而不会丢失某些用途。
但是这样的事情不是枚举本身的“责任”:更改枚举可能会中断与远程系统的通信,但是相反地,我们可能会修复通过更改枚举此问题!
在这种情况下,枚举是一条红线:如果一个系统需要采用这种方式,而另一个系统需要采用这种方式,该怎么办?无论我们编写多少测试,都不能兼而有之!真正的罪魁祸首是输入/输出接口,该接口应该产生/使用定义明确的格式,而不是“解释选择的整数”。因此,真正的解决方案是测试i / o接口:使用单元测试来检查其是否正在解析/打印所需的格式,而通过集成测试来检查该格式是否真正被另一方接受。
我们可能仍然想知道枚举是否被“充分地执行了”,但是在这种情况下,枚举又是一个红鲱鱼。我们真正关心的是测试套件本身。我们可以通过以下两种方式来建立信心:
- 代码覆盖率可以告诉我们,来自测试套件的各种枚举值是否足以触发代码中的各个分支。如果没有,我们可以添加触发未覆盖分支的测试,或者在现有测试中生成更多种类的枚举。
- 属性检查可以告诉我们代码中的各种分支是否足以应付运行时的可能性。例如,如果代码仅处理
red
,而我们仅使用进行测试red
,则覆盖率达到100%。属性检查器将(尝试)生成断言的反例,例如生成我们忘记测试的green
和blue
值。
- 变异测试可以告诉我们我们的断言是否实际上检查了枚举,而不是仅仅跟随分支并忽略它们之间的差异。