C#单元测试为什么不能过多使用 setup?

2023-10-04 13:51:09 +08:00
 huzhikuizainali

但是要注意,使用[Setup] 越多,测试代码可读性就越差。为了理解测试怎么得到实例,使用的对象是什么类型,人们不得不同时看文件里的两处代码。我告诉我的学生: “想象一下,代码的读者和你从未谋面也无法联系,在你离职两年之后才来读你的测试代码。你使得代码易读的每一个微小的努力,对他们都是极大的帮助。他们如果对你的代码有任何问题,周围都不会有人能够解答,所以你是他们唯一的希望。“如果读代码的人为了能理解你的测试而不得不在两段代码间来回地看,那可不是什么好主意。

请看上面这段单元测试的讲解,有两个问题:

1 、为什么“使用[Setup] 越多,测试代码可读性就越差。为了理解测试怎么得到实例,使用的对象是什么类型,人们不得不同时看文件里的两处代码。” 是因为由于 setup 的存在。所以每执行一次 test 标签的代码就会重新执行一次 m_analyzer = new LogAnalyzer(); 么? 但是这为什么会导致可读性变差。为什么会不得不同时看文件里的两处代码?而且书中说一段测试代码只能使用一个 setup 模块。所以何来“使用[Setup] 越多”的问题呢?

2 、为什么代码中最后一部分 public void TearDown () 说“不是必须的,在真实刹试中请不要使用”

1068 次点击
所在节点    C#
7 条回复
Zhuzhuchenyan
2023-10-04 15:54:42 +08:00
Setup 是否会导致可读性变差我持中立意见,对于复杂初始化的操作 Setup 是很方便的解决方案。

TearDown 确实不是必须的,因为每次 Setup 都会给 m_analyzer 一个新示例,不需要强行赋值 null 来清理。
huzhikuizainali
2023-10-04 16:54:14 +08:00
@Zhuzhuchenyan 谢谢回复
关于 setup 可读性的问题,你能猜出作者的意思么。他举的这个例子不一定很恰当。但是如果一个测试程序有几十个 test 标签呢?会有可读性的问题么?但我想真要这么多,可读性与有没有 setup 也没什么关系了吧?

TearDown 既然不是必须。代码清单中加入 TearDown 的目的是什么?想告诉读者什么信息?你能猜到么?
doghappy
2023-10-05 23:35:14 +08:00
作者想表达的是,在一个测试中,非必要不使用 setup tearDown ,如果非要构造复杂的对象,应该创建一个方法去初始化,你的测试方法调用初始化的方法。

如果你测试不得已引入了状态维护,你有可能在进行集成测试,此时可能需要用到这些复杂的生命周期代码。

setup 和 自己创建一个函数初始化对象的不同之处是,一个是隐式的,一个是显式的,显示可读性高,而且还有命名加持,例如 CreateObjectWithX 。setup 不表意,而且有的测试类中有,有的没有。读测试时你需要去关注生命周期,阅读性就变低了。

作者不推荐使用,但是还是列出来,是想告诉读者 NUnit 有哪些 API ,普及一些框架上的知识
huzhikuizainali
2023-10-05 23:48:54 +08:00
@doghappy 谢谢回复。
1 、如果你测试不得已引入了状态维护,你有可能在进行集成测试,--------作者后面也提到了这个观点,与你说法一致

2 、具体到代码清单 2-2 虽然有两个 test 标签,但是本质还是在做单元测试吧?只是针对输入文件不同的情况分成两段 test 。因为怕彼此状态关联。所以引入 setup 。我没学过 C#,不知道根据上下文猜测的对不对?

3 、一个是隐式的,一个是显式的-----------setup 不是系统自带的么?怎么反而是隐式调用?我有点看不懂。
4 、原文中“使用的对象是什么类型,人们不得不同时看文件里的两处代码。”--------这个意思你理解么?看文件里的两处代码这不是看代码的常态么?为什么 setup 的使用会加大这一困难?
doghappy
2023-10-06 05:38:20 +08:00
A2:从 2 个测试来看,我们能够理解到,每个测试运行前,都会由测试框架调用 setup 方法,也会掉用 tearDown ,这里我理解为作者想告诉我们,这两个方法每个测试都会掉用。 因此有可能会被误用,进行对象初始化。

A3 A4:是系统自带的,但是代码分离了。有的测试类中,可能没有 setup 方法,有的有。如果你正在阅读的 class 内容比较多,当你看都某个 test 方法时,你会想 m_analyzer 是一个字段,它在哪里被初始化呢?当你转到定义时,发现初始化是 null ,这时候你会猜测它可能在构造函数被初始化,然后你去看构造函数,发现没有构造函数,再父类里面被初始化的?但是没有父类。这是你得搜索所有代码,终于找到,原来 setup 中有进行初始化。我把这种叫“隐式”。因此我们发现 setup 容易被人忽视,作者的例子是比较短,所以你可以一眼看到初始化过程和 dispose 过程。
huzhikuizainali
2023-10-06 14:46:38 +08:00
@doghappy 谢谢你的回复

关于“原文中“使用的对象是什么类型,人们不得不同时看文件里的两处代码。”。我觉得你的答案是最合理的。还有几处想向你请教

5 、从代码清单 2-2 来看,setup 初始化对象 m_analyzer 是放在代码最顶端。一般看代码是不会看不到最顶端的吧?你说”当你转到定义时,发现初始化是 null “ 。这个定义在代码中是哪一句?

6 、实际工作中从代码可读性,可维护性来看。是不是对象初始化的语句和对象使用的语句相隔不能太远?一般公司代码规范会对此有明确规定么?
forgottencoast
2023-10-11 21:27:37 +08:00
歪个题,这个问题不属于 C#,这是 NUnit 测试框架的问题——可以归类于.Net
不过这个问题可能比较有代表性。
如你#6 回复的第 6 点,是的,应该这样说,声明对象(或者创建、构造)的语句应该尽可能的接近使用的地方。
我觉得作者就是这个意思。当然#3 也给出了很好的解释。
代码规范最多也就我这句话的建议,没办法给出具体的规定,毕竟代码的可读性和可维护性对业务不会产生什么影响。遵从这些最佳实践短期来说很难看出效果,长期来说好处又很难体现并且难以跟踪,公司管理层不重视是很自然的。所以,一般很多公司都不会有这么细节的规范。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/978868

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX