1.线程
1.1 线程概念
线程,程序执行流的最小执行单位,是行程中的实际运作单位,经常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
1.2 线程生命周期
1.3 实现多线程的几种方式
线程的4种实现方法
1: 继承Therad类
2:实现Runnable接口
3:实现Callable接口
4:使用线程池
1.4线程笔试题:以下两个程序的运行结果
package com.thread;
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
pong();
}
};
thread.run();
System.out.println(Thread.currentThread().getName()+" ping");
}
static void pong(){
System.out.println(Thread.currentThread().getName()+" pong");
}
}
// 运行结果:
main pong
main ping
原因是:未把线程提交给CPU,线程启动是:thread.start()
package com.thread;
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
pong();
}
};
thread.start();
System.out.println(Thread.currentThread().getName()+" ping");
}
static void pong(){
System.out.println(Thread.currentThread().getName()+" pong");
}
}
// 运行结果:
Thread-0 pong
main ping
2 线程池
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
2.1 常用的几种线程池
2.1.1 newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
//可以最大开65536, 短任务
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index+Thread.currentThread().getName());
}
});
}
cachedThreadPool.shutdown();
}
}
2.1.2newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
//固定线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index+Thread.currentThread().getName());
}
});
}
fixedThreadPool.shutdown();
}
}
2.1.3 newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
//默认一个
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
System.out.println(index+Thread.currentThread().getName());
}
});
}
singleThreadExecutor.shutdown();
}
}
2.1.4 newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
package com.thread;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService service=Executors.newScheduledThreadPool(3);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1,3,TimeUnit.SECONDS);
}
}
2.2 线程池应用到项目
package com.api.thread;
import com.utils.EmailUtils;
import com.utils.ExcelToMapUtils;
import com.utils.ParamsUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ApiThreadTest {
private static final Logger logger = LoggerFactory.getLogger(ApiThreadTest.class);
public static String path = System.getProperty("user.dir")+ File.separator+"data"+File.separator+"apitest2.xlsx";
public static List<String> emailList = new ArrayList<>();
public static void main(String[] args) {
testCaseParams(path,1);
}
private static void testCaseParams(String Path, int index) {
try {
List<Map<String, Object>> mapList = ExcelToMapUtils.importExcel(path, 1);
CountDownLatch latch = new CountDownLatch(mapList.size());
ExecutorService executorService=Executors.newCachedThreadPool();
for (Map<String, Object> map : mapList) {
ParamsUtils.addMap(map);
ApiTask apiTask = new ApiTask(path, latch);
//apiTask.start();
executorService.submit(apiTask);
}
latch.await();
executorService.shutdown();
String[] emailArray = new String[emailList.size()];
int j = 0;
for (String string : ApiThreadTest.emailList) {
emailArray[j++] = string;
}
System.out.println(":::"+emailArray[0]);
EmailUtils.sendEmailsWithAttachments("测试结果", "请查收", emailArray);
}catch (Exception e) {
logger.error(e.getMessage());
}
}
}
3.锁
3.1 Synchronized 关键字
3.1.1 原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
3.1.2 三种应用方式
普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
package com.thread;
public class SynchronizedTest {
private static String lock ="lock";
private static int count =0;
public static void main(String[] args) {
//10个线程
for (int i = 0; i < 100; i++) {
new Thread(() -> {
synchronized (lock){
count++;
System.out.println(Thread.currentThread().getName()+" count: "+count);
}
}).start();
}
}
}
3.2 Lock锁
Lock锁,可以得到和 synchronized一样的效果,即实现原子性、有序性和可见性。相较于synchronized,Lock锁可手动获取锁和释放锁、可中断的获取锁、超时获取锁。
Lock 是一个接口,两个直接实现类:ReentrantLock(重入锁), ReentrantReadWriteLock(读写锁)
3.2.1 ReentrantLock锁
ReentrantLock,意思是“可重入锁”,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
ReentrantLock获取锁定三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
3.3 可重入锁
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。
package com.thread;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest implements Runnable {
ReentrantLock lock = new ReentrantLock();
public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
ReentrantLockTest ss = new ReentrantLockTest();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
package com.thread;
public class LockTest implements Runnable {
public synchronized void get(){
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getId());
set();
}
public synchronized void set(){
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getId());
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
LockTest ss=new LockTest();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
3.4 Lock和synchronized的区别
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的
3.5 死锁
3.5.1 什么是死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
3.5.2 实现死锁
package matongxue;
public class DeadLock1 extends Thread{
Object s1;
Object s2;
public DeadLock1(Object s1, Object s2){
this.s1=s1;
this.s2=s2;
}
@Override
public void run() {
synchronized (s2) {
try {
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
System.out.println("first");
}
}
}
}
package matongxue;
public class DeadLock2 extends Thread{
Object s1;
Object s2;
public DeadLock2(Object s1, Object s2){
this.s1=s1;
this.s2=s2;
}
@Override
public void run() {
synchronized (s1) {
try {
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
System.out.println("second");
}
}
}
}
package matongxue;
public class DeadLockTest {
public static void main(String[] args) {
Object o1 =new Object();
Object o2 = new Object();
DeadLock1 deadLock1 = new DeadLock1(o1,o2);
DeadLock2 deadLock2 = new DeadLock2(o1,o2);
deadLock1.start();
deadLock2.start();
}
}
3.5.3 监控死锁
VisualVM监控死锁
4. ThreadLocal
4.1 ThreadLocal简介
ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问, 通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
我们知道有时候一个对象的变量会被多个线程所访问,这时就会有线程安全问题,当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。
当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
4.2 ThreadLocal 方法使用详解
public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本
public void set(T value) { } //set()用来设置当前线程中变量的副本
public void remove() { } //remove()用来移除当前线程中变量的副本
protected T initialValue() { } //设置与当前线程关联的ThreadLocal初始值
package com.thread;
import java.util.HashMap;
import java.util.Map;
public class ThreadLocalTest {
//设置与当前线程关联的ThreadLocal初始值
static ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal<Map<String,Object>>(){
@Override
protected Map<String,Object> initialValue() {
return new HashMap<>();
}
};
public static void main(String[] args) {
threadLocal.get().put("alisa","邹清");
doSomethings();
new Thread() {
@Override
public void run() {
threadLocal.get().put("1","22");
doSomethings();
}
}.start();
new Thread() {
@Override
public void run() {
threadLocal.get().put("2","33");
doSomethings();
}
}.start();
}
private static void doSomethings() {
System.out.println(Thread.currentThread().getName() + " " +threadLocal.get());
}
}
输出结果:
main {alisa=邹清}
Thread-0 {1=22}
Thread-1 {2=33}
4.3 应用到项目(解决上次课遗留并发问题)
package com.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONPath;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ParamsUtils {
public static ThreadLocal<Map<String, Object>> params = new ThreadLocal<Map<String, Object>>() {
@Override
protected Map<String, Object> initialValue() {
return new LinkedHashMap<String, Object>();
}
};
public static void addMap(Map<String, Object> map) {
params.get().putAll(map);
}
public static void add(String key,Object value) {
params.get().put(key,value);
}
public static Map<String, Object> get() {
return params.get();
}
public static void clear() {
params.get().clear();
}
public static void jsonResultExtract(String result,String regex){
if(JSON.isValidObject(result)&& StringUtils.isNotBlank(regex)){
Map<String,Object> mapJson=StringToUtils.convert(regex,";");
System.out.println("mapJson:"+mapJson);
String finalResult = result;
mapJson.forEach((key,value)->{
Object object= JSONPath.read(finalResult,value.toString());
//如果提取不到,全局搜索
if(object == null){
object = JSONPath.read(finalResult,".."+value.toString());
}
if(object instanceof List){
List<Object> objectList=(List)object;
System.out.println("objectLIst:"+objectList);
for(int i=1;i<=objectList.size();i++){
ParamsUtils.add(key+"_"+i,objectList.get(i-1));
}
ParamsUtils.add(key,objectList);
}else {
ParamsUtils.add(key,object);
}
});
}
}
public static void patternResultExtract(String result,String patternRegex){
if (StringUtils.isNotBlank(patternRegex) && StringUtils.isNotBlank(result)) {
Map<String, Object> regxMap = StringToUtils.convert(patternRegex,";");
final String regxResult = result;
System.out.println(regxMap);
regxMap.forEach((k, v) -> {
Pattern r = Pattern.compile(v.toString());
Matcher m = r.matcher(regxResult);
int count=0;
String value1 =null;
while (m.find()) {
count++;
if(count == 1){
value1 = m.group(1);
}else {
ParamsUtils.add(k+"_"+count,m.group(1));
}
}
if(count > 1){
ParamsUtils.add(k+"_"+1,value1);
//遇到变量值是列表的情况
ParamsUtils.add(k,value1);
}else {
ParamsUtils.add(k,value1);
}
});
}
}
static final String regex ="\\$\\{(.*?)\\}";//?非贪婪
static final Pattern p =Pattern.compile(regex);
public static String beforeReplace(String str) throws Exception {
if(StringUtils.isNotBlank(str)){
Matcher m = p.matcher(str);
while (m.find()){
str=str.replace(m.group(), MapUtils.getString(ParamsUtils.get(),m.group(1),""));
}
}
//增加函数表达式替换
return FunctionUtils.function(str);
}
}
package com.api.thread;
import com.utils.EmailUtils;
import com.utils.ExcelToMapUtils;
import com.utils.ParamsUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ApiThreadTest {
private static final Logger logger = LoggerFactory.getLogger(ApiThreadTest.class);
public static String path = System.getProperty("user.dir")+ File.separator+"data"+File.separator+"apitest2.xlsx";
public static List<String> emailList = new ArrayList<>();
public static void main(String[] args) {
testCaseParams(path,1);
}
private static void testCaseParams(String Path, int index) {
try {
List<Map<String, Object>> mapList = ExcelToMapUtils.importExcel(path, 1);
CountDownLatch latch = new CountDownLatch(mapList.size());
ExecutorService executorService=Executors.newCachedThreadPool();
for (Map<String, Object> map : mapList) {
ApiTask apiTask = new ApiTask(path, latch,map);
//apiTask.start();
executorService.submit(apiTask);
}
latch.await();
executorService.shutdown();
String[] emailArray = new String[emailList.size()];
int j = 0;
for (String string : ApiThreadTest.emailList) {
emailArray[j++] = string;
}
EmailUtils.sendEmailsWithAttachments("测试结果", "请查收", emailArray);
}catch (Exception e) {
logger.error(e.getMessage());
}
}
}
package com.api.thread;
import com.api.ApiTest;
import com.api.ApiTest2;
import com.api.TestCase;
import com.api.TestCaseResult;
import com.github.crab2died.ExcelUtils;
import com.utils.HttpClientUtils;
import com.utils.ParamsUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
public class ApiTask extends Thread{
private static final Logger logger = LoggerFactory.getLogger(ApiTask.class);
private String path;
private CountDownLatch latch;
private Map<String, Object> map;
public ApiTask(String path, CountDownLatch latch,Map<String, Object> map) {
super();
this.path=path;
this.latch=latch;
this.map=map;
}
@Override
public void run() {
try {
String resultPath = null;
ParamsUtils.addMap(map);
System.out.println("map---"+map);
List<TestCaseResult> testCaseResults = new ArrayList<>();
System.out.println("线程运行" + ParamsUtils.get());
List<TestCase> cases = ApiTest2.byExcel(ApiThreadTest.path);
List<TestCase> noSingleCases = cases.stream().filter(d -> "否".equals(d.getIsSingle())).collect(Collectors.toList());
for (TestCase testCase : noSingleCases) {
TestCaseResult testCaseResult = new TestCaseResult();
try {
testCase.setHeaders(ParamsUtils.beforeReplace(testCase.getHeaders()));
testCase.setUrl(ParamsUtils.beforeReplace(testCase.getUrl()));
testCase.setParams(ParamsUtils.beforeReplace(testCase.getParams()));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("组装后的参数::" + testCase);
String result = "";
if ("get".equals(testCase.getType())) {
result = HttpClientUtils.doGet(testCase.getUrl(), testCase.getHeaders());
} else if ("post".equals(testCase.getType())) {
result = HttpClientUtils.doPost(testCase.getUrl(), testCase.getParams(), testCase.getHeaders());
} else if ("postjson".equals(testCase.getType())) {
result = HttpClientUtils.doPostJson(testCase.getUrl(), testCase.getParams(), testCase.getHeaders());
}
ParamsUtils.jsonResultExtract(result, testCase.getResultJson());
ParamsUtils.patternResultExtract(result, testCase.getResultRegx());
BeanUtils.copyProperties(testCaseResult, testCase);
ApiTest.resultCheck(result, testCase, testCaseResult);
testCaseResults.add(testCaseResult);
}
ParamsUtils.clear();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss");
String dateString = simpleDateFormat.format(new Date());
resultPath = "result_" + currentThread().getName()+"_" + dateString + ".xlsx";
ExcelUtils.getInstance().exportObjects2Excel(testCaseResults, TestCaseResult.class, resultPath);
System.out.println("路径:" + resultPath);
ApiThreadTest.emailList.add(resultPath);
System.out.println(ApiThreadTest.emailList.get(0));
testCaseResults.clear();
}catch (Exception e) {
logger.error(e.getMessage());
}finally {
latch.countDown();
}
}
}
5.TestNG
5.1 TestNG 简介
TestNg主要用于单元测试和集成测试,它涵盖了 JUnit4 的全部功能,并且在参数化测试、依赖测试以及套件测试(组)方面功能上更加强大。
包依赖:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.10</version>
<scope>test</scope>
</dependency>
安装插件:
Create TestNG XML,创建testng的xml,通过项目的文件上右键选择,文件会被生成在项目的根目录下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite">
<test verbose="2" preserve-order="true" name="/Users/bjhl/workSpace/matongxue">
<classes>
<class name="apitest.testng.HttpClientTest"></class>
</classes>
</test>
</suite>
package apitest.testng;
import org.testng.annotations.Test;
public class HttpClientTest {
@Test(dependsOnMethods = {"test2"})
public void test1(){
System.out.println("this is test");
}
@Test
public void test2(){
System.out.println("this is test2");
}
}
5.1 TestNG+maven
A: 修改pom文件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M1</version>
<configuration>
<!-- 集成testng xml -->
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
<!-- 是否跳过单元测试 -->
<skipTests>false</skipTests>
<!-- 单元测试出错是否继续 -->
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
B: 执行单元测试(mvn test)
5.3 TestNG和Junit 4
5.4 TestNG特色功能
5.4.1 依赖测试
import org.testng.annotations.Test;
public class DependTest {
@Test(dependsOnMethods = {"test2"})
public void test1() {
System.out.println("test1 run");
}
@Test
public void test2() {
System.out.println("test2 run");
}
}
5.4.2 分组测试
第一组测试:
package apitest.testng;
import org.testng.annotations.Test;
public class GroupsOnMethod {
@Test(groups = "server")
public void serverTest1(){
System.out.println("this is server 1");
}
@Test(groups = "server")
public void serverTest2(){
System.out.println("this is server 2");
}
@Test(groups = "client")
public void clientTest1(){
System.out.println("this is client 1");
}
@Test(groups = "client")
public void clientTest2(){
System.out.println("this is client 2");
}
@Test(groups = "app")
public void appTest(){
System.out.println("this is app");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="groupSuite">
<test name="GroupsOnMethod">
<groups>
<run>
<!-- 不包含某个分组 -->
<exclude name="server"></exclude>
</run>
</groups>
<classes>
<class name="apitest.testng.GroupsOnMethod"/>
</classes>
</test>
</suite>
第二组测试:
package apitest.testng;
import org.testng.annotations.Test;
@Test(groups = "stu1")
public class GroupsOnClass1 {
public void groupsOnClass1_stu1() {
System.out.println("GroupsOnClass1中的stu1运行!");
}
public void groupsOnClass1_stu2() {
System.out.println("GroupsOnClass1中的stu2运行!");
}
}
package apitest.testng;
import org.testng.annotations.Test;
@Test(groups = "stu2")
public class GroupsOnClass2 {
public void groupsOnClass2_stu1() {
System.out.println("GroupsOnClass2中的stu1运行!");
}
public void groupsOnClass2_stu2() {
System.out.println("GroupsOnClass2中的stu2运行!");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="groupSuite1">
<test name="GroupsOnClass">
<groups>
<run>
<!-- 包含某个分组 -->
<include name="stu1"></include>
</run>
</groups>
<classes>
<class name="apitest.testng.GroupsOnClass1"/>
<class name="apitest.testng.GroupsOnClass2"/>
</classes>
</test>
</suite>
第三组测试:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="groupSuite" preserve-order="true">
<test name="groupOnMethodTest">
<groups>
<!-- 分组依赖 -->
<dependencies>
<group name="server" depends-on="app"/>
</dependencies>
</groups>
<classes>
<class name="apitest.testng.GroupsOnMethod"/>
</classes>
</test>
<test name="groupOnClassTest">
<groups>
<run>
<!-- 包含某个分组 -->
<include name="stu1"/>
</run>
</groups>
<classes>
<class name="apitest.testng.GroupsOnClass1"/>
<class name="apitest.testng.GroupsOnClass2"/>
</classes>
</test>
</suite>
5.4.3 参数化支持
通过DataProvider,返回值分别是Object[][]和Iterator<Object[]>
package apitest.testng;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.Vector;
public class DataProviderTest1 {
@Test(dataProvider = "testdb")
public void parameterIntTest(Class clzz, String str) {
System.out.println("Parameterized Number is : " + clzz);
System.out.println("Parameterized Number is : " + str);
}
//返回值是Object[][]
@DataProvider(name = "testdb")
public Object[][] parameterIntTestProvider() {
return new Object[][]{
{Vector.class, "test1"},
{String.class, "test2"},
{Integer.class, "test3"}
};
}
}
package apitest.testng;
import lombok.Data;
@Data
public class TestBean {
private String name;
private String msg;
}
package apitest.testng;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class DataProviderTest2 {
@Test(dataProvider = "mytest")
public void parameterIntTest(TestBean bean) {
System.out.println("Parameterized Number is : " + bean.getName());
System.out.println("Parameterized Number is : " + bean.getMsg());
}
@DataProvider(name = "mytest")
public Iterator<Object[]> parameterIntTestProvider() {
List<Object[]> dataProvider = new ArrayList<Object[]>();
for(int i=0;i<5;i++){
TestBean bean = new TestBean();
bean.setName("testname");
bean.setMsg("--"+i);
dataProvider.add(new Object[] { bean});
}
return dataProvider.iterator();
}
}
5.5 TestNG应用到项目
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"><suite name="All Test Suite">
<test verbose="2" preserve-order="true" name="/Users/bjhl/workSpace/matongxue">
<classes>
<class name="com.api.testng.TestNGTest"></class>
</classes>
</test>
</suite>
package com.api.testng;
import com.api.ApiTest;
import com.api.ApiTest2;
import com.api.TestCase;
import com.api.TestCaseResult;
import com.api.thread.ApiThreadTest;
import com.github.crab2died.ExcelUtils;
import com.utils.EmailUtils;
import com.utils.ExcelToMapUtils;
import com.utils.HttpClientUtils;
import com.utils.ParamsUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.mail.EmailException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
public class TestNGTest {
private static final Logger logger = LoggerFactory.getLogger(TestNGTest.class);
public static String path = System.getProperty("user.dir")+ File.separator+"data"+File.separator+"apitest2.xlsx";
@DataProvider(name = "excel",parallel = true)//parallel = true 并行
public Iterator<Object[]> initParams() {
List<Map<String, Object>> mapList = ExcelToMapUtils.importExcel(path, 1);
List<Object[]> dataProvider = new ArrayList<Object[]>();
mapList.forEach(d->dataProvider.add(new Object[] {d}));
return dataProvider.iterator();
}
@Test(dataProvider = "excel")
public void parameterIntTest(Map<String, Object> map) {
try {
String resultPath = null;
ParamsUtils.addMap(map);
System.out.println("map---"+map);
List<TestCaseResult> testCaseResults = new ArrayList<>();
System.out.println("线程运行" + ParamsUtils.get());
List<TestCase> cases = ApiTest2.byExcel(ApiThreadTest.path);
List<TestCase> noSingleCases = cases.stream().filter(d -> "否".equals(d.getIsSingle())).collect(Collectors.toList());
for (TestCase testCase : noSingleCases) {
TestCaseResult testCaseResult = new TestCaseResult();
try {
testCase.setHeaders(ParamsUtils.beforeReplace(testCase.getHeaders()));
testCase.setUrl(ParamsUtils.beforeReplace(testCase.getUrl()));
testCase.setParams(ParamsUtils.beforeReplace(testCase.getParams()));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("组装后的参数::" + testCase);
String result = "";
if ("get".equals(testCase.getType())) {
result = HttpClientUtils.doGet(testCase.getUrl(), testCase.getHeaders());
} else if ("post".equals(testCase.getType())) {
result = HttpClientUtils.doPost(testCase.getUrl(), testCase.getParams(), testCase.getHeaders());
} else if ("postjson".equals(testCase.getType())) {
result = HttpClientUtils.doPostJson(testCase.getUrl(), testCase.getParams(), testCase.getHeaders());
}
ParamsUtils.jsonResultExtract(result, testCase.getResultJson());
ParamsUtils.patternResultExtract(result, testCase.getResultRegx());
BeanUtils.copyProperties(testCaseResult, testCase);
ApiTest.resultCheck(result, testCase, testCaseResult);
testCaseResults.add(testCaseResult);
}
ParamsUtils.clear();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss");
String dateString = simpleDateFormat.format(new Date());
resultPath = "result_" +"_" + dateString+"_"+(int)(Math.random()*10)+ ".xlsx";
ExcelUtils.getInstance().exportObjects2Excel(testCaseResults, TestCaseResult.class, resultPath);
System.out.println("路径:" + resultPath);
ApiThreadTest.emailList.add(resultPath);
testCaseResults.clear();
}catch (Exception e) {
logger.error(e.getMessage());
}
}
//什么周期 等待所有测试完毕
@AfterClass
public void sendResult() throws EmailException {
String[] emailArray = new String[ApiThreadTest.emailList.size()];
int j = 0;
for (String string : ApiThreadTest.emailList) {
emailArray[j++] = string;
}
EmailUtils.sendEmailsWithAttachments("测试结果", "请查收", emailArray);
}
}