测开笔记九:线程&TestNG

2023-10-30

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);

    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

测开笔记九:线程&TestNG 的相关文章

随机推荐

  • 参数显著性检验的p值小于显著性水平不等于其具有经济学意义

    在做简单线性回归或者多元线性回归时 如何评估参数的统计意义和经济意义是我们研究问题的两个重要方面 理论意义和经济意义是如何显示在数字上的呢 以下是笔者在做相关或者线性回归课题时学习整理出来的 在此分享记录 参数的t统计量足够大 或者p值足够
  • python 接口自动化测试-----常见面试题汇总

    1 软件接口是什么 程序不同模块之间传输数据并作处理的类或函数 2 HTTP 和 HTTPS 协议区别 答 https 协议需要到 CA Certificate Authority 证书颁发机构 申请证书 一般免费证书 较少 因而需要一定费
  • 【Anaconda】基本操作

    1 创建 使用命令查看当前拥有的虚拟环境 conda info envs 在指定目录下创建新的虚拟环境 其中C ProgramData Anaconda3 envs 是创建的目录所在位置 pytorch 是新环境名称 python 3 8是
  • SpringBoot之【mybatisplus】分页插件、条件查询、sql打印开启

    文章目录 一 概述 二 流程 1 sql打印开启 2 分页插件 3 常用条件 一 概述 本篇主要写开启 sql的打印 分页插件开启 条件查询 二 流程 1 sql打印开启 yml文件添加如下配置 mybatis plus 配置slq打印日志
  • ForeFront Chat 免费版GPT-4来了!

    Forefront Chat简介 近日 Forefront AI 正式推出 Forefront Chat 允许用户免费体验GPT 4 的强大功能 Forefront AI 在 Twitter 上表示 今天 我们发布了 Forefront C
  • 为什么析构函数必须是虚函数?为什么默认的析构函数不是虚函数?

    本博客可能随时删除或隐藏 请关注微信公众号 获取永久内容 微信搜索 编程笔记本 获取更多信息 codingbook2020 今天我们来谈一谈面试 C 工程师时经常被谈到的一个问题 为什么析构函数必须是虚函数 为什么默认的析构函数不是虚函数
  • java高频面试题(2023最新)

    目录 一 java基础 1 八大基础类型 2 java三大特性 3 重载和重写的区别 4 pubilc protected dafault 不写 private修饰符的作用范围 5 和equals的区别 6 hashcode 值相同 equ
  • 如何解决“Error: xxx.js 已被代码依赖分析忽略,无法被其他模块引用”报错?

    今天在用uniapp框架写小程序 遇到报错 错误原因 微信开发者工具从 1 05 2201210 版本开始 对小程序项目新增了无依赖文件过滤能力 如果某个 js 文件被静态分析显示是无依赖文件 在实际运行时又被其他 js 文件 requir
  • java 文件读取和写入

    1 文件名 1 InputStream 字节流 和Reader 字符流 2 OutputStream 字节流 和 Writer 字符流 1 文件名 Java提供了File类 来表示一个文件 通过构造方法来指定路径 绝对路径 目录与目录之间用
  • BUCK电路-TL494方案 持续更新大概2周更新完成

    目录 一 电磁感应现象 这个现象是如何产生的 磁生电的变换的条件 二 电感的伏安特性 计算磁场大小可以用上面这个公式 磁感应强度B来表示 u0是真空磁导率 N是线圈的匝数 I是通过这个线圈的的电流大小 电生磁的过程就是由I来生成这个B 可以
  • SpringBoot 整合 ChatGPT API 项目实战,挣了3K!

    大家好 我是老赵 体验到了ChatGPT的强大之后 那么我们会想 如果我们想基于ChatGPT开发一个自己的聊天机器人 这个能搞定吗 ChatGPT平台已经为技术提供了一个入口了 很简单的就是实现了 一 准备工作 1 已成功注册 OpenA
  • Jmeter多接口测试之参数传递

    接口测试包含单接口测试和多接口测试 通过组合多个接口实现一组功能的验证称为多接口测试 单接口重在单个接口多种请求组合的响应断言 多接口重在组合不同接口 实现流程的串联和验证 多接口测试涉及到接口之间参数的传递 比如AB两个接口协同完成一个功
  • DDOS高防ip是什么?有什么用?

    ddos高防ip是网络安全公司针对服务器在遭受大流量的ddos攻击导致服务不可用的情况 推出的付费增值服务 ddos攻击是通过用大量的无效流量数据对该IP的服务器进行请求 导致服务器的资源被大量占用 无法对正确的请求作出响应通过配置ddos
  • shell变量的设定规则

    内容摘自鸟哥的Linux 私房菜 1 变量与变量内容以一个等号 来连结 如下所示 myname VBird 2 等号两边不能直接接空格符 如下所示为错误 myname VBird 或 myname VBird Tsai 3 变量名称只能是英
  • Django基础之Admin后台数据管理

    Admin后台数据管理 一个站点或者网站 除了给用户浏览及操作外 还需要对后台数据进行管理 比如添加商品 修改商品等等 Django提供了一个可插拔的后台管理系统 Admin 应用 该系统可以从模型中读取元数据 并提供以模型为中心的界面 A
  • C语言文件通讯录制作说明

    文件通讯 前言 很多计算机专业大学生第一个课设就是校园出入管理系统或者文件版通讯录 这些软件工程不仅可以锻炼我们的编程能力 也可以让我们更为深刻的理解C语言知识 为将来的学习打下基础 但是同学们肯定会有所疑问 下面是我的一个范例 提示 以下
  • 高德地图 – 1.问题集锦

    1 自动加载文本框的坐标 并在地图标注点 2 点击地图时 并且 逆地理编码 解析出地址方在文本框 js var lnglatXY var marker 初始化地图对象 加载地图 var map new AMap Map mapContain
  • 【PB】动态添加数据窗口列中下拉列表的内容

    1 设置数据窗口某个列 dept id 的style type为DropDownListBox dw 1 Modify dept id ddlb case any 2 为下拉列表框添加数据 boolean lb flag false str
  • 剖析C++标准库智能指针(std::auto_ptr)

    1 Do you Smart Pointer Smart Pointer 中文名 智能指针 舶来品 不可否认 资源泄露 resource leak 曾经是C 程序的一大噩梦 垃圾回收 机制 Garbage Collection 一时颇受注目
  • 测开笔记九:线程&TestNG

    1 线程 1 1 线程概念 线程 程序执行流的最小执行单位 是行程中的实际运作单位 经常容易和进程这个概念混淆 那么 线程和进程究竟有什么区别呢 首先 进程是一个动态的过程 是一个活动的实体 简单来说 一个应用程序的运行就可以被看做是一个进