从事IT行业的人士,大多都听说过代码复用,其实测试用例和脚本也是可以复用的。下面为大家介绍一个测试框架,可以帮助测试人员进行用例和脚本的复用。
几乎所有的产品测试都可以用一组或多组“测试执行->结果验证”所组成。以下是一个简单的测试流程图。测试框架通过读取该流程图,就能够自动生成文本测试用例。如果每个测试单元都配置了自动化脚本,那么测试框架同样也可以根据流程图生成完整流程的自动化执行脚本。

如果测试需求发生了变化,需要增加贴吧测试项,只需要对流程图稍作调整(黄色部分),就能够借助测试框架同时完成测试用例的更新,如下图所示。如果增加了对应测试单元的自动化脚本执行步骤,那么自动化脚本也可以自动更新。

如果测试人员想针对产品做稳定性测试,测试框架可以根据配置生成可以一直执行到指定时间的自动化脚本。
其实这个测试流程图就是一个最简单的MBT模型,很多测试人员都用到过。一个大型软件系统的测试需求,经过抽象之后都可以用多个这样的MBT模型表述。相比文本测试用例,MBT模型更加直观,测试人员能够很清晰的看到测试覆盖的范围。相比Xmind等软件罗列的测试点,MBT模型更具有逻辑性。越复杂的软件使用MBT的优势越明显。
下面以如下流程图为例用一个简单的Demo来演示这个测试框架的用法。

测试框架通过读取该流程图生成以下的接口文件:
- package com.automation.test.demo;
-
- import org.graphwalker.java.annotation.Model;
- import org.graphwalker.java.annotation.Vertex;
- import org.graphwalker.java.annotation.Edge;
-
- @Model(file = "com/automation/test/demo/BaiduTestDemo.graphml")
- public interface BaiduTestDemo {
-
- @Vertex()
- void AccessBaiduPageVerification();
-
- @Edge()
- void AccessAForum();
-
- @Edge()
- void NavigateToNews();
-
- @Vertex()
- void NavigateToNewsVerification();
-
- @Edge()
- void Login();
-
- @Edge()
- void NavigatetoHomepageFromPostpage();
-
- @Vertex()
- void LoginVerification();
-
- @Edge()
- void NavigateToForum();
-
- @Edge()
- void AccessBaiduPage();
-
- @Edge()
- void NavigatetoHomepageFromNews();
-
- @Vertex()
- void NavigateToForumVerification();
-
- @Edge()
- void AccessAPost();
-
- @Vertex()
- void AccessAForumVerification();
-
- @Vertex()
- void AccessAPostVerification();
-
- @Vertex()
- void NavigatetoHomepageVerification();
-
- @Edge()
- void Logout();
-
- @Edge()
- void NavigatetoHomepageFromForum();
- }
实现该接口文件:
- package com.automation.test.demo;
-
- import java.io.IOException;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.util.concurrent.TimeUnit;
-
- import org.graphwalker.core.condition.EdgeCoverage;
- import org.graphwalker.core.condition.StopCondition;
- import org.graphwalker.core.condition.TimeDuration;
- import org.graphwalker.core.generator.RandomPath;
- import org.graphwalker.core.machine.ExecutionContext;
- import org.graphwalker.java.test.TestBuilder;
- import org.openqa.selenium.By;
- import org.openqa.selenium.WebDriver;
- import org.openqa.selenium.firefox.FirefoxDriver;
- import org.testng.Assert;
- import org.testng.Assert.ThrowingRunnable;
- import org.testng.Reporter;
- import org.testng.annotations.AfterTest;
- import org.testng.annotations.BeforeTest;
- import org.testng.annotations.Test;
-
- public class BaiduTestDemoTest extends ExecutionContext implements BaiduTestDemo {
- public final static Path MODEL_PATH = Paths.get("com/automation/test/demo/BaiduTestDemo.graphml");
- WebDriver dr = null;
-
- @Test
- public void runFunctionalTest() throws IOException {
- BaiduTestDemoTest context = new BaiduTestDemoTest();
- context.setPathGenerator(new RandomPath(new EdgeCoverage(100)));
- new TestBuilder().addModel(MODEL_PATH, context).execute();
- }
-
- @Test
- public void runStabilityTest() {
- new TestBuilder()
- .addModel(MODEL_PATH, new BaiduTestDemoTest()
- .setPathGenerator(new RandomPath(
- (StopCondition) new TimeDuration(60, TimeUnit.SECONDS)))).execute();
- }
-
- @Override
- public void AccessBaiduPageVerification() {
- // TODO Auto-generated method stub
- Reporter.log("百度页面打开成功");
- dr.findElement(By.id("su"));
- dr.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
- }
-
- @Override
- public void AccessAForum() {
- // TODO Auto-generated method stub
- Reporter.log("打开一个贴吧");
- }
-
- @Override
- public void NavigateToNews() {
- // TODO Auto-generated method stub
- Reporter.log("访问百度新闻");
- }
-
- @Override
- public void NavigateToNewsVerification() {
- // TODO Auto-generated method stub
- Reporter.log("新闻页面打开成功");
- }
-
- @Override
- public void Login() {
- // TODO Auto-generated method stub
- Reporter.log("登录");
- dr.findElement(By.xpath("//*[@id='u1']/a[@name='tj_login']")).click();
- dr.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
- dr.findElement(By.id("TANGRAM__PSP_8__userName")).clear();
- dr.findElement(By.id("TANGRAM__PSP_8__userName")).sendKeys("admin");
- dr.findElement(By.id("TANGRAM__PSP_8__password")).clear();
- dr.findElement(By.id("TANGRAM__PSP_8__password")).sendKeys("password");
- // 验证码的输入就省略了,请自行选择识别方式
- dr.findElement(By.id("TANGRAM__PSP_8__submit")).click();
- dr.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
- }
-
- @Override
- public void NavigatetoHomepageFromPostpage() {
- // TODO Auto-generated method stub
- Reporter.log("从贴子页回到首页");
- }
-
- @Override
- public void LoginVerification() {
- // TODO Auto-generated method stub
- Reporter.log("登录成功");
- dr.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
- }
-
- @Override
- public void NavigateToForum() {
- // TODO Auto-generated method stub
- Reporter.log("访问百度贴吧");
- }
-
- @Override
- public void AccessBaiduPage() {
- // TODO Auto-generated method stub
- Reporter.log("访问百度页面");
- System.setProperty("webdriver.gecko.driver","D:\\Tools\\geckodriver.exe");
- dr = new FirefoxDriver();
- dr.manage().window().maximize();
- dr.get("http://www.baidu.com/");
- }
-
- @Override
- public void NavigatetoHomepageFromNews() {
- // TODO Auto-generated method stub
- Reporter.log("从新闻页回到首页");
- }
-
- @Override
- public void NavigateToForumVerification() {
- // TODO Auto-generated method stub
- Reporter.log("贴吧页面打开成功");
- }
-
- @Override
- public void AccessAPost() {
- // TODO Auto-generated method stub
- Reporter.log("打开一个帖子");
- }
-
- @Override
- public void AccessAForumVerification() {
- // TODO Auto-generated method stub
- Reporter.log("贴吧打开成功");
- }
-
- @Override
- public void AccessAPostVerification() {
- // TODO Auto-generated method stub
- Reporter.log("帖子打开成功");
- }
-
- @Override
- public void NavigatetoHomepageVerification() {
- // TODO Auto-generated method stub
- Reporter.log("页面显示百度首页");
- }
-
- @Override
- public void Logout() {
- // TODO Auto-generated method stub
- Reporter.log("注销登录");
- }
-
- @Override
- public void NavigatetoHomepageFromForum() {
- // TODO Auto-generated method stub
- Reporter.log("从贴吧页回到首页");
- }
-
- }
通过TestNG执行测试脚本后的测试报告如下:

到现在为止,这个测试框架初步演示完了。这里演示的是功能测试,接口测试同样采用这种方式进行。该框架来至Github上的一个开源项目(博主个人公众号有项目原文链接🔗),为了更方便使用,只选择了一部分功能展示讲解。
由于是开源项目,我们对其进行了封装和优化,让框架更加完善,这里提供几个优化的思路:
1. 如何让数据和逻辑分离?
在这个测试框架中,我们将变量分为两种:测试项目变量和执行环境变量。如果在同一个执行环境中测试另外一个项目,只需要配置测试项目变量;如果同一个项目测试要放置在多套环境中执行,就只需要配置执行环境变量。
这里我们使用Spring依赖注入的方式来管理测试变量:

2. 如何让测试模型可以用中文标识?
在Demo演示中,测试模型里的每个测试单元都是英文标识的,我们实际使用时肯定更加习惯于用英文。虽然原作者没有提供中英文的转换,我们可以自己实现。
通过一个XML文件来存储中英文对照关系,每个中文标识的测试单元都对照一个英文值。测试框架根据存储的中英文对照关系来生成接口文件。
3. 如何扩展加入新的测试模型?
测试人员可能并不满足现有“测试执行->结果验证”这样的表现方式,想增加一些自定义的图形。
这里需要用到Java的多态和继承特性了。让框架的所有类都实现于一个接口,并修改源码的测试单元读取返回值类型为该接口。在获取到该测试单元的时候,进行类型转换成自定义的图形类即可。类似Android的Context接口。
4. 如何展示测试报告?
如果对测试报告格式没有特殊的要求,可以采用TestNG默认的报告,如之前Demo所示。测试报告的格式为html,直接放置在一个HTTP服务器上展示即可。
5. 如何让自动化测试可以持续集成?
免费的话,TestNG + Ant + Jenkins是比较通用的解决方案。
6. 对接数据库
既然Appium和Selenium都依赖了Spring,就用Spring JDBC来对接数据库吧。
以上都是比较容易实现的优化思路。如果开发资源较多,可以实现更复杂的一些功能,比如:
- 实现测试用例管理系统。测试框架生成用例后自动导入到管理系统,自动化用例执行完成后自动在管理系统上标记结果
- 实现测试环境调度系统。能够自动将当前任务分摊到多个测试环境中执行,甚至分配给远程的测试环境执行。
点击下面卡片关注本博主个人公众号可以获取更多技术干货
▲ 《程序员一凡》 ▲
关注上方公众号获取
