
2.1 模块化
模块化即将测试用例中的公共部分抽离出来供其他测试用例调用。被模块化的公共部分可以单独维护,这样可以大大降低维护的工作量。
2.1.1 初步模块化
针对第1章的登录测试用例,可以抽离出3个公共部分:建立会话、登录和断开会话。
1.建立会话
在使用ChromeDriver类的构造方法创建一个ChromeDriver对象时,实际上是与chromedriver.exe建立了一个会话。如果测试代码对浏览器有多个操作,那么没必要在每个操作之前都建立一个会话,只需要保证建立一个会话,后续操作都携带该会话的会话ID即可。因此建立会话属于公共部分,可以被抽离出来。

然后调用该方法创建一个ChromeDriver对象,即建立一个会话。

读者可能认为该公共部分只有一行代码,还不如不抽离。以登录测试用例为例,这样的抽离的确没有发挥作用,反倒是增加了代码量。
由于建立会话属于初始化操作,结合TestNG的测试用例生命周期,更好的解决方法是将创建ChromeDriver对象的代码放在@BeforeClass注解修饰的初始化方法中,并定义一个ChromeDriver类型的字段。

这样在同一个测试类中的多个测试方法便可以共享这个会话,达到抽离公共部分的目的。
2.登录
如果需要在IMS系统中进行各种操作,那么需要先进行登录。这样的分析似乎应该将登录与建立会话一起被放在初始化方法中,但如果对登录功能本身做测试时,就需要覆盖不同的登录场景(如登录成功或登录失败),因此更为合理的方式是将登录单独抽离成方法。

测试方法直接调用该方法即可。

如果有另一个测试类需要登录操作,那么可以直接调用该测试类的登录方法即可实现登录,不需要重复编写代码。
3.断开会话
与建立会话相反,断开会话应该属于清理操作,放在@AfterClass注解修饰的清理方法中更为合理。


这样同一个测试类中的所有测试方法执行完成后才会断开会话,达到了会话共享的目的。
2.1.2 进一步优化
经过初步模块化后的测试用例代码看起来结构更为清晰,但仍然存在以下问题。
1)每个测试类都需要编写初始化和清理方法来建立和断开会话。
2)登录方法中的用户输入部分(即用户名和密码)被硬编码在代码中,如果换一个用户登录则需要修改登录方法,这样会使登录方法的复用效率大打折扣。
3)测试类应该注重测试本身,而不是对外提供服务(即登录服务)。确切地讲,一个新测试类只有导入上述测试类中的登录方法或继承上述测试类才能达到代码复用的目的,这种方式增加了测试类之间的耦合性。
以下依次提供上述问题的解决方案。
1.通过继承复用初始化和清理方法
TestNG中@BeforeClass注解和@AfterClass注解修饰的方法是可以被继承的,子类中如果也有@BeforeClass注解和@AfterClass注解修饰的方法,那么会先执行父类的@BeforeClass注解修饰的方法,后执行父类的@AfterClass注解修饰的方法。
首先定义一个抽象基类,代码如下。


然后测试类继承该抽象基类便可复用初始化和清理操作。

2.通过传参来实现动态输入用户名和密码
给登录方法增加形参用来接收用户名和密码,这样便可达到动态输入用户名和密码的目的。

请注意以上断言中的预期结果也被修改为了变量(即username)。
经过以上改造后,测试方法可以使用不同的用户名和密码来登录,且不需要修改登录方法。

3.将登录封装到单独的类中
想要避免测试类之间的相互耦合,将公共部分单独封装到一个类中不失为一个好的选择。

请注意这种方式需要在登录方法中加入ChromeDriver类型的形参,在调用该方法时需要传入一个ChromeDriver对象。
测试类只要拥有LoginLogic类的对象便可使用登录方法。

也可将登录方法定义为静态方法,代码如下。


这样测试类不需要拥有LoginLogic实例,只需要静态导入即可使用登录方法:

对比第1章的登录测试用例,当前测试方法已经被简化为只有一行代码了。
总结一下,本节使用模块化方式将测试用例分成了3层。
1)公共函数层:公共方法的封装,该部分代码与具体业务无关,适用于所有项目。对应本节的AbstractTestClass类。
2)业务函数层:业务方法的封装,该部分代码与具体业务有关,仅适用于当前项目。对应本节的LoginLogic类。
3)测试用例层:测试用例的封装,该部分代码为具体的测试用例。对应本节的Login测试类。