其他测试框架 关于 web 自动化测试框架的一些想法和时间,请大家看看多提点意见

simonpatrick · 2015年06月18日 · 最后由 hello 回复于 2016年02月22日 · 3348 次阅读
本帖已被设为精华帖!

最近用 java 在写自动化测试框架,写着写着有点感觉好繁复。所以写出来希望可以得到一些建议,以下主要针对的是网页测试,总体的想法是:

  1. Page Object 模式,页面元素和外面的测试数据的模型命名一样,这样省去一些对应的代码,里面自己分装了自定义的元素
  2. 使用类似于爬虫的第三方包生成 page object 模型
  3. 通过操作流程直接生成测试代码
  4. 使用 testng,data provider 来驱动测试

以下是一些实现:

使用一下脚本解析页面元素,source.txt 是页面的 html 源码,可以页面片段也可以是整个页面:


PageObjectParser poParser = PageObjectParser.buildFromFilePath("source.txt");
poParser.parseRadio();
poParser.parseInputBox("ng-model");

得到一下结果:

@ElementName(elementName="property.propertyUsage")
private Radio propertyUsage;
@FindBy(name="property.estateName")
@ElementName(elementName = "property.estateName")
private InputBox estateName;
@FindBy(name="property.address")
@ElementName(elementName = "property.address")
private InputBox address;
@FindBy(name="property.roomNo")
@ElementName(elementName = "property.roomNo")
private InputBox roomNo;
@FindBy(name="property.permitNo")
@ElementName(elementName = "property.permitNo")
private InputBox permitNo;

稍微复杂点的页面:

PageObjectParser poParser = PageObjectParser.buildFromFilePath("source.txt");
       poParser.parseRadio();
       poParser.parseInputBoxInTable("ng-model");
       poParser.parseSelect("div");

得到结果:

@FindBy(name="property_propertySource")
@ElementName(elementName="property.propertySource")
private Radio propertySource;
@FindBy(name="property_status")
@ElementName(elementName="property.status")
private Radio status;
@FindBy(name="property_shape")
@ElementName(elementName="property.shape")
private Radio shape;
@FindBy(name="property_currentStatus")
@ElementName(elementName="property.currentStatus")
private Radio currentStatus;
@FindBy(name="property_propertyLook")
@ElementName(elementName="property.propertyLook")
private Radio propertyLook;
@FindBy(name="property_isAuction")
@ElementName(elementName="property.isAuction")
private Radio isAuction;
@FindBy(name="property_flagLoan")
@ElementName(elementName="property.flagLoan")
private Radio flagLoan;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//input[@name='property_contactName']")
@ElementName(elementName="property.contactName")
private InputBox contactName;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//input[@name='property_floorAll']")
@ElementName(elementName="property.floorAll")
private InputBox floorAll;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//input[@name='property_floorHeight']")
@ElementName(elementName="property.floorHeight")
private InputBox floorHeight;
@FindBy(xpath="//tr[not(contains(@style,'display: none'))]//select[@name='property_relationship']")
@ElementName(elementName="property.relationship")
private SelectList relationship;

生成页面模型之后,生成一个页面元素描述的 excel,可以是多个页面:

WebUICodeGenerator.build().writePageObjectsToExcel("sample.xls",
               Lists.newArrayList(MockPage1.class,MockPage2.class));


给业务流程自定需要跑那些元素,标上顺序,使用代码生成一个流程的方法或者注解:

2. 自动生成 annotation 或者测试代码

当整个业务流程涉及的页面完成后,使用如下的代码生成一份 excel:

WebUICodeGenerator t=  WebUICodeGenerator.build("AddProperty.xls");
               t.generateAnnotationStatement("流程1");
       t.generateAnnotationStatement("流程12");

注解或者代码:得到注解

MockPage1:
@UIActions(actions={@UIAction(processName="流程1",elementActionDescription={"propertyUsage","estateName","address","roomNo"})
})
MockPage2:
@UIActions(actions={@UIAction(processName="流程1",elementActionDescription={"contactName","floorAll","isAuction","currentStatus","status","propertyDecoration"})
})

放到页面 Page Object 类去。编写流程类:

最后构建整个业务流程,使用如下代码:中文命名只是为了说明使用。

public class 流程1 extends BaseWebTestAction {

    public 流程1(WebDriver driver, TestData testData) {
        super(driver, testData);
    }

    @Override
    public void execute() {
        WebTestActionBuilder.createTestActionByUIAction(MockPage1.class, "流程1",driver, testData).execute();
        WebTestActionBuilder.createTestActionByUIAction(MockPage2.class, "流程1",driver, testData).execute();
    }
}

以上代码中 execute() 实质上等价:

MockPage1 page1 = ModifiedPageFactory.createPageObject(driver,MockPage1.class);
     page1.processUIAction("流程1",testData);
     MockPage2 page2 = ModifiedPageFactory.createPageObject(driver,MockPage2.class);
     page2.processUIAction("流程1",testData);

或者:

MockPage1 page1 = ModifiedPageFactory.createPageObject(driver,MockPage1.class);
     page1.getPropertyUsage().selectByVisibleText(testData.get("propertyUsage"));
     page1.getAddress().input(testData.get("address"));
     page1.getEstateName().input(testData.get("estateName"));
     page1.getRoomNo().input(testData.get("roomNo"));
     MockPage2 page2 = ModifiedPageFactory.createPageObject(driver,MockPage2.class);
     page2.getContactName().input(testData.get("contractName"));
     ...........

如果不生成注解,直接生成代码就是:
使用代码:

WebUICodeGenerator.build("AddProperty.xls").generateSingleTestStep("流程1");

生成流程的函数:

public static void 流程1(WebDriver driver,TestData testData){
WebTestActionBuilder.createTestActionByElementActionList(MockPage1.class,Lists.newArrayList("propertyUsage","estateName","address","roomNo"),driver,testData).execute();
WebTestActionBuilder.createTestActionByElementActionList(MockPage2.class,Lists.newArrayList("contactName","floorAll","isAuction","currentStatus","status","propertyDecoration"),driver,testData).execute();
}

3. 测试数据准备

测试离不开测试数据的准备,测试数据类可以直接页面 Page Object 来生成:

WebUICodeGenerator t=  WebUICodeGenerator.build();
       t.generateTestDataClass(Lists.newArrayList(MockPage1.class,MockPage2.class));

结果:


private String roomNo;
private String permitNo;
private String propertySource;
private String status;
private String shape;
private String currentStatus;
private String propertyLook;
..........

使用 testng 驱动测试

public class SampleTest extends BaseWebTest {

    @DataProvider(name = "sample_data")
    public Iterator<Object[]> getAPITestData(Method m) throws Exception{
        Map<String, Class> clazzMap = new HashMap<String, Class>();
        clazzMap.put("Sample", Sample.class);
        Iterator<Object[]> y = ExcelHelper.build("abc.xls").ToIteratorInColMode(clazzMap);
        return y;
    }

    @Test(dataProvider ="property_data" )
    public void addProperty(Sample testData) {
       Login。。。。。
        流程1 flow = new 流程1(driver,testData);
        flow.execute();
    }

}

excel 的数据就是类似与:

Sample.roomNo Sample.propertySource
134 345

大概的思路是这样的。请大家多提宝贵意见。在这个过程可能可能看不到一些 webdriver 的操作,这个操作主要是封装了一层对应关系,每个自定义元素都有一个默认操作,没有特别说明就使用默认的操作。

其中对于自定义的元素也是使用过了 PageFactory 的方式,参考 selenium 的实现,自己做了一些定制化。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 10 条回复 时间 点赞

感谢分享,这是个很不错的思路,大部分体力活用自动生成来做。

不知道你说的 “繁复” 具体是指什么?就这么看的话没觉得哪里明显繁复啊。

PS:麻烦添加头像,最后的 excel 数据 那里表格的 markdown 用错了,具体怎么用可以参考右下角排版说明。

多谢,已修改.繁复,怎么说呢,就是用的人觉得有点复杂. 所以我不知道其他公司的框架是怎么样用的。

解析页面生成页面元素表挺可行的
1.为什么页面源码还要存个 source.txt,不直接去爬虫解析的页面生成页面元素表
2.如果页面变了,重新解析生成的页面元素表,你这个维护再同一张表内的 case 流程顺序是不是就得重新排了?
3.作为一个菜鸟测试,感觉真的好复杂,技术菜逼伤不起

  1. 关于存 source 文件主要是可以一个页面片段一个页面片段去组装,另外有些页面源码在查看页面源码的时候不能完全看到 2.页面变了肯定要改的,如果小改动就不需要去重新生成了,代码都生成好了就在代码里面改

我感觉好像很多人担心页面改了会怎么样,我怎么觉得这其实不是什么问题,因为改了就相应的改一下,关键其实如果遇到改动,貌似没有什么很好的解决方案可以让测试代码完全不改。有些情况下可能定位可以写好一点比如是针对一些特定的属性或者 css,其他我感觉这个问题无解,而且不是什么问题,开发代码改了,测试代码也改貌似挺合理的。我个人的理解,可能太肤浅了。

俩个问题请教:

  1. 对于重复元素的定位,怎样精确定位
  2. expectation 怎么写

page object 最大的问题是在于多租户的情况下如何管理你的页面对象, 如果 LZ 做的是通用 web 测试框架的话, 那需要把多租户的情况考虑进去, 不同的租户在某些功能模块或多或少会有不同的情况. 另外, 在多租户的情况下, 针对每个租户的 smoke test 或者 regression 也要考虑进去.

还有一点就是, 在目前的互联网趋势下, 如果有条件的话可能需要考虑自动定位页面元素的情况. 原因是 1) 针对开发的频繁修改 2) web 自动化测试框架可以用于有些爬虫数据的验证, 同时爬虫最大的问题是页面改版, 智能定位查找可以有效地处理这些情况.

总体来说 web 自动化测试框架要比移动端的复杂的多. 我们都在前进的道路上...

俩个问题请教:

  1. 对于重复元素的定位,怎样精确定位
  2. expectation 怎么写

这两个问题我来回答一下:

  1. 重复元素的定位,目前的处理是这样的,但是不一定可以覆盖大部分的情况 由于一般页面和业务流程有关的情况都会对应一个数据层的元素,目前公司使用 angular js 所以和数据层的绑定其实是很清晰的, 所以主要在 xpath 中加上 not(contains(@style,'display=none;')),这样虽然是重复元素,但是由于加了显示不显示的判断,其实在单个页面上总是唯一的了,但是这个可能和开发的实现紧密相关
  2. expectation 的话只能自己写了。

我也写了一套,一直没时间分享,等过阵一起探讨一下,我也是自动生成的测试代码,现在面临的问题就是:太自动的时候,有些东西会手局限

这个不错,可否加个 Q 请教一下相关东西?

大神可在?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册