Hexo


  • Home

  • Archives

Postman这些功能你知道吗

Posted on 2019-11-29

关于环境,你需要知道的事

变量

变量的作用域

作用范围见图,更高优先级的变量会覆盖低优先级的同名变量
nested variable scopes

如何使用变量

假设一个变量url = http://localhost,使用时输入就可以引用变量代表的值。因为变量都是以字符串形式保存的,你可以在任何可以输入的地方引用它,比如URL、parameters、headers、authorization、request body等。

打印变量

在脚本中输入console.log(foo)可以打印变量的值,在菜单栏view中选择Show Postman Console
variables logged

从文件中获取变量

postman支持读取csv和json格式的文件,从文件中得到相应变量的值,请记住,postman只允许在执行一个collection时选择数据文件,不能提前导入文件,在collection runner中的Data选项中选择你的CSV或者JSON文件
import data file

例如你的CSV文件中的数据是:

1
2
3
4
5
path, value
post, 1
post, 2
post, 3
post, 4

选择文件后,可以预览文件中的数据,看看是否是自己预期的:
preview data

执行的时候你会发现请求了4次,因为你的数据又4组
request debug tooltip

动态变量

postman提供了一系列的变量可以拿来直接用,比如你可以用来来表示一个uuid-v4风格的guid,用得到过去某个时间点的时间戳,
表示随机月份。查看所有动态变量。要注意的是,这些变量只能用于请求的URL、headers和body中。

关于请求,你需要知道的事

控制台(CMD/CTRL + ALT + C)

在菜单栏的View.Show Postman Console可以打开postman的控制台,里面包含我们请求的完整信息和在脚本中打印的日志。你可以在脚本中用console.log(),console.warn()等打印所需的日志。
show Postman console

授权

有时候API调用时需要进行授权,授权数据可能是在header、body或者request的parameters中,postman可以帮助我们自动添加这些数据。

如何添加授权信息

在具体的某个请求中,点击Authorization栏选择具体的授权类型,然后就可以在右侧编辑相关的值。如果整个collection都用的一样的授权,你可以在collection的更多菜单里选择编辑,然后在Authorization栏编辑授权信息。
Edit Collection
Collection Authorization

设置好了以后集合里的请求都默认用这种授权,你也可以在请求中单独再设置特殊的授权。设置完成后点击Preview Request可以预览授权信息在请求中的样式。
Preview Request

Bearer token

定义:为了验证使用者的身份,需要客户端向服务器端提供一个可靠的验证信息,称为Token,这个token通常由Json数据格式组成,通过hash散列算法生成一个字符串,所以称为Json Web Token(Json表示令牌的原始值是一个Json格式的数据,web表示是在互联网传播的,token表示令牌,简称JWT)
Bearer Token Preview

Basic auth

定义:基于用户名和密码的加密方式,他的格式是Basic <Base64 encoded username and password>,用户名和密码都是Base64编码的字符串,跟在文本“basic”后。
Basic Auth
Basic Auth Encoded

Digest auth

这种认证方式的一般流程是,先发送一个请求,服务端会返回一个401和若干信息,然后你再用得到的这些信息配合用户名和密码发送请求。postman会自动尝试填充advanced的选项中。
Digest Auth

OAuth 1.0

有时候需要用到第三方服务,postman也提供了OAuth授权方式。1.0的协议是consumer用一个key和secret向第三方服务请求access token,第三方服务会给到一个初始的的token,consumer用这个token获得用户的授权以后,就可以向第三方服务申请到access token,并用这个token来获取用户数据。

填好Consumer Key, Consumer Secret, Access Token, 和Token Secret,选择你想要放置这些字段的地方,body、header等
OAuth 1.0

OAuth 2.0

选择OAuth 2.0授权类型,选择放置的位置,右边点击Get New Access Token,填好必要字段,点击Request Token,如果成功了的话你会看到返回的token、过期日期等信息,有时也会有refresh token,点击Use Token使用token。
OAuth 2.0
Get Access Token

添加Cookies

管理cookies

cookies link

新建cookies

点击Add Cookie按钮,下面就出现一个预置的cookie字符串,修改后点击保存即可。
create a cookie

增加域名

给一个不在当前域名列表里的域名增加cookie,可以在上方输入域名(不输入端口号和http://),点击Add按钮即可,点击该域名然后增加cookie。
add a domain

添加证书

postman应用中可以查看和设置ssl证书。点击菜单栏中的设置图标,选择settings,切换到Certificates一栏,点击Add Certificate,在host中填写域名(不带协议),上传CRT file和KEY file,如果有Passphrase也写上,没有就空着。
add certificate

当你添加好证书以后,发送请求时回自动带上证书信息,打开控制台就可以看到:

Postman console view

捕获HTTP请求

使用interceptor捕获浏览器请求

第一种:chrome浏览器中安装插件Postman Interceptor就可以捕获浏览器中的http请求,启动postman的chrome app,就可以同步到你的postman app中查看。

第二种:安装Interceptor Bridge(下载地址)在postman 本地app中启动interceptor,看到他的status是INTERCEPTOR CONNECTED就可以了。

用postman作为代理捕获请求

将postman设置为代理后,他可以监听你电脑或手机上发出的请求,捕获并发送给服务端,然后接收服务端的响应再给客户端。他的原理可以用这张图理解:

postman capture proxy

第一步:打开代理,点击右上角的捕获图标,打开Capture Request开关,设置端口,默认5555,捕获的请求保存在History中。

proxy settings modal

第二步:保证你的手机和你的电脑处于同一Wi-Fi下,在手机Wi-Fi设置中设置好代理信息,IP是电脑的IP,端口是刚刚设置的5555。

然后切换到postman应用中就可以看到History栏中捕获到的请求。

关于代理

代理可以做很多事情:

  • Record all traffic between your machine and the internet
  • Reveal the contents of all requests, responses, cookies, and headers
  • Route traffic to specified internet locations
  • Debugging
  • Security from direct attacks
  • DevOps load balancing

standard web proxy

配置全局代理

如果你所有的应用都是用的相同的配置,那么可以选择使用全局代理。从设置中找到Proxy栏,选择Proxy Type,默认http和https请求都会被勾选上。设置代理服务器的IP地址和端口,设置代理服务器是否需要授权,需要的话还要填写用户名和密码。有些请求不需要经过代理的,可以在Proxy Bypass中填写。

custom proxy

配置系统代理

当打开系统代理时,意味着让postman遵循你系中的代理设置,在这种场景下,postman app等同于用系统默认配置去发送请求,系统代理把请求发送给服务端,服务端通过系统代理返回响应。系统代理是默认开启的 ,如果系统代理和全局代理是同时开启的状态,postman会使用全局代理的配置。

system proxy

发送SOAP请求

postman支持发送SOAP请求,用SOAP endpoint 作为请求的 URL,method选为post,body的类型选择为text/xml,在body中定义好SOAP Envelope, Header and Body即可。

使用GraphQL

什么是GraphQL?postman可以允许直接创建和储存GraphQL schemas,自动完成GraphQL查询。

发送GraphQL查询

body type选择GraphQL,将你的查询语句写进去就可以了。postman的编辑器允许你同时编辑查询语句和变量。

edit variables

导入GraphQL schemas

点击左侧的APIs栏,点击+ New API,你可以选择创建或者导入,添加完毕点击保存,然后去collection栏,在body中选择GraphQL,在输入查询语句时,就可以自动提醒你有哪些GraphQL。

自动填充GraphQL

当你创建/导入了GraphQL schema,在body type中GraphQL选项里就可以点击下拉菜单选择。

schema selection

可视化API响应(毕竟有意思的课题,抽时间继续看一下)

postman提供了一种可编码的方式来可视化地展示了请求的响应,

关于集合,你可能还不知道的事

支持markdown编辑

集合、文件夹和请求的描述都是支持markdown格式的。

collection details view

支持评论

可以在已保存的请求中点就Comments选项,就可以添加评论,还可以在评论中at你的项目成员,被at的成员会收到postman 的通知,评论文本也支持markdown格式。

post comments

版本控制

postman允许对集合进行版本控制,你可以像管理项目代码一样管理一个集合。

创建分支

点击集合的···更多菜单,选择Create a Fork,输入这个分支的标签和拉到的workspace,然后点击Fork Collection。注意,如果这个集合有相关的mocks或者monitors,分支是没有的,你要重新为分支创建。

fork collection

拉取变更

pull changes

合并变更

pull changes

解决冲突

有时候一个分支会被两个或更多的人进行更改操作,这个时候,在Merge change的时候需要选择使用哪个改动,注意,选择以后还是要点击Merge all changes进行合并。

color legends

关于脚本

脚本的执行顺序

在一个集合中,可能有集合的脚本、文件夹的脚本、请求的脚本,脚本还分为预执行的脚本和测试脚本,他们的执行顺序如下图所示,其中集合和文件夹的脚本,就在每一个请求之前的前后,都被执行一次。

workflow for request in collection

假设有两个请求Request1和Request2在同一个集合的同一个文件夹下,我们通过log信息,来看一下各级别脚本的执行顺序:

logs in console

Mac上解压rar文件

Posted on 2019-09-25

1、下载解压工具:RARLAB,选择最新版本中for macOS版本
2、下载好以后,在命令行用tar命令解压

1
tar -xvf /Users/xxx/Downloads/rarosx-5.8.b1.tar

3、创建软连接
(软连接可以理解为让你在任何目录下都能用某个指令)

1
2
3
4
5
6
7
# 切换到/usr/local/bin目录下
# 注意不能是/usr/bin目录,Mac上没有修改权限

# 软连接的命令是ln -s 想要链接的可执行文件 创建的命令
sudo ln -s /Users/xxx/Downloads/rar/rar rar

# 上述/Users/xxx/Downloads/rar/rar就是你rarlab解压后里面的rar可执行文件所在位置,rar是你为这个连接创建的命令

4、成功了以后你就可以在任意路径下使用rar e命令解压rar文件了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rar e /Users/xxx/Downloads/AndroidTool.rar 

# 执行结果
RAR 5.80 beta 1 Copyright (c) 1993-2019 Alexander Roshal 11 Sep 2019
Trial version Type 'rar -?' for help


Extracting from /Users/xxx/Downloads/AndroidTool.rar

Extracting psb.jpg OK
Extracting AXmlResourceParser.jar OK
Extracting dd-plist-1.16.jar OK
Extracting AndroidTool.exe OK
Extracting favicon.ico OK
Extracting 使用说明.txt OK
All OK

MAVEN管理TestNG项目(来自官网的资料)

Posted on 2019-09-20

添加TestNG依赖

根据官网的说明,Maven不支持的TestNG版本有:

1
2
- TestNG 5.14.3: Bad formatted pom.xml. 
- TestNG 5.14.4 and 5.14.5: TestNG is using a missing dependency (org.testng:guice:2.0). Excluding it, may break some features.

在项目的pom文件中配置TestNG,版本号可以替换成自己想用的:

1
2
3
4
5
6
7
8
9
10
<dependencies>
[...]
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.8</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>

如果使用的是<=5.11的版本,则应该这样配置,jdk版本替换成自己使用的:

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
[...]
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.11</version>
<scope>test</scope>
<classifier>jdk15</classifier>
</dependency>
[...]
</dependencies>

Maven Surefire Plugin插件

Maven Surefire Plugin是用于mvn生命周期的测试阶段的插件,设置一些参数就可以灵活地在testNG下对测试进行自定义,最基本的用法是在pom的plugin标签下添加surefire插件制定测试的xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
[...]
</plugins>

配置好了以后在自己的项目目录下执行mvn test命令,就可以执testng.xml文件中包含的测试用例,最后在你项目下的/target/surefire-reports中可以看到测试报告。

参数配置

TestNG中是可以用@Parameters注释来传参数的,Maven也是可以的,把参数当作系统属性,在surefire插件的configuration标签下增加相应的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<systemPropertyVariables>
<phoneNumber>11111111111</phoneNumber>
<password>123456</password>
</systemPropertyVariables>
</configuration>
</plugin>
[...]
</plugins>

这样就相当于传了一个phoneNumber和一个password参数,在代码中需要用到的地方依旧可以使用@Parameters({“phoneNumber”, “password”})注释来获得这两个参数。需要注意的是,这里的参数都将会以字符串的形式被传达。

分组测试

TestNG中可以对测试的方法或类进行分组,所以你可以在maven中配置测试的组,下面表示测试functest,perftest两个组。这里顺便提一下,surefire会从你项目目录src/test/java下寻找命名以Test.java结尾的类,把他们当作TestNG测试类。

1
2
3
4
5
6
7
8
9
10
11
12
<plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<groups>functest,perftest</groups>
</configuration>
</plugin>
[...]
</plugins>

当然你也可以用标签来排除测试组。

多线程测试

添加parallel参数以设置多线程,如果不写线程数默认是5:

1
2
3
4
5
6
7
8
9
10
11
12
13
</plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<parallel>methods</parallel>
<threadCount>10</threadCount>
</configuration>
</plugin>
[...]
</plugins>

TestNG 6.9.8(JRE 1.7)及surefire 2.19以上版本,可以多线程运行测试套件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<suiteXmlFiles>
<file>src/test/resources/testng1.xml</file>
<file>src/test/resources/testng2.xml</file>
</suiteXmlFiles>
<properties>
<property>
<name>suitethreadpoolsize</name>
<value>2</value>
</property>
</properties>
</configuration>
</plugin>
[...]
</plugins>

日志详细等

可以设置surefire.testng.verbose的等级,从0-10越来越详细,默认是0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</plugins>
[...]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
[...]
<properties>
<property>
<name>surefire.testng.verbose</name>
<value>10</value>
</property>
</properties>
[...]
</configuration>
</plugin>
[...]
</plugins>

————以上内容来自Apache Maven

Mac环境配置

Posted on 2019-08-27

什么叫环境

对于计算机来说,每当你给他一个命令,他会在环境变量指定的路径里寻找这个命令对应的文件夹,执行该文件夹里的文件以完成命令。当你安装好一个新的工具,你不添加环境,在运行命令的时候,指定路径,也是可以执行的,但是当你有很多工具或者懒得每次都要输入路径时,配置环境变量就能帮你简化很多工作。

查看环境变量

在shell中输入echo $PATH就可以查看当前已有的环境变量,比如我的:

1
/Library/Frameworks/Python.framework/Versions/3.7/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/Users/lulian/downloads/apache-maven-3.6.1/bin:/Applications/Wireshark.app/Contents/MacOS:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin:/Users/lulian/downloads/apache-maven-3.6.1/bin:/Users/lulian/Library/Android/sdk/tools:/Users/lulian/Library/Android/sdk/platform-tools

每个变量之间都是冒号分隔的,比如我在非java目录下执行java命令,他会在环境变量里找到java的安装位置,然后找到对应的文件运行,就可以执行我的命令。

环境变量区分系统级别和用户级别,现在Mac的系统基本上用的都是bash,环境变量写在固定的文件中,/开头的都是系统级别的,~/都是用户级别的,以下按照优先级排序:

1
/etc/profile /etc/paths ~/.bash_profile ~/.bash_login ~/.profile ~/.bashrc

/etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行,并从/etc/profile.d目录的配置文件中搜集shell的设置。
~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件。

添加环境变量

一般我们都在用户级别的文件~/.bash_profile中添加环境变量,path的格式是这样的:

1
2
3
4
5
6
7
8
9
10
# 每个PATH之间都是使用冒号隔开的
export PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:------:<PATH N>
# 或者
export PATH=${PATH}:<PATH 1>
export PATH=${PATH}:<PATH 2>

# export xxx可以理解为暴露一个名为xxx的变量,这里是PATH
# 当你想引用PATH变量时,就写成${PATH}

# 第一种将路径合并在一起,不方便删除,建议使用第二种,换行挨个设置

举个例子,你在文件里添加某个环境变量:

1
2
3
4
5
6
# 暴露一个名为TEST_PATH的变量,变量的值是某个路径
exoprt TEST_PATH=/Users/yeshou
# 将这个路径添加到PATH中,就是在现有的PATH后面加个冒号和TEST_PATH
export PATH=${PATH}:${TEST_PATH}
# 当然你也可以写成一行,但是两行看起来会比较清晰
export PATH=${PATH}:/Users/test

比如配置安卓环境:

1
2
3
4
5
# 暴露ANDROID_HOME变量指向你下载的安卓sdk文件夹路径
export ANDROID_HOME=/Users/lulian/Library/Android/sdk
# 告诉path到tools和platform-tools目录下去找可执行文件
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools

添加完毕以后需要source一下这个文件使它生效,然后你就可以打印查看ANDROID_HOME:

1
2
luliandeMBP-2:~ lulian$ echo $ANDROID_HOME
/Users/lulian/Library/Android/sdk

TestNG监听器

Posted on 2019-08-26

TestNG的几种监听器

TestNG提供了一些接口,实现了在测试的不同时期内进行一些操作,主要有ITestListener、ISuiteListener、IInvokedMethodListener2、IExecutionListener、ITestListener等,所有listener接口都是继承自ITestNGListener接口。使用监听器的时候,需要自己新建一个类,实现相应的接口,并实现每个接口中的抽象方法,下面先演示如何用监听器。

创建监听器类

下面的代码新建了一个MyListener类,implements ITestListener接口。需要实现ITestListener接口中的7个方法。他们传进来的参数有2种:ITestResult和ITestContext,后面再介绍他们。其他的监听器都是这样使用的,只不过需要实现的方法是不同的。

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
package com.test.listener;

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

public class MyListener implements ITestListener{

@Override
public void onTestStart(ITestResult result) {
System.out.println("ITestListener_onTestStart:");
}

@Override
public void onTestSuccess(ITestResult result) {
System.out.println("ITestListener_onTestSuccess:");
}

@Override
public void onTestFailure(ITestResult result) {
System.out.println("ITestListener_onTestFailure:");
}

@Override
public void onTestSkipped(ITestResult result) {
System.out.println("ITestListener_onTestSkipped:");
}

@Override
public void onTestFailedButWithinSuccessPercentage(ITestResult result) { }

@Override
public void onStart(ITestContext context) {
System.out.println("ITestListener_onStart:");
}

@Override
public void onFinish(ITestContext context) {
System.out.println("ITestListener_onFinish:");
}
}

不同监听器中的方法

  1. ITestListener:onTestStart、onTestSuccess、onTestSuccess、onTestFailure、onTestSkipped、onTestFailedButWithinSuccessPercentage、onStart、onFinish
  2. IExecutionListener:onExecutionStart、onExecutionFinish
  3. IInvokedMethodListener2:beforeInvocation、afterInvocation、beforeInvocation、afterInvocation
  4. ISuiteListener:onStart、onFinish
  5. IConfigurationListener2:onConfigurationSuccess、onConfigurationFailure、onConfigurationSkip、beforeConfiguration

ITestListener和ISuiteListener的方法都比较好理解,我们来看一下他们格子的执行时机。假设有一个测试基类和测试类,他们各自都有@BeforeTest、@AfterTest、@BeforeClass、@AfterClass、@BeforeSuite和@AfterSuite方法。从打印的顺序中看一下他们执行的顺序:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[RemoteTestNG] detected TestNG version 6.8.9
IExecutionListener_onExecutionStart
ISuiteListener_onStart
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
测试类的@BeforeSuite
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
ITestListener_onStart:
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
基类的@BeforeTest
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
测试类的@BeforeClass
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
测试类的@BeforeTest
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
测试类的@DataProvider
ITestListener_onTestStart:
IInvokedMethodListener2_beforeInvocation
测试类的@Test
IInvokedMethodListener2_afterInvocation
ITestListener_onTestSuccess:
ITestListener_onTestStart:
IInvokedMethodListener2_beforeInvocation
测试类的@Test
IInvokedMethodListener2_afterInvocation
ITestListener_onTestSuccess:
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
测试类的@AfterClass
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
测试类的@AfterTest
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
基类的@AfterTest
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
ITestListener_onFinish:
IConfigurationListener2_beforeConfiguration
IInvokedMethodListener2_beforeInvocation
测试类的@AfterSuite
IInvokedMethodListener2_afterInvocation
IConfigurationListener2_onConfigurationSuccess
ISuiteListener_onFinish

===============================================
TestAllSheets
Total tests run: 2, Failures: 0, Skips: 0
===============================================

IExecutionListener_onExecutionFinish

监听器的使用

一种方法是在具体的测试方法前加上@Listeners注释,注释的括号里指明用哪个listener类,例如:

1
2
3
4
5
6
7
@Listeners(MyListener.class)
public class listenerTest {
@Test
public void templistener1(){
System.out.println("i'm listenerTest1");
}
}

第二种是在testng.xml文件中为测试套件制定listener,可以一个也可以多个,具体方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="TestAllSheets">
<listeners>
<listener class-name="com.test.listener.MyListener"></listener>
<listener class-name="com.test.listener.MyForthListener"></listener>
<listener class-name="com.test.listener.MyFifthListener"></listener>
<listener class-name="com.test.listener.MySecondListener"></listener>
<listener class-name="com.test.listener.MyThirdListener"></listener>
</listeners>
<test name="TestAllSheets" preserve-order="true">
<classes>
<class name="com.test.mutipulapi.TestAllSheets"></class>
</classes>
</test>
</suite>

监听器接口方法中传入的参数

比如ITestListener接口中onTestStart方法会传进来一个ITestResult类型的参数,其他的还有ITestContext、IInvokedMethod等,来看看可以用他们做什么吧。

ITestResult类

先上源代码:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package org.testng;

/**
* This class describes the result of a test.
*
* @author Cedric Beust, May 2, 2004
* @since May 2, 2004
* @version $Revision: 721 $, $Date: 2009-05-23 09:55:46 -0700 (Sat, 23 May 2009) $
*
*/
public interface ITestResult extends IAttributes, Comparable<ITestResult> {

//
// Test status
//
public static final int SUCCESS = 1;
public static final int FAILURE = 2;
public static final int SKIP = 3;
public static final int SUCCESS_PERCENTAGE_FAILURE = 4;
public static final int STARTED= 16;

/**
* @return The status of this result, using one of the constants
* above.
*/
public int getStatus();
public void setStatus(int status);

/**
* @return The test method this result represents.
*/
public ITestNGMethod getMethod();

/**
* @return The parameters this method was invoked with.
*/
public Object[] getParameters();
public void setParameters(Object[] parameters);

/**
* @return The test class used this object is a result for.
*/
public IClass getTestClass();

/**
* @return The throwable that was thrown while running the
* method, or null if no exception was thrown.
*/
public Throwable getThrowable();
public void setThrowable(Throwable throwable);

/**
* @return the start date for this test, in milliseconds.
*/
public long getStartMillis();

/**
* @return the end date for this test, in milliseconds.
*/
public long getEndMillis();
public void setEndMillis(long millis);

/**
* @return The name of this TestResult, typically identical to the name
* of the method.
*/
public String getName();

/**
* @return true if if this test run is a SUCCESS
*/
public boolean isSuccess();

/**
* @return The host where this suite was run, or null if it was run locally. The
* returned string has the form: host:port
*/
public String getHost();

/**
* The instance on which this method was run.
*/
public Object getInstance();

/**
* If this result's related instance implements ITest, returns its test name, otherwise returns null.
*/
public String getTestName();

public String getInstanceName();

/**
* @return the {@link ITestContext} for this test result.
*/
public ITestContext getTestContext();
}

来试一下这些方法调用以后得到的值是什么,假设我在自己实现的ITestListener类中,onTestSuccess方法里用传入的result示例来打印一些信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void onTestSuccess(ITestResult result) {
System.out.println("ITestListener_onTestSuccess:");
System.out.println("result status:" + result.getStatus());
System.out.println("result host:" + result.getHost());
System.out.println("result instance name:" + result.getInstanceName());
System.out.println("result name:" + result.getName());
System.out.println("result start millis:" + result.getStartMillis());
System.out.println("result end millis:" + result.getEndMillis());
System.out.println("result name:" + result.getName());
System.out.println("result test name:" + result.getTestName());
System.out.println("result is success:" + result.isSuccess());

ITestNGMethod method = result.getMethod();
System.out.println("result method description:" + method.getDescription());

Object[] parameters = result.getParameters();
System.out.println("result parameters:" + parameters.getClass().getSimpleName());
}

下面是打印的结果

1
2
3
4
5
6
7
8
9
10
11
12
ITestListener_onTestSuccess:
result status:1
result host:null
result instance name:com.test.mutipulapi.TestAllSheets
result name:testDifferentSheets
result start millis:1566893197588
result end millis:1566893201413
result name:testDifferentSheets
result test name:null
result is success:true
result method description:null
result parameters:Object[]

ITestContext类

源代码:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package org.testng;

import com.google.inject.Injector;
import com.google.inject.Module;

import org.testng.xml.XmlTest;

import java.util.Collection;
import java.util.Date;
import java.util.List;


/**
* This class defines a test context which contains all the information
* for a given test run. An instance of this context is passed to the
* test listeners so they can query information about their
* environment.
*
* @author Cedric Beust, Aug 6, 2004
* @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
*/
public interface ITestContext extends IAttributes {

/**
* The name of this test.
*/
public String getName();

/**
* When this test started running.
*/
public Date getStartDate();

/**
* When this test stopped running.
*/
public Date getEndDate();

/**
* @return A list of all the tests that run successfully.
*/
public IResultMap getPassedTests();

/**
* @return A list of all the tests that were skipped
*/
public IResultMap getSkippedTests();

/**
* @return A list of all the tests that failed but are being ignored because
* annotated with a successPercentage.
*/
public IResultMap getFailedButWithinSuccessPercentageTests();

/**
* @return A map of all the tests that passed, indexed by
* their ITextMethor.
*
* @see org.testng.ITestNGMethod
*/
public IResultMap getFailedTests();

/**
* @return All the groups that are included for this test run.
*/
public String[] getIncludedGroups();

/**
* @return All the groups that are excluded for this test run.
*/
public String[] getExcludedGroups();

/**
* @return Where the reports will be generated.
*/
public String getOutputDirectory();

/**
* @return The Suite object that was passed to the runner
* at start-up.
*/
public ISuite getSuite();

/**
* @return All the test methods that were run.
*/
public ITestNGMethod[] getAllTestMethods();

/**
* @return The host where this test was run, or null if it was run locally. The
* returned string has the form: host:port
*/
public String getHost();

/**
* @return All the methods that were not included in this test run.
*/
public Collection<ITestNGMethod> getExcludedMethods();

/**
* Retrieves information about the successful configuration method invocations.
*/
public IResultMap getPassedConfigurations();

/**
* Retrieves information about the skipped configuration method invocations.
*/
public IResultMap getSkippedConfigurations();

/**
* Retrieves information about the failed configuration method invocations.
*/
public IResultMap getFailedConfigurations();

/**
* @return the current XmlTest.
*/
public XmlTest getCurrentXmlTest();

public List<Module> getGuiceModules(Class<? extends Module> cls);
public void addGuiceModule(Class<? extends Module> cls, Module module);

public Injector getInjector(List<Module> moduleInstances);
public void addInjector(List<Module> moduleInstances, Injector injector);
}

在他的onFinish方法中尝试打印一些信息:

1
2
3
4
5
6
7
8
@Override
public void onFinish(ITestContext context) {
System.out.println("ITestListener_onFinish:");
System.out.println("context name:" + context.getName());
System.out.println("context suite name:" + context.getSuite().getName());
System.out.println("context start date:" + context.getStartDate().toString());
System.out.println("context end date:" + context.getEndDate().toString());
}

打印结果:

1
2
3
4
context name:TestAllSheets
context suite name:TestAllSheets
context start date:Tue Aug 27 16:10:16 CST 2019
context end date:Tue Aug 27 16:10:31 CST 2019

IInvokedMethod类

这个类中提供了5个方法,都很简单,就不详细写了,五个方法的解释如下:

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
package org.testng;

/**
* An interface representing a method that has been invoked by TestNG.
*
* This interface is internal.
*/
public interface IInvokedMethod {

/**
* @return true if this method is a test method
*/
public abstract boolean isTestMethod();

/**
* @return true if this method is a configuration method (@BeforeXXX or @AfterXXX)
*/
public abstract boolean isConfigurationMethod();

/**
* @return the test method
*/
public abstract ITestNGMethod getTestMethod();

public ITestResult getTestResult();

/**
* @return the date when this method was run
*/
public abstract long getDate();

}

总结

监听器顾名思义就是帮助使用者从程序的不同环节中获取他需要的数据,TestNG的监听器使用起来比较简单,本身他的报告中提供的信息就不少,对于需要更加详细的数据的,就可以借助监听器,来丰富报告的数据。

Maven + TestNG + HttpsURLConnection + Excel接口测试

Posted on 2019-08-17

1、分析业务接口

  1. 一般我们都是把excel表格的设计放在第一个,但我认为先分析好你要测的接口,提前确定好你需要的数据是什么,得到的数据是什么,然后你才能设计相应的框架,并思考技术选型。
  2. 经过分析,我选用Java的HttpsURLConnection库,新建一个请求类,请求方法有get和post两种,get请求的参数放在url后面,post请求参数以json格式放在body中。
  3. 所以excel作为数据源中需要告诉我用例名称、请求方法、参数的key和value、请求预期的状态码,请求结果中需要检查的值。
  4. 下一步就是确定用什么方法读取excel表格,我选的是jxl这个库,它的一个特点是只能读取.xls格式的表格,所以我们的数据源也要保存成这个格式。
  5. 那么读取完数据以后,要把它适当加工成程序更容易理解的格式,这样方便直接用。所以可以设计一个测试数据的类,把每一条测试用例都抽象成一个对象,每次用到数据都是实例化这个类。
  6. 有了测试数据类和请求类,还差检验结果了。请求的类中可以得到请求的结果,并且对结果进行处理获得状态码和结果body,需要校验的值可以从测试数据类中拿,再去进行对比。
  7. 这样分析下来,我们需要另一个类,他做的事情就是实例化请求类,用我们写的工具读取excel中的数据,并且把数据给到测试数据类,得到一个测试数据对象,然后对比结果。那么这个类每次测一条用例的时候都要重复做一些动作,比如取数据、对比数据,很像before test和after test的概念,所以这里我选用了TestNG框架。
  8. 那么我们的项目大概分为几个部分:请求类、测试数据类、发请求的类、各种工具类。这里就很方便去用maven作为一个管理工具

当然,这里只是简单分析思路,后面具体操作的时候,还是会有很多需要思考的细节。

2、设计excel表格

根据上面的分析,我设计的表格的表头有:「Test No.」「enabled」「Testname」「Method」「description」「Protocol」「requestMethod」「Address」「code」「CheckPoint1」「CheckValue1」「CheckPoint2」「CheckValue2」「Key1」「Value1」「Key2」「Value2」「Key3」「Value3」「Key4」「Value4」「Key5」「Value5」

Test No.是序号,给自己看的,enabled表示要不要执行这条用例,这是后来在实践中发现需要这样的开关才加上的,Testname、Method和description是到时候写在测试报告里面当作日志。CheckPoint和CheckValue需要多少个是根据实际需求来的,可以无限扩展。value和key如果是在get方法里,那就用于url后的请求参数,如果是post,就是body中的参数,同样支持无限扩展。我的表格一开始也不是这样的,在实践的过程中慢慢的优化,最后稳定在这样的格式。

3、添加依赖

Maven项目中添加依赖只需要在它的pom.xml文件中添加就可以,最终我的文件是这样的:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>apitest2</groupId>
<artifactId>apitest2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>apitest2</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.4</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.8</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.jayway.jsonpath/json-path -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/net.sourceforge.jexcelapi/jxl -->
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6.12</version>
</dependency>

</dependencies>
</project>

在https://mvnrepository.com 中搜索你需要用的库,比如testng,选一个版本号,一般用稳定版本,然后详情页中maven栏下的代码复制到你pom.xml中的dependencies标签中,等待项目自己下载好这个库,就可以使用了。

4、工程结构

配置文件

配置文件和表格等都放在resource文件夹中,配置文件主要包含了host、excel的文件位置、请求所需要的header信息

src/main/java

src/main/java里有三个包:test.com.api、test.com.request、test.com.utils和com.test.testdata,分别放了测试类、请求类、工具类和测试数据类

test.com.api包

包下包含2个类,TestApi是所有测试类的基类,它的的作用是读取配置文件中的token信息,如果没有的话就调用registerToken方法写进去,如果有就去更新一下。

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
47
48
49
50
51
52
53
54
55
56
57
public class TestApi {
protected Properties prop;
protected String excelPath;
protected String host;
protected String url;
protected JSONObject responseBody;
protected int responseCode;
protected String accessToken = "";
protected String refreshToken = "";
protected Map<String, String> tokenHeader;

@BeforeTest
public void setAccessToken() throws Exception{
//先去读一下token文件看有没有
readTokenFile();
if(TextUtils.isEmpty(accessToken)) {
//如果文件里没有内容,就获取2个token写进去
GetToken.registerToken(host);
}else {
//文件里面有内容,要去用旧的token获取新的
GetToken.refreshToken(host, accessToken, refreshToken);
//换完了以后再更新一下TestApi这里的
readTokenFile();
tokenHeader = new HashMap<String, String>();
tokenHeader.put("x-jike-access-token", accessToken);
tokenHeader.put("x-jike-refresh-token", refreshToken);
}
}

public void readTokenFile() {
try (FileReader reader = new FileReader(System.getProperty("user.dir") + "/src/main/resource/x-jike-access-token.txt");
BufferedReader br = new BufferedReader(reader)) {
//建议这里就读2行,分别赋值
accessToken = br.readLine();
if (!TextUtils.isEmpty(accessToken)) {
refreshToken = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}

//构造函数
public TestApi() {
try {
//数据流的形式读取配置文件
prop = new Properties();
FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/src/main/resource/config.properties");
prop.load(fis);
} catch (Exception e) {
e.printStackTrace();
}

host = prop.getProperty("Host");
excelPath = prop.getProperty("testData");
}
}

另一个类是单接口测试的类,继承了TestApi类,它会实例化请求类和测试数据类,实现发送请求和获取响应结果的功能,代码如下

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class TraverseCases extends TestApi{
Object[][] excelData;
TestData testData;
HttpRequest request;
int codeExpected;
List<NameValuePair> bodyParams;
List<TestData> testDatas;
List<NameValuePair> checkPoints;

@BeforeClass()
public void setUp() throws Exception {
//读取excel
excelData = ExcelProcess.processExcel(excelPath);
//实例化请求类
request = new HttpRequest();
}

@DataProvider(name="testData")
private Iterator<Object[]> testDataProvider() throws IOException {
List<Object[]> result= new ArrayList<Object[]>();
List<TestData> alldata = new ArrayList<TestData>();
TestData td = null;
for(int i = 1; i < excelData.length; i++) {
//防止Excel里面有空行
if(excelData[i][0] == null) {
continue;
}
//实例化测试数据
td = new TestData(i, excelData);
if(td.isEnabled()) {
alldata.add(td);
}
}
Iterator it = alldata.iterator();
while(it.hasNext()){
result.add(new Object[] { it.next() });
}
return result.iterator();
}

@Test(dataProvider = "testData")
public void testDifferentCases(TestData testData) throws IOException {
//传入的参数就是每一条测试数据
if (testData != null) {
LogUtil.info("Testcase description:" + testData.getTestDescription());
bodyParams = testData.getKeys();
url = host + testData.getAddress();
LogUtil.info("Request url:" + url);
String body = "";
if(!bodyParams.isEmpty()){
body = TurnListToString.turnBodyToString(bodyParams);
}
//这里去要区分get还是post
//有个需要说明的,在表格里面如果是get方法,那么key和value代表params,如果是post,那它们就代表body里面的东西
String requestMethod = testData.getRequestMethod();
if(requestMethod.equals("post")) {
request.sendPostRequest(url, body, tokenHeader);
LogUtil.info("Request body:" + body);
}else if(requestMethod.equals("get")) {
request.sendGetRequest(url, bodyParams, tokenHeader);
LogUtil.info("Request params:" + body);
}
//verify
responseCode = request.getResponseCode();
LogUtil.info("ResponseCode:" + responseCode);
codeExpected = testData.getCodeExpected();
assertEquals(responseCode, codeExpected);
String checkPoint = "";
checkPoints = testData.getCheckPoints();

for (NameValuePair pair : checkPoints) {
checkPoint = pair.getName();
//如果检查点为空就不检查了
if(!TextUtils.isEmpty(checkPoint)) {
String checkValue = pair.getValue();
String result = request.getStringFromResponseJSON(checkPoint, checkValue.length());
LogUtil.info("CheckPoint:" + checkPoint);
LogUtil.info("CheckValue:" + checkValue);
LogUtil.info("ValueResult:" + result);
assertEquals(result, checkValue);
}
}
}
}
}

上面涉及到TestNG的东西,这里就不叙述了,TestNG的用法可以见我的另一偏文章:TestNG。(所有非java提供的类和方法,都是自己写的工具类,具体就不贴出来了)。如果后面要写多接口的测试,也可以用testng中的depend或者dataprovider注释来设计先后的逻辑。

test.com.request包

这个包下只有一个类HttpRequest,这个类里最主要的两个方法是sendGetRequest和sendPostRequest,分别用于发送get请求和post请求。另外还包含了一些对返回结果的处理,以及一些getter方法,代码如下:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class HttpRequest {
String content = null;
URL requestUrl;
HttpsURLConnection connection;
int responseCode;
JSONObject responseBody;
PrintWriter out = null;

public void sendGetRequest(String url, List<NameValuePair> params, Map<String, String> header) throws IOException {
//处理请求的参数,参数就是在url后面加上?key1=value1&key2=value2
url = url + TurnListToString.turnKeysToString(params);
requestUrl = new URL(url);
connection = (HttpsURLConnection) requestUrl.openConnection();
// 默认是 GET方式
//设置本次连接是否自动重定向
connection.setInstanceFollowRedirects(true);
//用自己写的工具类,去设置请求header
RequestHeader.setRequestHeader(connection);
//再添加额外的header
RequestHeader.addExtraHeader(connection, header);

// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成
connection.connect();
InputStream inputStream = getInputStream();

if (null == inputStream) {
inputStream = connection.getInputStream();
}
readResponseContent(inputStream);
connection.disconnect();
}

public void sendPostRequest(String url, String body, Map<String, String> header) throws IOException {
requestUrl = new URL(url);
connection = (HttpsURLConnection) requestUrl.openConnection();
// 设置是否向connection输出,因为这个是post请求,参数要放在http正文内,因此需要设为true
connection.setDoOutput(true);
// Read from the connection. Default is true.
connection.setDoInput(true);
// 默认是 GET方式
connection.setRequestMethod("POST");
// Post 请求不能使用缓存
connection.setUseCaches(false);
//设置本次连接是否自动重定向
connection.setInstanceFollowRedirects(true);

//用自己写的工具类,去设置请求header
RequestHeader.setRequestHeader(connection);
//再添加额外的header
RequestHeader.addExtraHeader(connection, header);

out = new PrintWriter(connection.getOutputStream());
out.print(body);
out.flush();

// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
connection.connect();
InputStream inputStream = getInputStream();

//如果返回的状态码不是200,需要从ErrorStream里读返回数据
if (null == inputStream) {
try {
inputStream = connection.getInputStream();
} catch (IOException e) {
inputStream = connection.getErrorStream();
}
}
readResponseContent(inputStream);
connection.disconnect();
}

public void readResponseContent(InputStream inputStream) throws IOException {
StringBuilder builder = new StringBuilder();
String line = null;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}
content = builder.toString();
responseBody = (JSONObject) JSON.parse(content);
}

public InputStream getInputStream() throws IOException {
InputStream inputStream = null;
if (!TextUtils.isEmpty(connection.getContentEncoding())) {
String encode = connection.getContentEncoding().toLowerCase();
if (!TextUtils.isEmpty(encode) && encode.indexOf("gzip") >= 0) {
inputStream = new GZIPInputStream(connection.getInputStream());
}
}
return inputStream;
}

public JSONObject getResponseBody() {
return responseBody;
}

//传入valueLength是为了防止第一层没有找到key的时候,可以方便从字符串中找到value的值
public String getStringFromResponseJSON(String key, int valueLength) {
String value = responseBody.getString(key);
//如果第一层没有找到key,那么就把responseBody转成String,看看是否包含key,这个方法有点笨,暂时没想到更好的
if (TextUtils.isEmpty(value)) {
if (content.contains(key)) {
int beginIndex = content.indexOf(key) + key.length() + 2;
int endIndex = 0;
Character valueBeginLetter = content.charAt(beginIndex) ;
//如果包含可以,就取出value,一般json字符串的格式是"key":"value"
//如果value是布尔型或者整型就没有引号
if (valueBeginLetter.toString().equals("\"")) {
//如果是引号的话从下一个位置开始找
beginIndex ++;
endIndex = beginIndex + valueLength;
}
value = content.substring(beginIndex, endIndex);
}
}
return value;
}

public int getResponseCode() throws IOException {
responseCode = connection.getResponseCode();
return responseCode;
}

public String getHeaderString(String headerKey) {
String headerValue = connection.getHeaderField(headerKey);
return headerValue;
}

public String getStringFromResponseJSON(String key) {
return responseBody.getString(key);
}
}
test.com.testdata包

包里有一个TestData类,抽象出了一条测试用例,话不多说,来看代码:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* TestData类储存的是一条具体的测试用例,构造函数中处理了从Excel表格中读出来的数据
* @author lulian
* address 请求地址
* checkpoint 检查点
* checkValue 检查值
* body 请求body或者params,GET请求时用作params,POST时用作BODY,但在testdata类里面都是List<NameValuePair>类型储存的
* codeExpected 期待的状态码
* testDescription 用例描述,用来写在报告里面的
* requestMethod 请求的方法
* enabled 表示用例是否要被执行
*/
public class TestData {
private String address;
private List<NameValuePair> body;
private List<NameValuePair> checkPoints;
private int codeExpected;
private String testDescription;
private String requestMethod;
private boolean enabled;

public TestData(int caseLine, Object[][] excelData) {
//根据列的名字找到列的序号,然后再获取对应的值
address = excelData[caseLine][ColNumber.getColNumber("Address", excelData)].toString().trim();
String codeContentFromExcel = excelData[caseLine][ColNumber.getColNumber("code", excelData)].toString().trim();
codeExpected = Integer.parseInt(codeContentFromExcel);
testDescription = excelData[caseLine][ColNumber.getColNumber("description", excelData)].toString().trim();
requestMethod = excelData[caseLine][ColNumber.getColNumber("requestMethod", excelData)].toString().trim();
enabled = excelData[caseLine][ColNumber.getColNumber("enabled", excelData)].toString().toString().trim().equals("1") ? true : false;
//用NameValuePair存储所有请求参数
body = new ArrayList<NameValuePair>();
for (int j = ColNumber.getColNumber("Key1", excelData); j < excelData[caseLine].length - 1; j = j + 2){
excelData[caseLine].length);
//因为每种请求的参数个数不确定,在这里进行非空判断
if(excelData[caseLine][j].equals("")){
break;
}
NameValuePair pair = new BasicNameValuePair(excelData[caseLine][j].toString(),excelData[caseLine][j+1].toString());
body.add(pair);
}
checkPoints = new ArrayList<NameValuePair>();
//checkPoint的数量从CheckPoint1开始一直到Key1之前2个
for (int j = ColNumber.getColNumber("CheckPoint1", excelData); j < ColNumber.getColNumber("Key1", excelData); j = j + 2) {
if(excelData[caseLine][j].equals("")){
break;
}
NameValuePair pair = new BasicNameValuePair(excelData[caseLine][j].toString(), excelData[caseLine][j+1].toString());
checkPoints.add(pair);
}
}

public String getAddress() {
return address;
}

public List<NameValuePair> getCheckPoints() {
return checkPoints;
}

public List<NameValuePair> getKeys() {
return body;
}

public int getCodeExpected() {
return codeExpected;
}

public String getTestDescription() {
return testDescription;
}

public String getRequestMethod() {
return requestMethod;
}

public boolean isEnabled() {
return enabled;
}
}
test.com.utils包

这个包里我写了很多工具类,就不一一列出代码了,挑一些个别的,比如从excel中读数据的类ExcelProcess:

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
public class ExcelProcess {
public static Object[][] processExcel(String filePath) throws IOException, JXLException{
//数据流读入excel
File file = new File(System.getProperty("user.dir") + filePath);
Workbook wb = Workbook.getWorkbook(file);
//读取特定表单,计算行数、列数
Sheet sheet = wb.getSheet("case");
int numberOfRow = sheet.getRows();
int numberOfCell = sheet.getColumns();
//将表单数据存入dtt对象
Object[][] data = new Object[numberOfRow][numberOfCell];
for (int i = 0; i < numberOfRow; i++) {
if (sheet.getCell(0, i).getContents().isEmpty() || sheet.getCell(0, i).getContents().equals("")) {
continue;
}
for (int j = 0; j < numberOfCell; j++) {
if(sheet.getCell(j, i) == null || sheet.getCell(j, i).equals("")) {
continue;
}
Cell cell = sheet.getCell(j, i);
data[i][j] = cell.getContents();
}
}
return data;
}
}

关于jxl库的使用,这里不赘述了,请参考我的另一篇文章Java Excel API——jxl常用类,里面覆盖到了常用的方法。

5、总结

  • 以上展示的是对单接口进的测试,并没有根据实际业务,写一个全链路的接口测试。
  • 有了这个框架,后面再有新的接口测试,只需要往表格中添加数据,不需要改动代码,可扩展性强
  • 后面会考虑放到Jenkins上定时跑,并且将测试报告发到邮箱中

TestNG的基本使用

Posted on 2019-08-04

TestNG的注释

@Test
被加注了@Test的方法,都是一个Method。

@BeforeMethod/@AfterMethod
此注释方法,会在所在测试类中所有Methods被调用前后运行一次,有多少Methods就会执行多少次。

@BeforeTest/@AfterTest
BeforeTest会在该测试类中没有一个Method被执行的时候执行,AfterTest会在所有Method执行完毕以后紧接着执行一次

@BeforeClass/@AfterClass
BeforeClass会在测试类中还没有任何一个方法被执行的时候执行,AfterClass会在测试类中所有方法都被执行过了以后执行(包括AfterTest)

@BeforeGroups/@AfterGroups
TestNG中Groups的概念是相对于测试方法而言的,将具有相似功能的测试方法分组,这样在定义测试用例的时候就可以以组为单位加入对应的测试方法。一般在定义测试方法或者测试类的同时,为其指定所属的组,一个测试方法可以属于多个组。

BeforeGroups将在之前运行组列表。此方法保证在调用属于这些组中的任何一个的第一个测试方法之前不久运行。

AfterGroups将在之后运行组列表。该方法保证在调用属于任何这些组的最后一个测试方法之后不久运行。

@BeforeSuite/@AfterSuite
Suite可以理解为一个测试套件,一个Suite中可以包含Groups、Class、Test。

BeforeSuite在该套件的所有测试都运行在注释的方法之前,仅运行一次。

@AfterSuite在该套件的所有测试都运行在注释方法之后,仅运行一次。

@DataProvider
标记一种方法来提供测试方法的数据。注释方法必须返回一个Object[][],其中每个Object[]可以被分配给测试方法的参数列表。 要从该DataProvider接收数据的@Test方法需要使用与此注释名称相等的dataProvider名称

@Factory
将一个方法标记为工厂,返回TestNG将被用作测试类的对象。该方法必须返回Object[]。

@Listeners
定义测试类上的侦听器。

@Parameters
描述如何将参数传递给@Test方法。

testng.xml书写

第一行是固定写法

1
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

一般要先声明一个suite的名字,用于描述将要运行的脚本集,比如:

1
2
3
4
5
<suite name="First suite" verbose="1" >
<test name="TestName" >
……
</test>
</suite>

如果测试脚本是基于group的,那么你就要描述是哪些,比如包含了什么、排除了什么,用include和exclude来表示。只要在include组中的方法都会被运行,exclude中的都不会,如果一个方法既在include又在exclude中,也会被运行。举例:

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
//比如有这样的一些测试方法,各自标注了属于什么分组
public class TestClass1 {
@Test(groups = {"group1", "group2"})
public void testMethod1() {
}

@Test(groups = {"group1"} )
public void testMethod2() {
}

@Test(groups = {"group2"})
public void testMethod3() {
}
}

//当然也可以在整个类之前加注group name,例如下面表示整个类中的方法都是属于checkin-test组的,其中method1又属于func-test组

@Test(groups = {"group3"})
public class TestClass2 {
@Test(groups = {"group4"})
public void testmethod4() { ... }
public void testmethod5() { ... }
}

<!-- testng.xml中定义如下 -->
<groups>
<run>
<include name = "group1" />
<exclude name = "group2" />
</run>
</groups>

这上述情况下testMethod1和testMethod2都会被运行,其余的没有被include也没有被exclude的,都不会被运行。上面是按照method所属group为依据来将测试方法分组,也可以从包、类、方法的角度来区分,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<suite name="First suite" verbose="1" >
<test name="TestName" >

<!-- 选择一个包 -->
<packages>
<package name = "packageName" />
</packages>

<!-- 选择一个类 -->
<classes>
<class name = "className" />
</classes>

<!-- 选择一个方法 -->
<classes>
<class name = "className" />
<methods>
<include name = "methodName" />
<exclude name = "methodName" />
</methods>
</class>
</classes>
</test>
</suite>

预期异常测试

假设有这样的测试场景:一个方法会抛出一个异常,我们需要另一个方法去获得此异常,并进行一些操作,如果能正常处理,也是一个通过运行的case。随便举个例(不一定符合真实场景):

1
2
3
4
5
6
7
@Test(expectedExceptions = {FileNotFoundException.class})
public void whatToThrow() throws FileNotFoundException {
File f = null;
if (f == null) {
throw new FileNotFoundException();
}
}

上面的运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
[RemoteTestNG] detected TestNG version 6.14.3
PASSED: whatToThrow

===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

忽略测试

如果一个测试方法还没写好或者不想运行,可以用注释的方法忽略它,这样就不会被执行,比如:

1
2
3
4
@Test(enabled = false)
public void test(){
……
}

超时测试

“超时”表示如果单元测试花费的时间超过指定的毫秒数,那么TestNG将会中止它并将其标记为失败。例如:

1
2
3
4
5
6
@Test(timeOut = 3000)
public void timeoutTest() {
while(true) {

}
}

这个方法运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FAILED: timeoutTest
org.testng.internal.thread.ThreadTimeoutException: Method com.test.api.ExceptionTest.timeoutTest() didn't finish within the time-out 3000 at
……
……

===============================================
Default test
Tests run: 1, Failures: 1, Skips: 0

===============================================


===============================================
Default suite
Total tests run: 1, Failures: 1, Skips: 0

===============================================

套件测试

套件测试无法在代码中定义,不像groups,但是它可以用一个xml文件表示,一个套件里面可以包含多个测试。是testng.xml的根标记,它的属性包含:

属性 描述
name 套件的名称,是强制属性
verbose 运行级别
parallel TestNG是否运行不同的线程来运行套件
thread-count 如果启用并行模式时所使用的线程数
annotations 测试中使用的注释类型
time-out 所有测试方法的默认超时时间

依赖测试

在@Test注释中使用dependsOnMethods和dependsOnGroups来进行依赖测试。只有所依赖的方法执行成功以后,才会执行此方法。比如:

1
2
3
4
5
6
7
8
9
10
//method2只有在method1执行成功以后才会被执行,否则就会忽略
@Test
public void method1() {
System.out.println("This is method 1");
}

@Test(dependsOnMethods = { "method1" })
public void method2() {
System.out.println("This is method 2");
}

参数化测试

可以用testng.xml将参数传给测试方法,比如在testng.xml中写了:

1
2
3
4
5
6
7
8
9
10
<suite>
<test name="testPara">
<parameter name="para1" value="value1"/>
<parameter name="para2" value="value2"/>

<classes>
<class name="com.test.api.ExceptionTest"/>
</classes>
</test>
</suite>

那么在ExceptionTest这个类的测试方法中就可以使用para1和para2这两个参数,要在方法前加注@Parameters,比如:

1
2
3
4
5
6
@Test
@Parameters({"para1", "para2"})
public void testPara(String para1, String para2) {
System.out.println(para1);
System.out.println(para2);
}

另一种传参的方式是通过@DataProvider,新建一个类,加注@DataProvider,要求必须返回一个Object[][]类型,使用这个返回值时,在@Test后面加上dataProvider的name,举个最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
@DataProvider(name = "provider")
public Object[][] provideData(){
return new Object[][] {{1, 1}, {1, 2}, {1, 3}};
}

@Test(dataProvider = "provider")
public void printData(int i, int j) {
System.out.println("i:" + i + "\t" + "j:" + j);
}
}

比如上述的例子,执行的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[RemoteTestNG] detected TestNG version 6.14.3
i:1 j:1
i:1 j:2
i:1 j:3
PASSED: printData(1, 1)
PASSED: printData(1, 2)
PASSED: printData(1, 3)

===============================================
Default test
Tests run: 3, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================

@DataProvider要求必须返回一个Object[][]类型,所以如果是Map对象也是可以的,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
@DataProvider(name = "provider1")
public Object[][] provideData1(){
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value2");
return new Object[][] {{map}};
}

@Test(dataProvider = "provider1")
public void printData1(Map<String, String> map) {
for(Map.Entry<String, String> entry : map.entrySet()){
System.out.println(entry.getKey() + "\t" + entry.getValue());
}
}
}

上面这个例子,测试的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[RemoteTestNG] detected TestNG version 6.14.3
key1 value1
key2 value2
key3 value2
PASSED: printData1({key1=value1, key2=value2, key3=value2})

===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

如果一个DataProvider提供给多个test方法数据,也可以根据调用的方法名称,来提供不同的数据,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
@DataProvider(name = "provider2")
public Object[][] provideData2(Method method){
Object[][] obj = null;
if (method.getName() == "test1") {
obj = new Object[][] {{"case1", "valu1"}};
}else if (method.getName() == "test2") {
obj = new Object[][] {{"case2", "valu2"}};
}
return obj;
}

@Test(dataProvider = "provider2")
public void test1(String name, String value) {
System.out.println(name + "\t" + value);
}

@Test(dataProvider = "provider2")
public void test2(String name, String value) {
System.out.println(name + "\t" + value);
}
}

上面例子的执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[RemoteTestNG] detected TestNG version 6.14.3
case1 valu1
case2 valu2
PASSED: test1("case1", "valu1")
PASSED: test2("case2", "valu2")

===============================================
Default test
Tests run: 2, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

DataProvider还可以返回一个数组迭代器类型,我个人比较喜欢这种方式,比较容易理解,举个例子:

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
public class TraverseCases extends TestApi{
//假设属性都已经定义好了(这里省略不写了)

@BeforeClass()
public void setUp() throws Exception {
//假设应该做的初始化工作都在这里做了(这里省略不写了)
}

@DataProvider(name="testData")
private Iterator<Object[]> testDataProvider() throws IOException {
List<Object[]> result= new ArrayList<Object[]>();
List<TestData> alldata = new ArrayList<TestData>();
TestData td = null;
for(int i = 1; i < excelData.length; i++) {
td = new TestData(i, excelData);
alldata.add(td);
}
Iterator it = alldata.iterator();
while(it.hasNext()){
result.add(new Object[] { it.next() });
}
return result.iterator();
}

@Test(dataProvider = "testData")
public void testDifferentCases(TestData testData) throws IOException {
//这里每一次传进来的都是迭代器取的下一个测试数据
if (testData != null) {
//这里你就可以对测试数据进行操作了(这里省略不写了)
}
}
}

上面这样的写法,执行的结果会是这样的,passed:方法名(数据来源):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PASSED: testDifferentCases(com.test.utils.TestData@6150c3ec)
PASSED: testDifferentCases(com.test.utils.TestData@139982de)
PASSED: testDifferentCases(com.test.utils.TestData@3eb738bb)
PASSED: testDifferentCases(com.test.utils.TestData@74e52ef6)
PASSED: testDifferentCases(com.test.utils.TestData@bcec361)
PASSED: testDifferentCases(com.test.utils.TestData@3d285d7e)
PASSED: testDifferentCases(com.test.utils.TestData@2aceadd4)
PASSED: testDifferentCases(com.test.utils.TestData@7817fd62)
PASSED: testDifferentCases(com.test.utils.TestData@77f1baf5)

===============================================
Default test
Tests run: 9, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 9, Failures: 0, Skips: 0
===============================================

@Test中其余属性

除了上面提到的一些属性,其余还有:

@Test(alwaysRun = true) ,这个属性一般和dependsOnMethods一起用。如果这个属性设置了true,那么这个方法总是会被执行,哪怕它依赖的方法执行失败了,他也会被执行。如果它没有依赖的方法,那这个属性设置了也没用。不设置的情况下默认false。

@Test(description = “”) 这个属性是字面意思,如果设置了会出现在html报告里面,如果ourput等级大于等于2,也会出现。

@Test(ignoreMissingDependencies = true) 如果有依赖的方法,且该方法没有被执行,那这个方法也就被执行。

@Test(invocationCount = 2) 代表这个方法被执行多少次,默认是1次。

@Test(invocationTimeOut = 3000) 表示这个方法被执行的最高时长(一般是设置了执行次数,那么就表示执行这么多次总共能花的时间)。如果不设置执行次数,这个属性不会生效。如果超过了设置的时长还没有执行完,那么就当作方法failed。

@Test(skipFailedInvocations = true) 如果这个属性设置了true,且运行次数大于1,那么每次运行失败时,不标记成fail,而后跳过。

@Test(threadPoolSize = 4) 线程数,如果运行数没有设定的话,这个属性也不会生效。

TestNG的报告

个人认为报告这块不要话太多精力去装饰,如果不是需要上交报告给其他部门或者领导,只是自己看的话,直接明了就好。我的一个需求是执行方法的时候添加很多日志信息,不管成功失败,最后都方便查看追踪。只要适当添加一些log信息,我觉得这样的报告也够了。我在utils包里面添加了一个简单的类:

1
2
3
4
5
public class LogUtil {
public static void info(String msg) {
Reporter.log(msg.toString());
}
}

这样产生的报告里面就会有你想要打印的信息

Java Excel API——jxl常用类

Posted on 2019-07-20

简介


Java Excel API提供了对Excel读、写、修改的方法。只要你的电脑上有Java虚拟机,就可以使用jxl。

部分特性


总的来说,jxl可以从Excel 95、97、2000、XP、2003中读取数据,可以用Excel 97或更老的版本读写,可以生成
Excel 2000格式的表格。支持多种内容格式和单元格样式,也支持很多语言。能够复制图表、插入、复制图片等。

常用类


WorkBook

· Represents a Workbook. Contains the various factory methods and provides a variety of accessors which provide access to the work sheets.

· 创建一个Workbook对象
Workbook根据传入参数不同,提供了四种类方法来返回一个WorkBook类型的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1、传入java.io.File file类型参数
File file = new File(filePath);
Workbook wb = WorkBook.getWorkbook(file);

//2、传入java.io.File file和WorkbookSettings
//WorkbookSettings是用来设置workbook的属性的,不使用它时则会使用默认的设置
WorkbookSettings ws = new WorkbookSettings();
ws.setEncoding("ISO-8859-1");
Workbook wb = WorkBook.getWorkbook(file, ws);

//3、传入InputStream
InputStream is = new FileInputStream(file);
Workbook wb1 = Workbook.getWorkbook(is);

//4、传入InputStream和WorkbookSettings
略

Sheet

· Represents a sheet within a workbook. Provides a handle to the individual cells, or lines of cells (grouped by Row or Column)

· 创建Sheet对象,假设已经实例化了一个Workbook对象wb

1
2
3
4
5
//1、传入sheet的名字
Sheet sheet = wb.getSheet("sheet1");

//2、传入sheet的序号,从0开始
Sheet sheet = wb.getSheet(0);

· 对Sheet对象可进行的一些操作

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
//获取sheet的行数和列数
int numberOfRow = sheet.getRows();
int numberOfCol = sheet.getColumns();

//获取某一列的CellView
CellView cv = sheet.getColumnView(0);

//CellView是用来设置或者获取Cell某些属性的,比如format和size
String backgroudColor = cv.getFormat().getBackgroundColour().getDescription();
int size = cv.getSize();

//获取sheet的名字
String sheetName = sheet.getName();

//获取sheet中图片的数量
int picNum = sheet.getNumberOfImages();

//获取某一行的CellView
CellView cv = sheet.getRowView(0);

//获取sheet的设置
SheetSettings ss = sheet.getSettings();

//SheetSettings是用来设置或者获取sheet某些属性的
//返回水平冻结的行序号,没有就是0
int frozenRowNum = ss.getHorizontalFreeze();

Cell

· Represents an individual Cell within a Sheet. May be queried for its type and its content

· 创建Cell对象,假设已经有了Sheet对象sheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1、根据cell内容
Cell cell1 = sheet.findCell("test");

//2、根据所给的内容、查找范围和查找的顺序(正着找还是倒着找)
Cell cell2 = sheet.findCell("test", 0, 0, 10, 1, true);

//3、根据所给的pattern、查找范围和查找的顺序
Cell cell3 = sheet.findCell(Pattern.compile("Key.*"), 0, 0, 12, 1, false);

//4、根据行、列序号
Cell cell4 = sheet.getCell(column, row);

//5、根据单元格的位置
Cell cell5 = sheet.getCell("B2");

//6、根据列号/行号,获得一列/行的单元格,得到一个Cell[]
Cell[] cells = sheet.getColumn(0);
Cell[] cells = sheet.getRow(0);

· 对Cell对象可进行的一些操作,假设已经有了Cell对象cell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//获取cell的特性
CellFeatures cf = cell.getCellFeatures();

//CellFeatures is container for any additional cell features
String cellComment = cf.getComment();
String dataValidation = cf.getDataValidationList();

//获取cell的format
CellFormat cf = cell.getCellFormat();

//format里可以获取背景色、字体等
String backgroundColor = cf.getBackgroundColour().getDescription();
String font = cf.getFont().getName();

//获取行号和列号
int colNumber = cell.getColumn();
int rowNumber = cell.getRow();

//获取单元格的内容
String contents = cell.getContents();

//获取CellType
CellType ct = cell1.getType();
String type = ct.toString();

写入Excel


· 主要是jxl.write下的一些类

WritableWorkbook

· 创建一个WritableWorkbook对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1、根据File文件创建
WritableWorkbook wwb = Workbook.createWorkbook(file);

//2、根据File的名字,和Workbook生成一份复制的对象
WritableWorkbook wwb = Workbook.createWorkbook(file, wb);

//3、根据File和WorkbookSettings
WritableWorkbook wwb = Workbook.createWorkbook(file, ws);

//4、根据File、Workbook和WorkbookSettings
WritableWorkbook wwb = Workbook.createWorkbook(file, wb, ws);

//5、根据OutputStream
WritableWorkbook wwb = Workbook.createWorkbook(os);

//还有三种是OutputStream + Workbook、OutputStream + WorkbookSettings、OutputStream + Workbook + WorkbookSettings,不再一一举例

· 对WritableWorkbook对象可进行的一些操作,假设已经有了WritableWorkbook对象wwb

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
//复制sheet,三个参数分别代表:要拷贝的sheet的序号/名字、新sheet的名字、新sheet的序号
wwb.copySheet(1, "test", 1);
wwb.copySheet("test", "test1", 1);

//创建sheet,传入名字和序号
wwb.createSheet(name, index);

//获取表中的sheet数量
int sheetNumber = wwb.getNumberOfSheets();

//获取表中所有sheet的名字
String[] namesOfAllSheets = wwb.getSheetNames();

//根据位置,获取特定的WritableCell
WritableCell wc = wwb.getWritableCell("Sheet1!A4");

//从另一个sheet复制一个新sheet,三个参数是:新sheet的名字、新sheet的序号、copy的sheet
WritableSheet ws = wwb.importSheet(name, index, s);

//移动一个sheet的位置,返回移动后的sheet
WritableSheet ws = wwb.moveSheet(int fromIndex, int toIndex);

//移除一个sheet
wwb.removeSheet(int index);

//设置一个新的输出文件
wwb.setOutputFile(java.io.File fileName);

//写
wwb.write();

//关
wwb.close();

WritableSheet

· 创建WritableSheet,假设有个WritableWorkbook的对象wwb

1
2
3
4
//根据序号或名字来创建,或者直接获取所有的sheet,得到一个sheet数组
WritableSheet ws = wwb.getSheet(index);
WritableSheet ws = wwb.getSheet(name);
WritableSheet[] ws = wwb.getSheets();

· 对WritableSheet的操作,假设有一个WritableSheet的对象ws

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
//向sheet中添加一个WritableCell
ws.addCell(cell);

//向sheet中添加一个WritableImage
ws.addImage(image);

//获得WritableImage
WritableImage wi = ws.getImage(i);

//获取图片数量
int picNumber = ws.getNumberOfImages();

//插入一列/行
ws.insertColumn(col);
ws.insertRow(row);

//移除一列/行
ws.removeColumn(col);
ws.removeRow(row);

//移除图片
ws.removeImage(wi);

//设置列/行的格式,需要传入CellView
ws.setColumnView(col, view);
ws.setRowView(col, view);

//设置列宽、行高
ws.setColumnView(col, width);
ws.setRowView(col, height);

//设置sheet的名字
ws.setName(name);

WritableCell

· 创建WritableCell,假设已有WritableSheet对象ws

1
2
3
//根据位置或者行、列序号
WritableCell wc1 = ws.getWritableCell(loc);
WritableCell wc2 = ws.getWritableCell(column, row);

· 对WritableCell的操作,假设已有WritableCell对象wc

1
2
3
4
5
6
7
8
//深度copy一个WritableCell,得到一个新WritableCell,但是这个新cell还未被加入sheet中,col,row代表要拷贝到的位置
WritableCell wcNew = wc.copyTo(col, row);
ws.addCell(wcNew);

//设置cell的特性
WritableCellFeatures wcf = new WritableCellFeatures();
wcf.setComment("评论");
wc.setCellFeatures(wcf);

参考


上面简单列举jxl中的一些类和方法,更多请参考jxl API

John Doe

8 posts
5 tags
© 2019 John Doe
Powered by Hexo
|
Theme — NexT.Pisces v5.1.4