为何过度使用SetUp会降低测试可读性及TearDown为何无需手动置空
引自《单元测试的艺术》第二版第34页
但要知道,你使用[SetUp]越多,测试的可读性就越差,因为人们必须查看文件中两个位置的测试代码,才能理解测试如何获取实例以及测试使用的每个对象的类型。我告诉我的学生:“想象一下,你的测试读者从未见过你,也永远不会见到你。他们在你离开公司两年后来阅读你的测试。你所做的每一件能帮助他们无需提问就能理解代码的小事都是极大的帮助。他们身边可能没人能回答这些问题,所以你是他们唯一的希望。”让他们的眼睛在代码的两个区域之间不断跳转来理解你的测试并非明智之举。
using NUnit.Framework; [TestFixture] public class LogAnalyzerTests { private LogAnalyzer m_analyzer=null; [SetUp] public void Setup() { m_analyzer = new LogAnalyzer(); } [Test] public void IsValidFileName_validFileLowerCased_ReturnsTrue() { bool result = m_analyzer.IsValidLogFileName("whatever.slf"); Assert.IsTrue(result, "filename should be valid!"); } [Test] public void IsValidFileName_validFileUpperCased_ReturnsTrue() { bool result = m_analyzer.IsValidLogFileName("whatever.SLF"); Assert.IsTrue(result, "filename should be valid!"); } [TearDown] public void TearDown() { //the line below is included to show an anti pattern. //This isn’t really needed. Don’t do it in real life. m_analyzer = null; } }
技术疑问解答
1. 为何过度使用SetUp会降低测试代码的可读性?为何需要查看文件中两个位置的代码才能理解测试实例的获取方式及对象类型?
当实例初始化逻辑被放到[SetUp]方法中时,测试方法本身无法直接展示它依赖对象的创建过程。比如示例里的两个测试方法,只用到了m_analyzer私有字段,但新读者必须跳转到Setup()方法,才能知道这个对象是通过new LogAnalyzer()创建的,甚至要确认字段的具体类型。
如果测试类里有大量测试方法,且所有前置逻辑都藏在SetUp中,读者需要反复在测试方法和SetUp之间切换,才能梳理清楚每个测试的初始状态。尤其是当SetUp逻辑变复杂(比如初始化多个对象、设置不同依赖),测试的“自解释性”会完全丢失——读者没法一眼看懂测试的前置条件,必须额外花时间整合分散在两处的逻辑,自然降低了可读性。
2. 为何示例中的TearDown()方法无需手动实现,实际开发中不应这样做?
首先,.NET及NUnit运行环境会自动管理对象生命周期。测试方法执行完毕后,当前测试类的实例会被销毁,私有字段(如m_analyzer)会被垃圾回收机制处理,手动将m_analyzer = null完全是多余操作。
其次,这种手动置空属于“过度清理”,会引入不必要的代码噪音。如果LogAnalyzer持有非托管资源,清理逻辑应该放在LogAnalyzer自身的Dispose方法中实现,而非测试的TearDown里手动置空引用。对于普通托管对象,完全不需要这一步,写了只会让测试代码臃肿,还容易误导新手做无用功。
内容的提问来源于stack exchange,提问作者bokabokaboka

