我们在做服务端测试时,经常使用的自动化测试框架或平台大多通过restful风格使用http协议接入系统,例如常见的Jmeter、LoadRunner、Postman等,还有不常用或付费的工具如TestRail、katalon、soapUI等,很少见到切入到方法层实现更细颗粒度的工具。对此,本文将封装一款切入到方法层并实现可视化操作的白盒测试框架,突破http应用层壁垒,协助测试人员实现方法级别的测试。

一、使用技术评估

1、开发语言

本框架使用java作为开发语言,大版本为jdk1.8,涉及到的关键技术有java反射、字节码编程、自定义注解、Stream流(jdk8)等。

2、目标方法标识

如果测试人员想测试某个或某组方法,可行方式是在代码方法上打上标识,方便项目运行期自动收集这些方法,本框架使用java自定义注解实现对目标方法的标识。

3、可视化展示

本框架使用模板引擎thymeleaf+springboot提供测试人员操作界面的可视化展示。

二、设计思路简述

1、定义捕捉标记,便于收集目标方法

为了方便测试人员在目标方法上标注,需要新建一个自定义注解@White,主要包含两个信息:一是目标方法的测试备注,二是方法的执行环境配置(判断目标方法是否使用spring容器环境运行)。

 @Inherited@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface White {    /**     * 方法标注名     *     * @return     */    String value() default "";    /**     * 方法是否来自或使用Spring容器对象(默认不使用)     *     * @return     */    boolean fromIOC() default false;}

2、定义接口,方法捕捉器与方法执行器

我们需要定义一个方法捕捉器,用于在项目运行期收集捕捉标注为@White的方法,再定义一个方法执行器,用于测试时执行相关方法。捕捉器接口,重载了一个含有泛型参数的方法,方便传入的不同的捕获源,如包名、包名组或ioc容器对象。

 public interface Catcher<T> {    /**     * 执行捕捉操作     */    void doCatch() throws Exception;    /**     * 执行捕捉操作(传参数)     */    void doCatch(T t) throws Exception;}

执行器接口,重载了一个含参方法,ActElement用于传入目标方法的测试参数,同时扩展了一个执行次数,用于执行压力测试。

 public interface Actuator {    /**     * 执行目标方法     *     * @return     * @throws Exception     */    void doWhiteMethod() throws Exception;    /**     * 执行目标方法     *     * @param element     * @throws Exception     */    void doWhiteMethod(ActElement element) throws Exception;}@Data@NoArgsConstructor  //无参构造@AllArgsConstructor //有参构造public class ActElement {    /**     * 执行次数     */    private int actNums;    /**     * 测试参数     */    private String params;}

抽象处理器AbstractProcessor,实现捕获器和执行器(利用适配器模式,继承该方法后可不必实现所有接口方法,保持代码清爽简洁)

 public abstract class AbstractProcessor implements Actuator, Catcher {    @Override    public void doWhiteMethod() throws Exception {    }    @Override    public void doWhiteMethod(ActElement element) throws Exception {    }    @Override    public void doCatch() throws Exception {    }    @Override    public void doCatch(Object o) throws Exception {    }}

3、封装方法工厂

首先定义WhiteMethod类,作为白盒测试的方法主体,它继承了AbstractProcessor类,并提供了Actuator方法执行器接口的实现。

 @Data@NoArgsConstructor  //无参构造@AllArgsConstructor //有参构造public class WhiteMethod extends AbstractProcessor {    /**     * 类名     */    private String className;    /**     * 注解注释     */    private String notes;    /**     * 反射class     */    private Class> clazz;    /**     * 反射方法     */    private Method method;    /**     * 方法是否来自或使用Spring容器对象     */    private boolean fromIOC;    /**     * spring容器中的bean对象     */    private Object bean;    @Override    public void doWhiteMethod(ActElement element) throws Exception {        excuteMethod(element.getActNums(), element.getParams());    }    /**     * 反射执行方法     *     * @param params 输入参数     */    public void excuteMethod(String params) throws Exception {        System.out.println("======= Test Staring ======>>  [class/bean:" + className + ", method:" + method.getName() + "]");        //如果测试人员未输入测试参数,使用默认值生成参数组        Object[] args = (params == null || "".equals(params)) ? AutoSourceUtil.getSampleParams(method) : AutoSourceUtil.getRealParams(method, params);        //是否使用容器对象执行方法        if (fromIOC) {            AutoSourceUtil.doMethod(clazz, method, args, bean);        } else {            AutoSourceUtil.doMethod(clazz, method, args, null);        }    }    /**     * 反射执行方法(多次)     *     * @param params 输入参数     * @param times  执行次数     */    public void excuteMethod(int times, String params) throws Exception {        while (times > 0) {            excuteMethod(params);            times--;        }    }}

我们通过一个核心类MethodsFactory,来封装白盒测试的方法工厂,内部维护了一个map格式的方法池。MethodsFactory继承自AbstractProcessor,实现了捕捉器接口的doCatch(Object object)方法,object扩展了三种入参,分别是包路径、包路径数组、ioc容器,根据不同入参类型通过initFactory()方法来实现工厂的初始化。

     /**     * 白盒测试的方法池     */    public static final Map<String, WhiteMethod> whiteMethods = new ConcurrentHashMap<>();    /**     * 实现白盒测试方法捕捉器     *     * @param object 捕获入口,包路径或IOC容器     * @throws Exception     */    @Override    public void doCatch(Object object) throws Exception {        if (object instanceof String) {            initFactory((String) object);        } else if (object instanceof String[]) {            initFactory((String[]) object);        } else if (object instanceof ApplicationContext) {            initFactory((ApplicationContext) object);        }    }    /**     * 通过包名初始化工厂     *     * @param packageNames 扫描的包名组     * @throws Exception     */    public void initFactory(String[] packageNames) throws Exception {        initFactory(packageNames, null);    }    /**     * 通过包名初始化工厂(二)     *     * @param packageName 扫描的包名     * @throws Exception     */    public void initFactory(String packageName) throws Exception {        String[] packageNames = new String[]{packageName};        initFactory(packageNames, null);    }    /**     * 通过容器初始化工厂     *     * @param context IOC容器     * @throws Exception     */    public void initFactory(ApplicationContext context) throws Exception {        initFactory(null, context);    }    /**     * 初始化工厂     *     * @param packageNames 扫描的包名     * @param context      IOC容器     * @throws Exception     */    public void initFactory(String[] packageNames, ApplicationContext context) throws Exception {        //初始化普通方法入池        if (packageNames != null) {            Arrays.asList(packageNames).stream().forEach(name -> initWhiteMethods(name));        }        //初始化容器相关方法入池        if (context != null) {            initWhiteMethods(context);        }    }

初始化工厂,这里重载了initWhiteMethods()方法,区分使用包名还是容器去扫描捕捉对应的目标方法。其中,通过包名的方式扫描,使用工具类PackageUtil的getAllTargets(String packageName)方法,获取所有packageName下类名集合并遍历,收集类中匹配White注解的方法,而通过ioc容器的方式扫描,则是通过spring-beans包的ListableBeanFactory接口(继承自BeanFactory接口,即spring容器的bean工厂)的getBeanDefinitionNames()方法获取所有的bean集合,再执行遍历收集。

     /**     * 通过包名路径初始化方法池     *     * @param packageName     */    public void initWhiteMethods(String packageName) {        List<String> list = PackageUtil.getAllTargets(packageName);        if (list.size() == 0) {            return;        }        list.stream().forEach(className -> {            try {                Class> clazz = Class.forName(className);                Method[] methods = clazz.getDeclaredMethods();                for (Method method : methods) {                    if (method.isAnnotationPresent(White.class)) {                        White white = method.getAnnotation(White.class);                        if (!white.fromIOC()) {//只收集非容器关联的方法                            whiteMethods.put("package_key_" + className + "_" + method.getName(), new WhiteMethod(className, white.value(), clazz, method, white.fromIOC(), null));                        }                    }                }            } catch (Exception e) {                e.printStackTrace();            }        });    }    /**     * 通过spring容器初始化方法池     *     * @param applicationContext     */    public void initWhiteMethods(ApplicationContext applicationContext) {        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();        Arrays.asList(beanDefinitionNames).stream().forEach(beanDefinitionName -> {            try {                Class> clazz = applicationContext.getType(beanDefinitionName);                Method[] methods = clazz.getDeclaredMethods();                for (Method method : methods) {                    if (method.isAnnotationPresent(White.class)) {                        White white = method.getAnnotation(White.class);                        if (white.fromIOC()) {                            whiteMethods.put("spring_key_" + beanDefinitionName + "_" + method.getName(), new WhiteMethod(beanDefinitionName, method.getAnnotation(White.class).value(), clazz, method, white.fromIOC(), applicationContext.getBean(beanDefinitionName)));                        }                    }                }            } catch (Exception e) {                e.printStackTrace();            }        });    }

4、方法执行

我们通过java反射执行目标测试的方法,invoke(Object obj, Object... args),必要元素为方法所属对象(obj)、参数(args)。

(1)新建方法所属对象

一般使用class对象的newInstance()方法新建对象,但是当方法来自或使用Spring容器时,改为使用BeanFactory接口的getBean(String var1)方法获取bean对象,如果强行使用newInstance()新建对象,有可能导致获取对象某些属性时出现空指针异常。

(2)模拟/获取拼接方法参数

如果要进行测试的方法是有参的,但是测试人员没有输入参数,或者执行批量压力测试不方便输入动态参数时,我们可以通过参数的类型对参数数组进行赋初始化值,如果测试人员有输入参数(以String方式,逗号分隔),则进行动态解析成对应的args参数数组。AutoSourceUtil封装了具体拼接与执行方式,其中参数有可能是基本类型,这时候要使用枚举类BaseJavaType做一下特殊处理。

 public class AutoSourceUtil {    /**     * 返回反射对应的样例初始化值     *     * @param clazz 反射对象     * @return     * @throws Exception     */    public static Object getSampleValue(Class clazz) throws Exception {        if (clazz == BaseJavaType.BYTE.getVal())            return 0;        if (clazz == BaseJavaType.CHAR.getVal())            return 0;        if (clazz == BaseJavaType.SHORT.getVal())            return 0;        if (clazz == BaseJavaType.INT.getVal())            return 0;        if (clazz == BaseJavaType.LONG.getVal())            return 0;        if (clazz == BaseJavaType.DOUBLE.getVal())            return 0;        if (clazz == BaseJavaType.FLOAT.getVal())            return 0;        if (clazz == BaseJavaType.BOOLEAN.getVal())            return false;        else return clazz.newInstance();    }    /**     * 返回样例测试对应的基本类型值或对象     *     * @param clazz 反射对象     * @param param 测试方法字符串     * @return     */    public static Object getRealValue(Class clazz, String param) {        if (clazz == BaseJavaType.BYTE.getVal())            return Byte.parseByte(param);        if (clazz == BaseJavaType.CHAR.getVal())            return param.charAt(0);        if (clazz == BaseJavaType.SHORT.getVal())            return Short.parseShort(param);        if (clazz == BaseJavaType.INT.getVal())            return Integer.parseInt(param);        if (clazz == BaseJavaType.LONG.getVal())            return Long.parseLong(param);        if (clazz == BaseJavaType.DOUBLE.getVal())            return Double.parseDouble(param);        if (clazz == BaseJavaType.FLOAT.getVal())            return Float.parseFloat(param);        if (clazz == BaseJavaType.BOOLEAN.getVal())            return Boolean.parseBoolean(param);        else return clazz.cast(param);    }    /**     * 反射调用方法     *     * @param clazz  反射class对象     * @param method 反射方法     * @param args   需要执行的方法参数     * @param object 方法对象,设置为null时会使用clazz.newInstance()新建对象     * @return     * @throws Exception     */    public static Object doMethod(Class> clazz, Method method, Object[] args, Object object) throws Exception {        if (method.getParameterTypes().length == 0) {            //无参方法,args置为空            args = null;        }        return (object == null) ? method.invoke(clazz.newInstance(), args) : method.invoke(object, args);    }    /**     * 对无测试参数的有参方法,构造对应格式的初始入参数组,各参数使用空白默认值     *     * @param method 反射的方法     * @return     * @throws Exception     */    public static Object[] getSampleParams(Method method) throws Exception {        //获得一个方法参数数组        Class[] paramTypes = method.getParameterTypes();        //拼接动态参数        Object[] args = new Object[paramTypes.length];        for (int i = 0, j = paramTypes.length; i < j; i++) {            args[i] = getSampleValue(paramTypes[i]);        }        return args;    }    /**     * 根据测试人员输入的参数拼接成对应格式的可执行参数组     *     * @param method 反射的方法     * @param params 手动输入的参数     * @return     * @throws Exception     */    public static Object[] getRealParams(Method method, String params) throws Exception {        String[] stringParams = params.split(",");        Class[] paramTypes = method.getParameterTypes();        if (paramTypes.length != stringParams.length) {            throw new WhiteException("测试参数输入有误!");        }        //拼接动态参数        Object[] args = new Object[paramTypes.length];        for (int i = 0, j = paramTypes.length; i < j; i++) {            //基本类型参数处理,            args[i] = getRealValue(paramTypes[i], stringParams[i]);        }        return args;    }}public enum BaseJavaType {    BYTE("byte", byte.class),    CHAR("char", char.class),    SHORT("short", short.class),    INT("int", int.class),    LONG("long", long.class),    DOUBLE("double", double.class),    FLOAT("float", float.class),    BOOLEAN("boolean", boolean.class);    BaseJavaType(String name, Class clazz) {        this.name = name;        this.clazz = clazz;    }    public Class getVal() {        return clazz;    }    //基本变量类型    private String name;    //基本变量类型对应的Class    private Class clazz;}

三、使用介绍

使用时继承MethodsFactory类即可使用方法捕捉器和执行器。

1、原生jvm虚拟机环境使用

我们在需要测试的类Demo上的各个方法上加上@White注解。

 package com.whiteBox;import com.whiteBox.core.anno.White;public class Demo {    private int result;    @White("无参方法")    public int getResult() {        System.out.println("getResult() has be done");        return result;    }    public void setResult(int result) {        this.result = result;    }    @White("有参(一个参数)")    public void one(String one) {        System.out.println("method_one:" + one);    }    @White("有参(两个参数)")    public void two(String two, String twos) {        System.out.println("method_two:" + two + twos);    }    @White("有参(不同类型参数)")    public void three(String two, int hello) {        System.out.println(two + "method_three:" + hello);    }}

DemoTest继承MethodsFactory,调用捕捉器扫描"com.whiteBox"包名下的所有标注了@White的方法,再调用MethodsFactory实现的执行器doWhiteMethod(),它可以将所有扫描收到到的方法执行一遍,如果是有参数方法,会根据类型创建初始参数。

 public class DemoTest extends MethodsFactory {    public static void main(String[] args) throws Exception {        DemoTest demoTest = new DemoTest();        //捕捉器捕捉相应包内标记的方法        demoTest.doCatch("com.whiteBox");        //执行白盒测试        demoTest.doWhiteMethod();    }}

控制台响应结果如下:可以看到,被@White标注的方法被成功执行。

 ======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:two]method_two:======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:one]method_one:======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:three]method_three:0======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:getResult]getResult() has be done

2、spring容器环境中使用

(1)spring样例环境搭建

我们使用springboot+thymeleaf搭建一个简单的用户增删改查demo,maven构建项目,pom添加依赖:

          <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-thymeleafartifactId>        dependency>        <dependency>            <groupId>org.webjars.bowergroupId>            <artifactId>jqueryartifactId>            <version>2.0.3version>        dependency>        <dependency>            <groupId>org.webjars.bowergroupId>            <artifactId>bootstrapartifactId>            <version>3.0.3version>        dependency>          <dependency>            <groupId>org.projectlombokgroupId>            <artifactId>lombokartifactId>            <version>1.18.12version>        dependency>

后端按照经典MVC模式设计,其中dao层和service层各方法加上了@White注解,代表该方法将被白盒测试框架收集。

 @Data@NoArgsConstructor  //无参构造@AllArgsConstructor //有参构造public class User {    private int userId;    private String userName;    private String sex;    private String desc;}@Componentpublic class UserDao {    private static List<User> locallist = new ArrayList<>();    private static AtomicInteger atomicInteger = new AtomicInteger(1);    static {        User u = new User(0, "元始天尊", "男", "最开始的人");        locallist.add(u);    }    @White(value = "dao层-查询用户", fromIOC = true)    public User getUser(int id) {        List<User> users = locallist.stream().filter(u -> u.getUserId() == id).collect(Collectors.toList());        return users.get(0);    }    @White(value = "dao层-用户列表", fromIOC = true)    public List<User> list() {        return locallist;    }    @White(value = "dao层-新增用户", fromIOC = true)    public void add(User user) {        user.setUserId(atomicInteger.getAndIncrement());        locallist.add(user);    }    @White(value = "dao层-修改用户", fromIOC = true)    public void edit(User user) {        locallist.removeIf(u -> u.getUserId() == user.getUserId());        locallist.add(user);    }    @White(value = "dao层-删除用户", fromIOC = true)    public void delete(int id) {        locallist.removeIf(u -> u.getUserId() == id);    }}@Servicepublic class UserService {    @Autowired    private UserDao userDao;    @White(value = "service层-查询用户", fromIOC = true)    public User getUser(int id) {        return userDao.getUser(id);    }    @White(value = "service层-用户列表", fromIOC = true)    public List<User> list() {        return userDao.list();    }    @White(value = "service层-新增用户", fromIOC = true)    public void add(User user) {        userDao.add(user);    }    @White(value = "service层-修改用户", fromIOC = true)    public void edit(User user) {        userDao.edit(user);    }    @White(value = "service层-删除用户", fromIOC = true)    public void delete(int id) {        userDao.delete(id);    }}@Controllerpublic class UserController {    @Autowired    private UserService userService;    @RequestMapping("/")    public String index() {        return "redirect:/list";    }    @RequestMapping("/list")    public String list(Model model) {        model.addAttribute("users", userService.list());        return "user/list";    }    @RequestMapping("/toAdd")    public String toAdd(Model model) {        User user = new User();        model.addAttribute("user", user);        return "user/userAdd";    }    @RequestMapping("/add")    public String add(User user) {        userService.add(user);        return "redirect:/list";    }    @RequestMapping("/toEdit")    public String toEdit(Model model, int id) {        model.addAttribute("user", userService.getUser(id));        return "user/userEdit";    }    @RequestMapping(value = "/edit")    public String edit(User user) {        userService.edit(user);        return "redirect:/list";    }    @RequestMapping("/delete")    public String delete(int id) {        userService.delete(id);        return "redirect:/list";    }}

使用thymeleaf模板引擎编写表单页面,用户列表:list.html

 <html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8"/>    <title>用户列表title>    <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>head><body class="container"><br/><h3>用户列表h3><br/><br/><div class="with:80%">    <table class="table table-hover">        <thead>        <tr>            <th>编号th>            <th>用户名th>            <th>性别th>            <th>描述th>            <th>修改th>            <th>删除th>        tr>        thead>        <tbody>        <tr th:each="user : ${users}">            <th scope="row" th:text="${user.userId}">1th>            <td th:text="${user.userName}">neotd>            <td th:text="${user.sex}">1td>            <td th:text="${user.desc}">6td>            <td><a th:href="@{/toEdit(id=${user.userId})}">编辑a>td>            <td><a th:href="@{/delete(id=${user.userId})}">删除a>td>        tr>        tbody>    table>div><div class="form-group">    <div class="col-sm-2 control-label">        <a href="/toAdd" th:href="@{/toAdd}" class="btn btn-info">新增a>    div>div>body>

新增页面userAdd.html

 <html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8"/>    <title>添加用户title>    <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>head><body class="container"><br/><h1>增加用户h1><br/><br/><div class="with:80%">    <form class="form-horizontal" th:action="@{/add}" th:object="${user}"  method="post">        <input type="hidden" name="userId" th:value="*{userId}" />        <div class="form-group">            <label for="userName" class="col-sm-2 control-label">userNamelabel>            <div class="col-sm-10">                <input type="text" class="form-control" name="userName" id="userName" placeholder="userName"/>            div>        div>        <div class="form-group">            <label for="sex" class="col-sm-2 control-label">agelabel>            <div class="col-sm-10">                <input type="text" class="form-control" name="sex"  id="sex"  placeholder="sex"/>            div>        div>        <div class="form-group">            <label for="desc" class="col-sm-2 control-label">desclabel>            <div class="col-sm-10">                <input type="text" class="form-control" name="desc"  id="desc"  placeholder="desc"/>            div>        div>        <div class="form-group">            <div class="col-sm-offset-2 col-sm-10">                <input type="submit" value="提交" class="btn btn-info" />                                     <a href="/toAdd" th:href="@{/list}" class="btn btn-info">返回a>            div>        div>    form>div>body>html>

编辑页面userEdit.html

 <html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8"/>    <title>修改用户title>    <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>head><body class="container"><br/><h1>修改用户h1><br/><br/><div class="with:80%">    <form class="form-horizontal" th:action="@{/edit}" th:object="${user}" method="post">        <input type="hidden" name="userId" th:value="*{userId}"/>        <div class="form-group">            <label for="userName" class="col-sm-2 control-label">userNamelabel>            <div class="col-sm-10">                <input type="text" class="form-control" name="userName" id="userName" th:value="*{userName}"                       placeholder="userName"/>            div>        div>        <div class="form-group">            <label for="sex" class="col-sm-2 control-label">agelabel>            <div class="col-sm-10">                <input type="text" class="form-control" name="sex" id="sex" th:value="*{sex}" placeholder="sex"/>            div>        div>        <div class="form-group">            <label for="desc" class="col-sm-2 control-label">desclabel>            <div class="col-sm-10">                <input type="text" class="form-control" name="desc" id="desc" th:value="*{desc}" placeholder="desc"/>            div>        div>        <div class="form-group">            <div class="col-sm-offset-2 col-sm-10">                <input type="submit" value="提交" class="btn btn-info"/>                                     <a href="/toAdd" th:href="@{/list}" class="btn btn-info">返回a>            div>        div>    form>div>body>html>

本地启动容器,访问http://localhost:8080/list,即可进行简单的user增删改查,此处不赘述。

(2)容器下执行捕捉器收集白盒测试方法

我们可以在spring容器启动后使用方法捕捉器收集目标方法,在管理平台界面进行单个方法的分析与测试。这里定义一个WhiteHandler,继承MethodsFactory,实现ApplicatonContextAware接口,在setApplicationContext()方法中获取BeanFactory的实例applicationContext,同时对包"com.whiteBox"和spring容器进行扫描收集。

 @Componentpublic class WhiteHandler extends MethodsFactory implements ApplicationContextAware {    /**     * 重写setApplicationContext方法,获取IOC容器:ApplicationContext实例     *     * @param applicationContext     * @throws BeansException     */    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        try {            //捕捉该包下的标注方法            doCatch("com.whiteBox");            //捕捉spring容器环境下相关标注方法            doCatch(applicationContext);        } catch (Exception e) {            e.printStackTrace();        }    }}
(3)容器下使用方法执行器进行测试

为方便图形化测试,新建一个WhiteController,提供白盒方法的查询和执行处理。前面提到WhiteMethod继承了AbstractProcessor并实现了doWhiteMethod方法,可以通过它对单个方法进行测试执行。

 @Controller@RequestMapping("white")public class WhiteController {    /**     * 获取工厂里的方法池,并以List形式输出     * @return     */    private List<WhiteMethod> getMethods(){        return MethodsFactory.whiteMethods.values().stream()                .collect(Collectors.toList());    }    /**     * 方法列表,列出所有标注的方法     *     * @param model     * @return     */    @RequestMapping("/list")    public String list(Model model) {        model.addAttribute("methods", getMethods());        return "white/whiteList";    }    /**     * 跳转至方法详情     *     * @param model     * @param index     * @return     */    @RequestMapping("/toExecute")    public String toExecute(Model model, int index) {        WhiteMethod whiteMethod = getMethods().get(index);        model.addAttribute("index", index);        model.addAttribute("whiteMethod", whiteMethod);        return "white/doExecute";    }    /**     * 执行测试方法     *     * @param index     * @param times     * @param params     * @return     * @throws Exception     */    @RequestMapping("/execute")    public String execute(int index, int times, String params) throws Exception {        WhiteMethod whiteMethod = getMethods().get(index);        ActElement element = new ActElement(times, params);        whiteMethod.doWhiteMethod(element);        return "redirect:/white/list";    }}

前端页面whiteList.html

 <html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8"/>    <title>白盒测试方法列表title>    <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>head><body class="container"><br/><h3>方法列表h3><br/><br/><div class="with:80%">    <table class="table table-hover">        <thead>        <tr>            <th>序号th>            <th>类名/Bean名th>            <th>方法名th>            <th>标注th>            <th>容器th>            <th>操作th>        tr>        thead>        <tbody>        <tr th:each="whiteMethod : ${methods}">            <th scope="row" width="60px" th:text="${methods.indexOf(whiteMethod)+1}">1th>            <td th:text="${whiteMethod.className}">td>            <td th:text="${whiteMethod.method}">td>            <td width="200px" th:text="${whiteMethod.notes}">1td>            <td width="50px" th:text="${whiteMethod.fromIOC}">1td>            <td width="60px"><a th:href="@{/white/toExecute(index=${methods.indexOf(whiteMethod)})}">执行a>td>        tr>        tbody>    table>div>body>html>

方法执行页面doExecute.html

 <html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8"/>    <title>白盒测试方法列表title>    <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>head><body class="container"><br/><h3>方法列表h3><br/><br/><div class="with:80%">    <table class="table table-hover">        <thead>        <tr>            <th>序号th>            <th>类名/Bean名th>            <th>方法名th>            <th>标注th>            <th>容器th>            <th>操作th>        tr>        thead>        <tbody>        <tr th:each="whiteMethod : ${methods}">            <th scope="row" width="60px" th:text="${methods.indexOf(whiteMethod)+1}">1th>            <td th:text="${whiteMethod.className}">td>            <td th:text="${whiteMethod.method}">td>            <td width="200px" th:text="${whiteMethod.notes}">1td>            <td width="50px" th:text="${whiteMethod.fromIOC}">1td>            <td width="60px"><a th:href="@{/white/toExecute(index=${methods.indexOf(whiteMethod)})}">执行a>td>        tr>        tbody>    table>div>body>html>

启动容器后,访问http://localhost:8080/white/list,即可看到所有被标注的方法详情,其中容器,true代表使用spring容器环境的对象执行方法。

a.测试普通方法:

点击第7条,输入测试参数:"你好,2020",次数5。

点击提交,控制台打印的测试日志如下,可以看到该方法执行了5次:

 ======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:three]你好method_three:2020======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:three]你好method_three:2020======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:three]你好method_three:2020======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:three]你好method_three:2020======= Test Staring ======>>  [class/bean:com.whiteBox.Demo, method:three]你好method_three:2020
b.测试容器方法

在容器方法的注解上fromIOC属性要为true,我们点击列表第5条,测试service层的add方法,输入次数为2,点击提交。

控制台日志,可以看到userService的新增方法执行了2次:

 ======= Test Staring ======>>  [class/bean:userService, method:add]======= Test Staring ======>>  [class/bean:userService, method:add]

再访问用户列表http://localhost:8080/list,即可看到新增了编号为1和2的两个用户,测试service层新增方法成功。

下面我们测试下dao层的删除方法,回到http://localhost:8080/white/list方法列表,点击第12条执行,输入测试参数:2,次数:1,点击提交,这时将删除编号为2的用户。

控制台日志,可以看到userDao的删除方法执行了1次:

 ======= Test Staring ======>>  [class/bean:userDao, method:delete]

重新查看用户列表,发现编号为2的用户已经被删除,测试dao层方法成功执行。

四、总结与思考

本框架尝试通过自定义注解标识目标方法并项目运行期自动收集相关方法,封装测试参数和测试次数入口,达到辅助测试人员进行白盒或压力测试的目标。代码尽可能的兼容到了复杂运行环境和测试需求,但是难免有一些不足的地方,例如执行海量次数压力请求设置成异步、进一步集成序列化组件实现跨语言支持等。最后给大家留下一个思考,如果业务代码的Service层方法添加基于 @Transactional 的声明式事务管理,通过本框架是否能实现对事务的支持呢?

代码下载地址:https://github.com/kaccnGit/whiteframework.git

使用inetaddress测试目标可达性_纯java手写打造方法级白盒测试框架相关推荐

  1. 使用inetaddress测试目标可达性_白盒测试工具―Winams介绍

    CoverageMaster winAMS : 适用于嵌入式目标机代码的单元测试工具 全面支持嵌入式微机!验证嵌入式C/C++软件 实施以模块为单位的自动化单元测试工具 不需要HookCode 直接使 ...

  2. 使用inetaddress测试目标可达性_PDPS软件机器人虚拟仿真:Smart Place功能介绍与使用方法...

    概述 对于机器人工作站或生产线的虚拟仿真,很大一部分的作用是找出机器人与工装夹具等外围设备的最佳布局位置.市面上大多数的工业机器人虚拟仿真软件都有这种专门用于检测机器人与外围设备之间最佳布局位置的功能 ...

  3. Java Web打印控件(纯java手写版)

    场景: 向数据库中添加一条记录时,调用客户端局域网中的打印机自动将数据打印出来.(当然是客户端打印,服务器端打印还要控件???) 要求: 不弹出.不打印预览.直接自动打印.(静默打印) 需要兼容各大浏 ...

  4. Java 手写一个SQL分页

    Java手写一个类似PageHelper的分页SQL 目前分页插件众所周知的莫过于和mybatis完美融合的PageHelper了,简单两行代码就实现了sql分页,配合PageInfo类,将数据总数量 ...

  5. Java手写线程池-第一代(原创)

    个人简介 作者是一个来自河源的大三在校生,以下笔记都是作者自学之路的一些浅薄经验,如有错误请指正,将来会不断的完善笔记,帮助更多的Java爱好者入门. 文章目录 个人简介 Java手写线程池(第一代) ...

  6. java 手写 jvm高性能缓存

    java 手写 jvm高性能缓存,键值对存储,队列存储,存储超时设置 缓存接口 1 package com.ws.commons.cache; 2 3 import java.util.functio ...

  7. html编写气泡对话框,HTML+CSS入门 纯CSS手写圆角气泡对话框

    本篇教程介绍了HTML+CSS入门 纯CSS手写圆角气泡对话框,希望阅读本篇文章以后大家有所收获,帮助大家HTML+CSS入门. < 嗯--我们设计师强烈要求一定要圆角!圆角的气泡对话框,不要那 ...

  8. Java 手写签字去除背景 背景透明

    Java 手写签字去除背景 背景透明 /*** 白底照片去除白底 形成透明底图片* @param file 需要去除背景的图片* @param Path 去除背景后保存图片的路径* @return t ...

  9. java版安卓按键精灵_纯Java实现跨平台鼠标键盘模拟、找图找色,Java版按键精灵...

    由原本的Java使用JNI调用dll实现模拟辅助操作,升级到纯Java来实现,最新:https://github.com/xnx3/xnx3 仙人辅助_寻仙自动打怪 /** * 鼠标.键盘.延迟等基本 ...

最新文章

  1. PHP mongodb 的使用
  2. python seek tell_Python指针seektell详解
  3. 要锻炼二手交换的能力
  4. 升级到Windows 8.1
  5. 单片机小白学步系列(五) 集成电路、封装相关知识
  6. 利用Caffe实现mnist的数据训练
  7. redis专题:使用redis实现分布式锁
  8. 计算机录入技术五笔输入法教案,五笔输入法教案
  9. 电脑pdf截长图滚动截图保存图片
  10. dos窗口运行.java文件
  11. 华为数字化转型步骤、方法和目标
  12. 怎样用软件测试主机电源,电脑电源功率怎么测试
  13. matlab极坐标图刻度,[转载]如何在Matlab极坐标polar绘图上增加刻度单位
  14. 字节跳动视频面试经历
  15. 解决制作FAT32格式的重装U盘中文件过大问题
  16. RK3288-ANDROID8.1-电源指示灯
  17. 3分钟带你了解微信小程序开发
  18. gyctf_2020_foolish_query(C++中的shared ptr指针的误用)
  19. Win11开机启动项在哪设置
  20. 计算两点方向的方位角

热门文章

  1. 在微软5年,我学到的几个小技能
  2. linux系统克隆安装教程,使用Clonezilla克隆Linux安装的方法
  3. ca 自建 颁发证书_自建 ca 及使用 ca 颁发证书
  4. 用paddleocr识别汉字_(暑期实践)PaddleHub一键OCR中文识别
  5. 自动备份 SourceSafe
  6. Linux下解决高并发socket最大连接数限制,tcp默认1024个连接
  7. 如何实现一个符合规范的Promise
  8. 跟我一起学docker(17)--多节点mesos集群
  9. 干货| PHPCon上TARS-PHP全面解读及PPT下载
  10. Linux下rz,sz与ssh的配合使用