【IT168 技术文档】
首先说一下Test Fixture,我不知道怎么样翻译这个Test Fixture,没能搜到一个翻译的比较合适的。最让我气愤的是某人翻译的一本书中,直接把Test Fixture翻译成为测试夹具,这明显就是什么词霸词典硬翻译出来的,我强烈鄙视这样不负责任的翻译行为。
The test fixture is everything we need to have in place to exercise the SUT
我觉得这是一个对Test Fixture的一个很清晰明了的定义,就是运行被测软件所需要的一切东西,这个“东西”不单只是数据,同时还包括对被测软件的准备,例如实例化某个被测方法所在的类,准备数据库的ConnectionString等。通常来说,有三种方法来准备Test Fixture。
1. 内联方式:这种方式就是直接在测试方法中编写准备Test Fixture的代码。用这种方法的缺点是很容易造成代码的重复,出现很多复制粘贴的代码。同时,如果这个SETUP的过程比较复杂,也会降低测试代码的可读性,可维护性。另外的一个问题就是,这种方法很容易会带来测试数据Hard code的隐患。既然有那么多缺点,这种方法还有什么生命力呢?首先,可能对于初学者来说,这种方法是最简单的;其次,在一些只需要准备简单的Test Fixture的场合中,这种方法还是给编写测试的人提供了便利。
2. 委托方式:简单来说就是把Test Fixture的准备抽取为一个外部的方法,然后在需要的时候进行调用。这种方式的好处就是使得测试代码可读性更强,并且这部分的SETUP代码可以重用。而且这种做法可以屏蔽对SETUP过程的认知,使得测试人员的关注点落在真正的测试代码上面,而不是如何SETUP。
3. 隐式方式:很多xUnit框架的实现都提供了不同的隐式SETUP和TEARDOWN。例如MSTEST里面的[TestInitialize]和 [TestCleanup]标签,就提供了一种隐式准备Test Fixture的支持。就是在每一个测试方法运行前,都会执行一次标有[TestInitialize]标签的方法。使用这种方法的好处就是写一次就能在各个测试中都实现了Test Fixture的准备,不用每次都显示地调用一个外部方法,不过缺点也不少:
可能会令测试比较难懂,因为这些隐式调用不是必须的,有可能会被遗漏掉。
不能使用哦本地变量来保存对象,只能用test class 里面的filed或者property
变相地使用了全局变量
在做单元测试的过程中,需要灵活地运用这三种Test Fixture的准备方法。例如在我的工作当中,我在以下情况使用到了隐式准备方式:
在某一个测试项目中,由于测试的数据不多,所以我使用了XmlSerializer,把测试的数据都放在一个XML文件中,然后利用 XmlSerializer把XML中的数据映射到相应的配置类中。对于这样的情况,这些测试数据在测试运行的全过程中都需要用到,所以我选择了在测试类初始化的时候,只运行1次,来准备这些测试数据(Test Fixture的一部分)。
1 [ClassInitialize()]
2 public static void MyClassInitialize(TestContext testContext)
3 {
4 XmlSerializer xmlSerializer = new XmlSerializer(typeof(Config));
5 FileStream fs = new FileStream("Config.xml", FileMode.Open);
6 config = (Config)xmlSerializer.Deserialize(fs);
7 }
还是在同一个项目中,我想让每一个测试之间互相不要受到影响,所以我在[TestInitialize()]方法中对于一个Web Service的客户端进行初始化,保证每一次运行测试前,这个客户端都是“新”的
1 [TestInitialize()]
2 public void MyTestInitialize()
3 {
4 USi18nService = new InteropWebSerivce();
5 USi18nService.Url = config.USi18nAddress;
6 }
在另外的一个项目中,需要对一个方法进行测试,该方法的功能是根据Email地址从PreSignup表里面获取相关的资料,那么首先要做的就是完成一个PreSignup的操作,才能检查这个方法。而完成PreSignup操作并不是所有的测试都需要的,所以我不选择隐式调用,而是选择委托调用,调用一个外部的帮助方法,来帮助我完成PreSignup操作;同时这个PreSignup的操作在其他一些测试中也是需要的,所以也达到了重用的效果。
那么在什么时候会用到内联方法来准备Test Fixture呢?其实我自己经常用这种方法,有以下一个场景,我需要对一个方法进行测试,这个方法要做的事情就是检查一个电话号码是否符合规范,那么我就会先创建一个List,然后在List里面填充了各种不同的电话号码,然后在后面用一个foreach语句把List里面的数据遍历一遍,可能这些电话号码的数据只在这一个方法里面才有用,所以我没有选择把它抽取成一个方法。
其实不同的单元测试框架有不同的思想,例如在NUnit里面,一个测试类的标签就是叫[Test Fixture],其实作者的设计思想就是一个测试类,就是一套Test Fixture;如果不是一套Test Fixture,那么不要把测试方法写到一起。其实这三种方法各有所长,在我刚开始学习和尝试做单元测试的时候,我刚接触到类似[Setup] [TearDown]这样的隐式调用的时候,我觉得这就是银弹,我要充分使用它。但是随着工作的深入,发现这个隐式调用会带来一些问题,然后就慢慢转用了调外部方法,或者直接内联到测试方法中。只有结合实际情况,结合的上下文来运用这些方法,才能真正提高单元测试代码的质量,减少我们的工作量。