小码奔腾

记录一些和自动化测试、CI有关的想法


  • Home

  • Archives

Jenkins执行完构建后testng插件找不到testng-results.xml

Posted on 2018-03-23

昨天把Jenkins(ver. 2.107.1,此处注意,这几乎是一个最新的Jenkins版本)安装在mac上,并建了一个简单的job,来体验下Jenkins+Maven+TestNG的效果,遇到这样一个有意思的问题,就是使用默认设置,也配置了TestNG的测试结果报告插件。

当Jenkins构建执行后,也可以正常找到指定的pom.xml文件,并执行Maven命令mvn test -PmyTestngProfile.xml来启动测试,但是测试完成后,却显示:

1
2
3
TestNG Reports Processing: START
Looking for TestNG results report in workspace using pattern: **/target/surefire-reports/testng-results.xml
Did not find any matching files.

花了点时间研究下,在Configure里的TestNG XML report pattern里,只有设置为完全绝对路径的时候,才能找到testng-results.xml。

揭晓答案吧,其实就是当构建进行到去寻找testng-results.xml的时候,当前目录并不是最开始的项目代码目录(pom.xml所在的目录),而是下面这个Jenkins在mac上安装后的一个默认workspace,然后以此为当前目录继续构建工作。

/Users/Shared/Jenkins/Home/workspace/myProject

解决办法是去构建的Configure -> General -> Advanced… -> Use custom workspace,也即使用自定义的工作空间目录,把这个目录设置为项目代码的真实地址,然后构建就会以此为当前目录,然后就可以顺利找到testng的报告文件了。

小结WEB接口测试

Posted on 2018-03-16

最近在做一个接口测试的更新,往里面添加了很多新的测试,连续加班两周,这里做一些总结。

被测设备介绍

这是一个针对某款智能设备的WEB API的测试,设备内运行OpenWRT系统,内部使用一套节点来记录很多设备信息和配置信息,设备也提供一个WEB GUI页面,可以通过打开其主页来做配置,也提供了一套WEB 接口API,来实现远程设置功能(其实就是给WEB GUI页面来调用)

测试框架介绍

测试用python实现,主要用到的有单元测试框架pytest,HTTP库requests,还有ssh连接用的pamamiko。

工作背景

这次工作之前,这套接口测试已经基本成型,运行了一段时间,最近是根据客户要求,往其中添加更多的set测试。可以理解为,已经有的测试多数是get测试,使用类似于cgi?get=wifi_status这样的query去获WEB的response,也即一个json串,再继续解析出设备上Wi-Fi的具体信息的值,再针对这些值做一些验证(和通过ssh登陆上设备调用shell命令获取的信息进行对比)。

总结与反思

  • 设计测试后的时候,有些是简单的举一反三和想当然,因此浪费了一些时间。测试中获取的信息有这样几个途径:一是向设备发送get请求,会收到带json串的response,json串中即是返回的信息,这一点是API文档中认可的。二是向设备发送set请求(确实是发送GET请求而不是POST,设计如此,也即发送GET请求,该请求中包含类似于&act=set&service=wifi&enabled=0这样的信息),也会收到带json串的response,也可以解析出返回的信息。然后我最开始错误的认为,这里json串的信息是可以用来验证结果,实际上这一点是API文档中并没有提到的,这里完全是我想当然,没有事先验证我的想法的结果就是,白白浪费了一些时间,走了弯路。
  • 加set测试的时候,我基本是一气code完,事后证明,还是应该做完一个后,想办法在本地跑起来,和熟悉这部分的同事做一个简单的评审,确保我的理解是和大家一致的,然后再继续做其他的部分。
  • 格式良好、易读的log打印会帮助迅速定位失败的测试,了解到测试执行的时候到底发生了什么事情。这点太重要了,这套接口测试其实也由我维护的(分析失败的测试,报bug或者更新测试代码),以前我就加了一个log打印,比如这个函数Util.printQueryAndJson(),望文生义,就是当发送一个request请求后,执行这个函数,把刚刚发送的request请求里的query字符串,和返回的response的json串打印出来。这里我就犯了个错误,以为set请求返回的json串是可以和get请求后返回的json一样,可以拿来做验证,实际上却不是。

下面是重构测试代码的时候,用到的一段python代码,用到函数里的内部函数特性,也称为闭包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python

def run():
a = {"name":"Li Ming", "age":23}

def inner_func():
a.clear()#此处python自动识别出这是一个外部变量a
a.update({"language":"python"})
inner_func()
print(a)


def new_inner_func():
a = {"language":"java"} #此处未能影响外部的变量a,只是一个内部函数里变量

new_inner_func()
print(a)

run()

执行结果是

1
2
{'language': 'python'}
{'language': 'python'}

测试框架设计学习笔记

Posted on 2018-01-28

Part One

开始学习Udemy上的一个课程Design Selenium Test framework from scratch-Architect level,随手写一些笔记。

课程第一部分主要在讲testNG,在项目上目前一直接触的是JUnit,用起来还比较方便,小结一下,用到了JUnit里以下特性:

  • Category,进行test case分类,方便同一份代码在不同环境下选择执行不同的test case
  • Rule,解决test case执行失败时,进行截图并上传至Jenkins
  • Runner,解决执行中动态决定是否skip特定的test case

目前在课程中学习到testNG的以下特点:

  • testNG本身是测试框架,功能比较丰富
  • testNG在Eclipse上有插件支持,使用方便
  • testNG可以使用testing.xml来控制测试执行控制,从上到下分了Suit -> test -> classes -> methods这样几个分层
  • methods标签下可以使用include、exclude标签来包含或排除某些 method,也即test case
  • method,也即test case命名的时候要遵循一个统一的规则,比如FunctionOneLogin(), FunctionTwoOpen()等,方便后续在选择执行的时候,用正则表达式筛选test case,例如name=”Fun.*”表示相应的class里所有以”Fun”开头的test case

Part Two

学习到第12节课程,了解到testNG的anotation有以下(and more):

  • @BeforeSuite suite是testNG使用的xml文件中的一个概念,也许可以理解为最上层的一个测试用例套件,一个suite可以包含有多个test
  • @BeforeTest test这里可以理解成一个测试模块(test module),一个test可包含多个测试类class
  • @BeforeClass class是指测试类,BeforeClass用来标记在一整个class测试类里的所有测试方法执行之前优先执行
  • @BeforeMethod method,就是一个测试用例,或者说是一个test类中的单个测试方法
  • @Test 用来注释某个测试类里的方法是一个测试方法,也即测试用例
  • @AfterMethod
  • @AfterClass
  • @AfterTest
  • @AfterSuite
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Test suite - 1">
<test thread-count="5" name="My Test module 1">
<classes>
<class name="com.main.MainTest01" />
<class name="com.test.NGTest01" />
</classes>
</test>
<test name="My Test module - 2">
<classes>
<class name="com.test.NGTest02" />
</classes>
</test>
</suite>

补充一下,testNG中的xml还可以使用package标签,含义不言自明。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="My Test Suite">
<test thread-count="5" name="My Test module">
<packages>
<package name="com.main"></package>
</packages>
</test>
</suite>

Part Three

学习到18节,完成了testNG的部分,其中包括了以下testNG的特性:

  • group,例如加上tag @Test(groups={“smoke”}),就表示这个test属于smoke group,可以属于多个group,然后相应的在xml文件中,做如下定义,即可使用group,这里可以include也可以exclude。
1
2
3
4
5
6
7
8
9
10
11
12
13
<suite name="Test - 1">
<test name="My Test modul">
<groups>
<run>
<include name="smoke" />
</run>
</groups>
<classes>
<class name="com.main.MainTest01" />
<class name="com.test.NGTest01" />
</classes>
</test>
</suite>
  • 定义test之间的依赖执行,例如加上tag @Test(dependsOnMethods={“Main03”}),即表示这个test依赖于另一个test Main03,将在其之后执行。
  • 参数化,例如以下tag的定义,和xml里的定义,这样即可让Main03 test使用xml里定义的变量值。
1
2
3
4
5
6
7
8
// 属于 com.main package里的 MainTest class
@Parameters({"URL1", "URL2"})
@Test
public void Main03(String url1, String url2) {
System.out.println("main 03");
System.out.println(url1);
System.out.println(url2);
}
1
2
3
4
5
6
7
8
9
10
<suite name="My Tet Suite">  
<parameter name="URL1" value="www.google.com" />
<parameter name="URL2" value="www.facebook.com" />

<test name="My Test modul">
<packages>
<package name="com.main" />
</packages>
</test>
</suite>
  • @DataProvider特性,具体还是见代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@DataProvider
public String[][] getData() {
String[][] data = new String[2][2];
data[0][0] = "1st-username";
data[0][1] = "1st-password";

data[1][0] = "2nd-username";
data[1][1] = "2nd-password";

return data;
}

//这个test会被执行2次,以便使用getData()里提供的,所有的2组测试数据
@Test(dataProvider="getData")
public void Main05(String username, String password) {
System.out.println(username);
System.out.println(password);
}
  • Listeners特性,有点像JUnit里的TestWatcher,典型用法就是失败后截图,具体见代码..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.main;

import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;

public class Listeners implements ITestListener {

@Override
public void onTestStart(ITestResult result) {
// do something when a test just start

}

@Override
public void onTestSuccess(ITestResult result) {
// do something when a test success
}

@Override
public void onTestFailure(ITestResult result) {
// do something when a test fail
}

@Override
public void onTestSkipped(ITestResult result) {
// do something when a test is skipped
}

@Override
public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
// TODO Auto-generated method stub
}

@Override
public void onStart(ITestContext context) {
// TODO Auto-generated method stub
}

@Override
public void onFinish(ITestContext context) {
// TODO Auto-generated method stub
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="My Tet Suite">
<listeners>
<listener class-name="com.main.Listeners" />
</listeners>

<parameter name="URL1" value="www.google.com" />
<parameter name="URL2" value="www.facebook.com" />

<test thread-count="5" name="My Test modul">
<packages>
<package name="com.main"></package>
</packages>
</test>
</suite>

实现Selenium Webdriver自动化测试中对失败测试进行截图并发布到Jenkins

Posted on 2018-01-23

在基于Selenium Webdriver(Java) + Junit4 + Jenkins 的web 自动化测试中,进行失败test的截图,同时发布到Jenkins上。

这两天在研究这个问题,这是一点总结,首先需要版本够高的Jenkins,并安装Junit Attachments plugin,同时注意要去Jenkins配置Additional test report features,选择启用 Publish test attachments,这样这个Junit Attachments插件可以帮助检查标准输出中,是否有特定格式的关于附件的log,然后依据log中的附件文件的地址,把该附件上传至Jenkins中。

还需要代码里的支持,我手上的web自动化测试,是基于Selenium Webdriver(Java) + Junit4实现的,这里需要实现一个Junit4里的 rule,我这是ScreenshotRule,继承于TestWatcher,改写其中的failed()方法,也即当 test case failed 的时候,执行截图操作,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.ibm.robot.web.testrules;

import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;

import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;

public class ScreenshotRule extends TestWatcher {

private WebDriver driver = null;
public ScreenshotRule(WebDriver driver) {
this.driver = driver;
}

@Override
protected void failed(final Throwable e, final Description description) {

String userDir = System.getProperty("user.dir");
String baseDir = userDir + "/failed-screenshots/"
+ description.getClassName() + "/";
String screenshotName = description.getClassName() + "."
+ description.getMethodName() + ".png";

File screen = null;
screen = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);

try {
File dir = new File(baseDir);
if(!dir.exists()) FileUtils.forceMkdir(dir);
File localFile = new File(baseDir + screenshotName);
FileUtils.copyFile(screen, localFile);

// Work with JUnit Attachments plugin to attach the files
// produced by Junit to Jenkins
System.out.println("[[ATTACHMENT|" + baseDir + screenshotName + "]]");
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}

最后还要去test的基类中,启用这个rule,注意这个rule类在使用的时候,需要传入test的基类中使用的driver,也即:

1
2
@Rule
public ScreenshotRule screenshotRule = new ScreenshotRule(driver);

让JUnit4里的test运行时动态决定执行或Skip

Posted on 2018-01-16

Part One

一组应用了Junit4的测试,需要增加一个动态判断,来决定是否跑test。我知道这组测试其实已经应用了junit的Category功能,来做测试组的初期分类,在跑这一整套测试的时候,执行环节会针对不同的被测产品给出一个custom参数,测试代码跑起来后会根据这个custom的值,来选择排除某些category和包括哪些category。

现在这个策略不够用了,因为之前标记好的category,同一个分类下还是有动态选择执行的需求,于是这次用到了Junit4中rule特性。

下面代码就实现了一个JUnit4中的rule。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.ibm.robot.web.util;

import com.ibm.robot.web.util.WebPropertiesLoader;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class NotRun5gCase implements TestRule {
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
String methodName = description.getMethodName();
loader = new WebPropertiesLoader();
String ap_2g = loader.getString("WIFI.ap.2G", "unknow");
String ap_5g = loader.getString("WIFI.ap.5G", "unknow");
if (!(ap_2G.equals(ap_5G) && methodName.contains("5GHz"))) {
base.evaluate();
}
}
};
}
}

从代码中可见,这条rule规定当ap_2g的值与ap_5g相等,同时test的方法名中包含5GHz的话,则不执行这个test。 然后把这条rule应用到具体的test中即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class WiFiTest{

@Rule
public TestRule notRun5gCase = new NotRun5gCase ();

@Before
public void setUp() throws Exception {
System.out.println("setup actions");
}

@After
public void tearDown() throws Exception {
System.out.println("tearDown actions");
}

@Test
public void testWiFi2GHz() {
//to test wi-fi 2g
}

@Test
public void testWiFi5GHz() {
//to test wi-fi 5g
}
}

Part Two

其实还是上次的问题,在上一篇中提到解决办法是应用JUnit4里的Rule来实现,今天继续研究了下,觉得还是不够好,因为实际需求是,需要在运行测试的时候去动态skip某些test,今天请教了下一位朋友,就有了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.junit4test;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(MyRunner.class)
public class AprilTest {

@Test
public void test1() {
assert("abc".equals("abc"));
}

@Test
public void test2() {
assert("abc".equals("abc"));
}

@Test
public void test3() {
assert("abc".equals("abc"));
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.junit4test;

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;

public class MyRunner extends BlockJUnit4ClassRunner {

public MyRunner(Class<?> klass) throws InitializationError {
super(klass);
}

@Override
protected boolean isIgnored(FrameworkMethod child) {
if (child.getName().contains("3")) { // 此处可做动态判断,来觉得是否skip该test
return true;
} else {
return false;
}
}

}

远程修改OpenWRT开发板中的文件

Posted on 2017-12-26

这两天收到一个任务,某接口测试的测试用例需要更新,需要在测试中修改开发板中的文件。

先白话下上层的一些东西,包括这套接口测试在内,可见的全部测试都挂在Jenkins上,测试对象是某刷了OpenWRT修改版的智能设备,Jenkins上游自然是自动编译生成build文件的各种job(有主线和针对不同需求的分支),一旦成功生成新的build文件,就会触发下游各种各样的测试,其中包括接口测试。

挂在Jenkins上的所有测试大都基于一个公司内部用shell实现的基础测试框架,包含了很多基本函数,像刷build,ssh连接执行命令,试探主机是否在线,获取主机版本号等等,然后到了具体测试的实现的时候,就各显神通了,大部分测试都由shell实现,web页面测试的有Selenium Webdriver和Casperjs。

废话完了说重点,要解决的问题就是要远程登录到OpenWRT开发板(智能硬件)上修改某文件( /etc/config/fw ),要找到该文件后在文件中某行下添加一行语句,比如找到 /etc/config/fw 文件,在文件找到 config firewall 这一行,再在这一行下插入一条 option blacklist ‘1’。

测试中是在基础测试框架中的 control.sh 里执行 python3 -m py.test –junitxml=./result/results.xml 来把测试拉起来,于是就查找python里做远程ssh登录执行Linux的方法,找到paramiko模块,最后实现代码大致以下,sed那一段挺麻烦,不过总算是找到解决方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
def sshclient_execmd(cmd):
try:
s = paramiko.SSHClient()
s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
s.connect(hostname=list.host_ip, port=22, username=list.username, password=list.password,timeout=5)
stdin, stdout, stderr = s.exec_command(command=cmd,timeout=30)
return s,stdout
except Exception as e:
print(e)
s.close()
return s, None

sshclient_execmd('''sed -i '/^config.*firewall/a\ option blacklist '"'1'"'' /etc/config/fw''')
1…456
Reed Xia

Reed Xia

记录一些和自动化测试、CI有关的想法

33 posts
32 tags
GitHub E-Mail
© 2018 Reed Xia
Hosted by GitHub Pages