上篇我们添加了用户模块的test case,算上登录模块,我们已经有五个test case了。TestRunner.java看起来如下:
可以看出来,随着TestRunner.java越来越长,如果有很多test case你就得实例化每一个+调用test()方法,很不方便维护。比如我现在只想执行登录模块的,不想执行别的?或者是,现在的系统默认是用chrome来跑的,我想用ie或是firefox该怎么办呢?其中一个答案就是设置一个配置文件来解决。通过读取文件上的值我们可以自由配置我们想要的测试参数,如果希望改变则直接修改配置文件即可。
首先声明一下,配置文件可以是任意格式、任意后缀名的,只要满足你设计的框架要求就行了。我们设计框架以来一直用的都是Excel文件,那这次搞配置文件我们就用csv文件吧,顺便复习一下。新建文件夹config -> config.csv:
在里面写上如下内容:
就这一行,里面有4列,定义了用哪种浏览器,driver的路径,以及测试的模块等:
先把csv文件上的数据读到数组里,文件读取也属于测试中的一些常用的、必要的步骤,所以还是放在com.testalliance.hrsystem.test中,这里我们创建一个新类Utility.java:
写如下代码:
依靠getModules()方法读取csv文件的内容,读到逗号时进行分割并把当前一行信息放入一个数组中,然后把每一个数组放入ArrayList里。不太理解的朋友可以去参考csv文件操作那篇。接下来我们把TestRunner.java改成下面这个样子:
第16行调用getModules()方法获取所有的配置信息,然后通过循环ArrayList一个个读取出来。第27行和28行是处理测试模块的步骤,可能有些人看不太懂。第27行用字符串的replace()方法将"{"和"}"去掉,然后以“|”为新的分隔符再划分成几部分放入arr_modules数组中。比如我们例子中从ArrayList读取出来的最后一列字符串是"{login|employee}",先将左右括号去掉后变成"login|employee",再以"|"为分隔符把login和employee放进数组arr_modules里。另外,你可以把每个case的test()方法都修改成接受三个参数用于执行测试步骤。这么一改,我发现可以有选择性地执行模块了。
但即便是这样,我们还是觉得TestRunner.java很臃肿,测试用例多了之后还要加上各种判断语句和实例化过程,其实并没有改变太多。所以,我们还要继续修改。首先观察一下测试用例类和测试文件的名称:
文件名称是一一对应的,只不过后缀名不一样而已。仔细想一下,如果只希望执行某个模块的测试用例,我们势必要遍历整个测试文件夹:
每读到一个文件,就要实例化对应的用例,比如读到TCLogin3.xlsx,我们就要实例化TCLogin3,读到TCEmp1.xlsx,我们就要实例化TCEmp1。测试文件名本质上是一个字符串,而类名本质上是一个类,那我可不可以有一种办法,可以把字符串转成类,这样是不是程序执行时只要通过循环读取文件名就能自动实例化类了?是不是我们就不用再一遍遍来回写了?做这步之前,我们先简单修改一下TestRunner.java:
画红框的部分就是增加或修改的地方,第30行到37行就是遍历某个测试数据文件夹然后取出某个文件名的过程。取出的文件名带有后缀.xlsx,要去掉,因为类名是没有后缀的。这里用到的都是字符串的操作,不说太多了。正是因为我们每次取出一个文件代表一个类,也就是一个测试用例,我们其实可以把测试的准备工作从每个test()方法中单独提取出来,这步在第45行和46行。因为单独提取出来了,那我们完全可以在准备步骤里就传入driverBrowser,driverPath和browser三个参数,test()方法里只需要传入driver即可。这样,准备工作是准备工作,测试步骤是测试步骤,两者分开了。把测试类中test()方法里的准备步骤去掉,以TCLogin1.java为例:
现在更明显了吧?我们现在要做的就是要把TestRunner.java里面的if-else语句替换成文件名转类名的过程就行了。好了,问题来了,怎样才能把文件名转成类名呢?java里有一个类似的功能,叫做反射机制,可以帮助我们。
首先我们回顾一下之前介绍的一些java基本概念。在讨论面向对象的时候我们说实例化一个对象时内存中应该是这样的:
当执行到Wanghong w = new Wanghong()时,Wanghong对象被创建出来,但与此同时其实还有一个对象被创建出来(其实是在.class文件被加载到JVM的时候),这个对象的类型是Class,指向的是Wanghong类的一些信息:
我们在介绍java的第一篇就说了,一段程序运行时首先会生成一个.class文件,随后这个.class文件会被加载到java虚拟机(JVM)中并被执行,这个Class类型的对象就是这个时候创建的。那怎样得到这个对象呢?为了演示,我新建了一个叫JavaReflection的项目:
看下面的代码:
Wanghong.java:
Test.java:
看Test.java,第一种得到Class类对象的方法就是用Wanghong类的对象调用一个叫getClass()的方法,然后可以打印出来它的一些基本信息;第二种方法直接调用class属性;第三种调用forName()方法,需要添加该类的完整路径作为参数,注意是完整路径,包括包名。
有一点需要指出的是,在同一个运行时一个类只有一个Class类对象被创建出来,也就是说wClass1、wClass2、wClass3它们都是一个东西。Java反射这部分知识在测试中暂时用得不多,我就讲到这儿,具体可以参考其它一些文章,我参考的就是这篇,讲得很详细。大家现在仔细看看第三种方式,类的完整路径是一个字符串,我们通过这个字符串可以获得类名。有人说你讲这个反射我还是没明白和咱们的测试执行有什么关系,接着看。java映射的概念就是可以把一个类中的成员(包,构造方法,成员方法,成员变量等等)映射成一个个对象,比如Class对象,是通过com.test包中的Wanghong类得到的。同样,如果我们写Class c = Class.forName("com.testalliance.hrsystem.tests.login.TCLogin1“)也会得到一个Class对象。TCLogin1.java里有一个test()方法,用于执行测试用例,我们也可以把这个成员方法映射成对象。TCLogin1.java默认还有构造函数,用于初始化一个对象,我们还可以把构造函数映射成对象。这么多对象干什么用?答案在下面的红框里:
第59行是成员方法的反射使用。用Class对象调用getMethod()方法可以获取到某个成员方法的对象,它接收的参数一个是方法名,还一个是参数类型,因为我们是driver,所以写WebDriver.class。参数类型根据方法的参数个数而定,test()方法只有一个driver参数,所以我们只放一个WebDriver.class。第61行是构造方法的反射使用,构造方法的作用是初始化一个类,在这里会得到测试类的对象classObj,这一步相当于Wanghong w = new Wanghong()。第63行用方法对象调用invoke方法传入classObj和driver作为参数,相当于调用了test()方法。其实还是一个道理,只不过对象、方法、参数都颠倒过来了,感觉很别扭。这种正向反向可以看下图对比一下:
想初始化测试类就得先获取Class对象,想调用test()方法就得先获取方法对象,这么理解可能就容易些。更多反射的用法还是可以参照这篇,我就不再啰嗦了。
执行一下,测试通过。改动到此为止,下一篇我们再做几个简单的变化,这个框架雏形就完成了。对于配置文件来说还是那句话,只要和自己设计的框架吻合就可以了。
这篇文章的源代码在SeleniumExcelDataDrivenFrame4项目里边。