精华内容
下载资源
问答
  • 这篇文章的目的就是用来记录一下java单元测试报告产生的过程,防止时间久忘记。先讲直接使用eclipse去搭建一个测试环境的流程: 上面是一个简单例子的搭建,src中存放源码,test中存放测试代码,注意添加test文件夹后...

    这篇文章的目的就是用来记录一下java单元测试报告产生的过程,防止时间久忘记。

    先讲直接使用eclipse去搭建一个测试环境的流程:

    3317b30099d85e11cca81e75b9b7030e.png

    上面是一个简单例子的搭建,src中存放源码,test中存放测试代码,注意添加test文件夹后请把它add到build_path中。

    接着我们来看一下Calculator.java和CalculatorTest.java

    package com.elastos;

    public class Calculator {

    public String one() {

    return "One";

    }

    public String two() {

    return "Two";

    }

    }

    package com.elastos.test;

    import junit.framework.TestCase;

    import org.junit.Test;

    import com.elastos.Calculator;

    public class CalculatorTest extends TestCase {

    private Calculator calculator = new Calculator();

    @Test

    public void testOne() {

    assertEquals("One", calculator.one());

    }

    @Test

    public void testTwo() {

    assertEquals("One", calculator.two());

    }

    }

    这里有个问题值得说一下,我使用了JUnit4,JUnit4有个最大的特定就是不用继承TestCase,使用Annotation就OK了。于是我就这么干了,结果发现:

    No tests found in com.elastos.test.CalculatorTest

    junit.framework.AssertionFailedError: No tests found in com.elastos.test.CalculatorTest

    找不到测试用例,于是我又让它继承了TestCase,这样一来就立马OK了。继承TestCase之后,[email protected],不用也可以。

    (个人猜想Ant可能会先去判断测试类是否实现了Test接口。有,就提取测试用例;没有,就直接跳过,抛出异常。这里先把猜想放这里,待有时间好好研究一下。

    如果对Test接口有疑惑,可以阅读下我的另一篇文章http://www.voidcn.com/article/p-wfrcimok-cb.html)。

    下面我们就开始使用Ant执行测试用例并且输出测试报告。

    右击项目文档,选择export,就会弹出如下对话框:

    233cf1ee601ae802586f9f0afee03ece.png

    选择建立Ant Buildfile就会在工程目录下产生一个build.xml。当然,这个文件也可以自己手动去创建,做个自定义的build.xml。

    build.xml产生后,右击该文件,然后点击Run As Ant Build就好了。如下:

    6ddb531fb799c34345a444ab0e5ad37f.png

    上面我勾选了三项,分别依次进行了编译、运行CalculatorTest、输出测试报告。

    那么这都是怎么在build.xml中实现的呢?

    上面就代表build,依赖build-subprojects,build-project

    build-subprojects没干什么事情,我们来看build-project,它依赖于init,编译src、test下代码放到bin文件中。

    新建一个bin文件夹,然后把src、test下所有非java结尾的文件连带目录一起拷贝到bin/文件夹下面。

    编译完成后,就去执行CalculatorTest

    新建junit文件夹存放执行结果。fork="yes"表示在一个独立的VM进程中运行tests。

    生成测试报告放在junit文件夹中,实际上就提取TEST-*.xml中的一些信息放到index.html。

    打开index.html,就是你想要的报告。如下:

    e298f746884f14219a1089d5bfb920f2.png

    展开全文
  • JUnit自动

    2010-02-22 18:02:19
    JUnit自动化 无论如何,在你给下一个功能编码前,你必须有个测试证明你的新的功能有效。在你给新的功能编码完成之后,你可以为前边的功能运行测试。这样可以保证新的进展不会对旧的代码产生影响。 如果你的测试很...

    JUnit自动化

    无论如何,在你给下一个功能编码前,你必须有个测试证明你的新的功能有效。在你给新的功能编码完成之后,你可以为前边的功能运行测试。这样可以保证新的进展不会对旧的代码产生影响。

    如果你的测试很严格,为了帮助你设计新的代码并且保证不与老的代码冲突,你就必须把持续的单元测试当作你开发周期的一个普通的部分。你还必须可以任何时候不费工夫地自动运行这些测试。

    3.1  利用Ant进行测试

    1.下载并安装ant,配置ant环境:

    ANT_HOMEE:\devtool\eclipse_j2ee_europa\plugins\org.apache.ant_1.7.0.v200706080842

    PATH%ANT_HOME%\bin;

    cass_path: %ANT_HOME%\lib;

    2.在工程的根目录下创建build.xml构建文件

    <?xml version="1.0" encoding="UTF-8"?>

    <project name="oohla" >

       <property file="build.properties"/>

       <property name="src.dir" location="src"/>

      

       <property name="src.java.dir" location="${src.dir}/main/java"/>

       <property name="src.test.dir" location="${src.dir}/test/java"/>

      

       <property name="target.dir" location="target"/>

       <property name="target.class.java.dir" location="${target.dir}/classes"/>

       <property name="target.class.test.dir" location="${target.dir}/test-classes"/>

      

       <property name="target.report.dir" location="${target.dir}/report"></property>

      

       <echo message="${src.java.dir}"/>

       <!-- 编译src/main/java下文件 -->

       <target name="compile.java">

          <mkdir dir="${target.class.java.dir}" />

          <javac destdir="${target.class.java.dir}" >

             <src path="${src.java.dir}"/>

          </javac>

       </target>

       <!--编译src/test/java下文件-->

       <target name="compile.test" depends="compile.java">

          <mkdir dir="${target.class.test.dir}" />

          <javac destdir="${target.class.java.dir}">

              <src path="${src.test.dir}"/>

              <classpath>

                 <pathelement location="${target.class.java.dir}"/>

              </classpath>

          </javac>

       </target>

       <!-- 编译src/test/javasrc/main/java下文件 -->

       <target name="compile" depends="compile.java,compile.test"></target>

     

       <!-- 运行测试用例 -->

       <target name="test" depends="compile">

           <mkdir dir="${target.report.dir}"/>

           <junit printsummary="yes" haltonerror="no" haltonfailure="no" fork="yes">

               <formatter type="plain" usefile="false" />

               <formatter type="xml"></formatter>

               <!-- <test name="com.doone.util.TestAll" todir="${target.report.dir}"/> -->

               <batchtest todir="${target.report.dir}">

                   <fileset dir="${src.test.dir}">

                      <include name="**/*Test.java"></include>

                      <exclude name="**/Test*All.java"></exclude>

                   </fileset>

               </batchtest>

               <classpath>

                   <pathelement location="${target.class.java.dir}"></pathelement>

                   <pathelement location="${target.class.test.dir}"></pathelement>

               </classpath>

           </junit>

       </target>

      

       <echo message="${target.report.dir}"/>

       <!-- 生成报告文件 -->

       <target name="report" depends="test">

           <mkdir dir="${target.report.dir}/html"></mkdir>

           <junitreport todir="${target.report.dir}">

              <fileset dir="${target.report.dir}">

                 <include name="TEST-*.xml"/><!-- TEST必须大写 -->

              </fileset>

              <report todir="${target.report.dir}/html" />

           </junitreport>

       </target>

      

    </project>

    3.创建build.xml的资源文件build.properties

    4.eclipse中运行build.xml图中的显示的targets就是build.xml文件中的target

     

     

    注:也可在dos下运行自动测试:

    oohla/项目目录下输入:ant report

    5.结果

    控制台会打印出测试结果信息

    同时在工程的target目录下生成report文件

     

    其中target为report生成TESTS-TestSuites.xmlhtml文件,打开html中的index.html页面

     

    页面中的元素:

    1.  Packages 自动化所涉及的包(点击列表中的某个链接可查看包下每个类的运行结果信息)

    2.  Classes 自动化所涉及的类(点击列表中某个链接可查询此类运行结果的详细信息)

    3.  Summary 运行结果信息

     

    3.2  利用maven运行测试

    1.下载并安装maven

    2.创建maven工程

     

    3.下载maven 的surefire-report插件用于htmlde形式直观的显示测试结果信息

    方法:在pom.xml中加入下面代码

    <reporting>

            <plugins>

                <plugin>

                  <groupId>org.apache.maven.plugins</groupId>

                  <artifactId>maven-surefire-report-plugin</artifactId>

                </plugin>

             </plugins>

      </reporting>

    4.下载maven site(用于解析生成运行结果信息的xml文件为直观化的html文件)

    在dos下,在oohla/项目目录下输入:mvn site

    5.运行测试用例

    在dos下,在oohla/项目目录下输入:mvn test

    或点击maven test 如下图

     

     

     

    展开全文
  • Java程序员自动组件注入的几种方式你会哪一种?...注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果。问题1:@Aut...

    cb7bb776be748641ebb52e7617f654b6.png

    Java程序员自动组件注入的几种方式你会哪一种?

    1bc2036444d8cc88e69bd916c56f8186.png

    Java程序员在开发的时候,一般都会遇到组件注入这个问题,回想起当年刚进入Java程序员这个行业的时候也遇到过这样的问题,那么我们今天就来讨论一下组件注入会有哪几种方式,希望能帮助大家快速攻破这个技术难点。

    注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果。

    问题1:@Autowired 、@Inject 、@Resource 的区别

    @Autowired

    [if !supportLists]· [endif]Spring标准

    [if !supportLists]· [endif]按照类型注入(ByType)

    [if !supportLists]· [endif]AutowiredAnnotationBeanPostProcessor对其进行处理

    [if !supportLists]· [endif]支持属性、set方法、构造方法注入,可以联合@Qualifier完成ByName,联合@Primary解决同类多 Bean冲突(不推荐,除非迫不得已)

    [if !supportLists]· [endif]拥有required属性判定空

    [if !supportLists]· [endif]构造函数只能有一个,构造方法注入@Autowire可以不带

    [if !supportLists]· [endif]@Configuration中也可以不带@Autowire

    [if !supportLists]· [endif]推荐set方法、构造方法注入

    @Resource

    [if !supportLists]· [endif]JSR250标准

    [if !supportLists]· [endif]按照名称注入(ByName)

    [if !supportLists]· [endif]CommonAnnotationBeanPostProcessor对其进行处理

    [if !supportLists]· [endif]不支持@Qualifier、@Primary

    [if !supportLists]· [endif]不支持required属性判定空

    @Inject

    [if !supportLists]· [endif]JSR330

    [if !supportLists]· [endif]按照名称注入(ByType)

    [if !supportLists]· [endif]支持@Qualifier、@Primary

    [if !supportLists]· [endif]不支持required属性判定空

    [if !supportLists]· [endif]需要依赖

    <dependency>

    <groupId>javax.inject</groupId>

    <artifactId>javax.inject</artifactId>

    <version>1</version>

    </dependency>

    问题2:下面代码有没有问题?

    @Configuration

    public class WorkerConfiguration {

    @Autowired

    Environment environment;

    public WorkerConfiguration(){

    System.out.println(environment.getProperty("os.name"));

    }

    @Bean

    public Dept dept() {

    System.out.println("dept>>>>>>>>>>>>>>>>>>>");

    return new Dept();

    }

    }

    展开全文
  • 2、dao层和Service层主要是操作数据库,一般不会出错(出错往往是新增了字段而表中没加),而且自动化测试这两层测试类处理不好会产生垃圾数据; 3、写测试类会增加工作量,断言写得不准确即形同虚设; 请...
  • 软件开发习惯中一个细微更改都可能会对软件质量产生巨大改进。将单元测试合并到开发过程中,然后从长远角度来看它可以节省多少时间和精力。本文通过使用代码样本说明了单元测试的种种好处,特别是使用Ant和JUnit带来...
  • 1、何为数据驱动 什么是参数化?...搜索:不同的搜索条件产生不同的搜索结果,搜索也是常见的测试项,单个搜索参数或者多种搜索参数的组合;同样也会产生多个用例。 以上两种场景都有一个共同点,就是测...

    1、何为数据驱动

    什么是参数化?什么又是数据驱动?经常有人会搞不明白他们的关系,浅谈一下个人的理解,先来看两个测试中最常见的场景:

    • 登录:不同的用户名,不同的密码,不同的组合都需要做登录场景的测试,正常的排列组合下可能会产生多个用例
    • 搜索:不同的搜索条件产生不同的搜索结果,搜索也是常见的测试项,单个搜索参数或者多种搜索参数的组合;同样也会产生多个用例。

    以上两种场景都有一个共同点,就是测试的执行步骤不变,变的只是输入的测试数据,那么引出两个概念——参数化数据驱动

    • 参数化:我们在写自动化用例的时候会有很多方法,一般我们都会把数据通过参数来传递给方法,而不会直接在方法中写“死”,所以方法之间的数据传递都是通过参数化来进行,利用参数化进行数据与变量的对应;比如我们的登录账号密码设置在参数中,再将参数传递到方法中。

      public MainPage login(String username, String password) {
              sendKeys(inputUsername,username);
              sendKeys(inputPassword,password);
              click(loginBtn);
              return new MainPage();
      }
      
    • 数据驱动:将参数化中的数据来源变成从外部读取,参数有一个存放数据的地方,在用例执行的时候去去数据;这个数据存储的地方可以是我们定义的数组、hashmap,也可以是从外部文件中(excel、csv、xml、yaml等)读取。
      例如上述的搜索案例,我们可以将搜索条件放入外部文件中,每次执行搜索用例时,去文件中获取数据,根据获取到的数据执行不同的搜索测试即可。

      -
       - 洗衣液
      -
       - 帽子
      -
       - 手套
      

    总结下来

    数据驱动为自动化测试框架的一种设计思想,而参数化是实现数据驱动的一种手段,我们利用参数化来完成数据驱动,从而将数据与脚本分离,增加了框架的可维护性和脚本的复用性

    2、为什么要做数据驱动

    测试数据
    • 在执行测试工作过程中,有很多过程是需要动态变化的,如果每一次的变化都需要编码部署,那么整个执行的流程就会边长;
    • 对于业务测试工程师来说,维护自动化代码有一定的门槛,需要熟悉编程语言和测试框架的结构;
    • 定义好了数据驱动,将变化的数据放入配置文件中进行维护,既便捷(无需找到对应代码修改部署),也降低了维护的门槛(业务测试只需要在配置文件中修改数据即可)
    测试步骤
    • 与测试数据的数据驱动大致相同,主要也是方便业务测试维护,降低维护门槛和代码修改部署出错的风险;修改配置文件,整个业务行为和抽象是不用改变的,当然,在UI自动化中配合PO一起使用会“风味更佳”。
    动态生成测试步骤
    • 手工录制测试步骤,直接生成代码比较困难,可以生成步骤的配置文件,让代码去读配置文件,完成自动化的回放;(此方面本人暂时仅了解过,还未实践落地,理论上是可以实现的。)

    3、在哪里做数据驱动

    不要在哪里做数据驱动
    • 不要在测试用例内完成大量的数据驱动:
      用例通过PO的调用是能够非常清晰展现出业务执行场景的,业务才是用例的核心;一旦在用例里使用了大量数据驱动,如调用各种yaml、csv等数据文件,会造成用例可读性变差,维护复杂度变高;
    可以在哪里做数据驱动
    • 测试数据的数据驱动
    • 测试步骤的数据驱动
      • 定位符
      • 行为流
    • 断言的数据驱动

    4、如何做数据驱动

    4.1 数据格式的选择

    我们要将数据存入文件中,不同的文件有着不同的数据格式,那么作何选择呢?

    • 不同数据格式文件的对比
    文件格式 优点 缺点
    Excel 生成数据方便 二进制文件不利于版本管理
    Csv 可使用Excel编辑 文本格式方便版本管理,不容易描述复杂的层级结构
    Yaml 格式完备,可读性好,可以注释 格式简单
    Xml 格式完备 冗长复杂
    Json 格式完备,可读性一般 不能编写注释,格式死板

    从上述对比结果中,json和yaml对于数据结构的支持和书写程度是较好的;但是,yaml的写法更简洁,并且还可以注释,因此最推荐使用的就是(从表格中的所处都位置也可猜到~)…位于C位的Yaml
    那么到底什么是Yaml,又如何使用,下面简单来了解一下

    4.2 Yaml文件的使用

    yaml的语法

    • 大小写敏感
    • 使用缩进表示层级关系
    • 缩进时不允许使用Tab键,只允许使用空格。
    • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
    • # 表示注释

    yaml支持的三种数据结构

    • 纯量(scalars):单个的、不可再分的值,例如数字、字符串、布尔值等

    • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)

      #键值对形式
      key: value
      #行内对象
      person: { name: allen, age: 25 }
      
    • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)

      #以-开头表示为一个数组里的值
      - A
      - B
      - C
      #数组内嵌套子数组,用一个空格缩进表示
      - 
       - a
       - aa
      - 
       - b
       - bb
      
    • 对象和数组可以结合使用,形成复合结构

      languages:
       - Ruby
       - Perl
       - Python 
      websites:
       YAML: yaml.org 
       Ruby: ruby-lang.org 
       Python: python.org 
       Perl: use.perl.org 
      

    这里举的是最常用的用法,更多的yaml用法可参考阮一峰的博客:
    https://www.ruanyifeng.com/blog/2016/07/yaml.html

    4.3 数据读取-jackson

    既然有了数据存储的地方,那么就要对数据进行读取,这里就要介绍另一位帮手,Java的jackson

    jackson是Java的一个库,用的最多的是jackson-databindjackson-dataformats-text,分别用来处理jsonyaml数据格式,它可以将文件中的数据和Java中的对象建立一种映射关系,把一个文件数据通过类型建立关联,并创建出一个类的实例,反之也可以把一个对象写入文件中。

    4.3.1 jackson-databind

    先来看jackson-databindjson文件的操作

    • 添加maven依赖
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.9.3</version>
      </dependency>
      
    • 写json文件
      1)先创建一个类,包含变量name,age
      public class TestFileSource {
          public String name;
          public int age;
          }
      
      2)创建单元测试,创建ObjectMapper对象,调用writeValuejson文件进行写操作
      @Test
      void writeJson() throws IOException {
          ObjectMapper mapper = new ObjectMapper();
          TestFileSource testFileSource = new TestFileSource();
          mapper.writeValue(new File("..\\demo.json"),testFileSource);
      }
      
      3)得到demo.json文件的结果,从结果可以看到TestFileSource类中的变量已经被写入的json文件中
      {"name":null,"age":0}
      
    • 读json文件
      1)创建单元测试,创建ObjectMapper对象,调用readValue方法对json文件进行数据读取
      @Test
      void readJson() throws IOException {
          ObjectMapper mapper = new ObjectMapper();
          TestFileSource testFileSource = mapper.readValue(TestFileSource.class.getResourceAsStream("/demo.json"), TestFileSource.class);
          System.out.println(testFileSource);
          System.out.println(testFileSource.age);
      }
      
      2)读取结果
      	ApiDemos.testcase.TestFileSource@4562e04d
      	0
      
    • 输出漂亮的json格式
      1)创建单元测试,创建ObjectMapper对象,调用writerWithDefaultPrettyPrinter().writeValueAsString方法可对指定对象进行json数据格式的输出
      @Test
      void prettyPrintJson() throws JsonProcessingException {
          ObjectMapper mapper = new ObjectMapper();
          // pretty print
          String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(new TestFileSource());
          System.out.println(json);
      }
      
      2)打印结果
      {
        "name" : null,
        "age" : 0
      }
      
    • 参考链接
      jackson-databindGitHub地址:https://github.com/FasterXML/jackson-databind
    4.3.2 jackson-dataformats-text

    再来看jackson-dataformats-text,这是一个可以对YAMLCSVPropertiesXML文件进行操作的库,也是目前最常用的,不过这里我们只重点关注其对YAML文件的操作

    • 添加maven依赖

      <dependency>
          <groupId>com.fasterxml.jackson.dataformat</groupId>
          <artifactId>jackson-dataformat-yaml</artifactId>
          <version>2.9.8</version>
      </dependency>
      
    • 读yaml文件
      想要读取yaml文件,最主要的是在new ObjectMapper对象的时候加入new YAMLFactory(),这样就成功切换至yaml操作的状态,然后利用readValue方法就可以完成对yaml文件的数据读取了
      1)创建yaml文件

      name: allen
      age: 11
      

      2)创建ObjectMapper对象,设置new YAMLFactory()

      @Test
      void readYaml() throws IOException {
              ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
              TestFileSource testFileSource = mapper.readValue(TestFileSource.class.getResourceAsStream("/demo2.yaml"), TestFileSource.class);
              System.out.println(testFileSource);
              System.out.println(testFileSource.age);
      }
      
    • 打印结果

      ApiDemos.testcase.TestFileSource@ba2f4ec
      11
      

      readValue的方法中可以看到,第一个参数填的是文件地址,第二个参数就是精髓所在!我们可以给定一个对象类型,或者一个二维数组等,用来产生映射关系,将文件数据和我们的对象绑定,方便数据的读取。
      如上述例子中我们通过TestFileSource的实例化对象来调用age变量,

    • 输出漂亮的yaml格式
      与json输出的方式基本一致,只需要在new ObjectMapper对象的时候加入new YAMLFactory()即可
      1)创建类和类的成员变量,包含纯量、数组和哈希

      public class TestFileSource {
      
          public String name = "tester";
          public int age = 2;
          public Object[][] arr= {{1,2,3,},{"a","b","c"}};
          public HashMap<String,Object> map = new HashMap<String, Object>(){
              {
              put("name","tester");
              put("sex","男");
              }
          };
      }
      

      2)创建单元测试,创建ObjectMapper对象,加入new YAMLFactory() 参数,调用writerWithDefaultPrettyPrinter().writeValueAsString方法可对指定对象进行yaml数据格式的输出

      @Test
      void prettyPrintYaml() throws JsonProcessingException {
          ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
          // pretty print
          String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(new TestFileSource());
          System.out.println(json);
      }
      

      3)打印结果

      ---
      name: "tester"
      age: 2
      arr:
      - - 1
        - 2
        - 3
      - - "a"
        - "b"
        - "c"
      map:
        sex: "男"
        name: "tester"
      
    • 参考链接
      jackson-dataformats-text GitHub地址:https://github.com/FasterXML/jackson-dataformats-text

    5、数据驱动

    上面提到了数据驱动可以在几个方面进行:

    • 测试数据的数据驱动
    • 测试步骤的数据驱动
      • 定位符
      • 行为流
    • 断言的数据驱动

    下面分别来看如何进行数据驱动

    5.1 测试数据的数据驱动

    5.1.1 Junit5的参数化

    说到测试数据的数据驱动,就必然离不开测试框架的参数化,毕竟测试数据是传给用例的,用例是由框架来管理的,这里以目前最推荐使用的Junit5框架为例,介绍参数化的使用

    @ParameterizedTest+@ValueSource参数化

    在Junit5中,提供了@ParameterizedTest注解来实现方法的参数化设置,另外@ValueSource注解用来存放数据,写法如下:

    @ParameterizedTest
    @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
    void palindromes(String candidate) {
        assertTrue(StringUtils.isPalindrome(candidate));
    }
    
    @ParameterizedTest+@CsvSource参数化

    Junit5还提供了@CsvSource注解来实现csv格式的参数传递,写法如下:

    @ParameterizedTest
    @CsvSource({
            "滴滴,滴滴出行",
            "alibaba,阿里巴巴",
            "sougou,搜狗"
    })
    public void searchStocks(String searchInfo,String exceptName)   {
        String name = searchpage.inputSearchInfo(searchInfo).getAll().get(0);
        assertThat(name,equalTo(exceptName));
    }
    
    @ParameterizedTest+@CsvFileSourc数据驱动

    最终,Junit5提供了@CsvFileSourc注解来实现csv数据格式的数据驱动,可以传递csv文件路径来读取数据,写法如下:

    • csv数据文件:
      pdd
      xiaomi
      pdd
      
    • 用例实现:
      @ParameterizedTest
      @CsvFileSource(resources = "/data/SearchTest.csv")
      void choose(String keyword){
      ArrayList<String> arrayList = searchPage.inputSearchInfo(keyword).addSelected();
      }
      

    对于简单的数据结构,可以使用CSV,上面也说过,较为复杂的数据结构,推荐使用yaml,接下来看如何用yaml文件完成测试数据驱动

    @ParameterizedTest+@MethodSource参数化
    • 先来看Junit5提供的另一个注解——@MethodSource,此注解提供的方法是我们做测试数据驱动的核心,它可以让方法接收指定方法的返回值作为参数化的入参,用法是在注解的括号中填入数据来源的方法名,具体用法如下:
      @ParameterizedTest
      @MethodSource("stringProvider")
      void testWithExplicitLocalMethodSource(String argument) {
          assertNotNull(argument);
      }
      
      static Stream<String> stringProvider() {
          return Stream.of("apple", "banana");
      }
      
    @ParameterizedTest+@MethodSource参数化 + jackson yaml数据驱动

    有了@MethodSource的参数化支持,我们就可以在方法中利用jackson库对yaml文件进行数据读取,从而完成数据驱动了

    • 现有如下yaml数据文件,我需要取出testdata中的测试数据
        username: 888
        password: 666
        testdata:
          滴滴: 滴滴出行
          alibaba: 阿里巴巴
          sougou: 搜狗
      
    • 创建Config类:
      import java.util.HashMap;
      
      public class Config {
          public String username;
          public String password;
          public HashMap<String,String> testdata = new HashMap<>();
      }
      
    • 创建Config对象,与yaml文件建立映射关系,读取数据,通过@MethodSource完成数据的参数化传递
      public class TestSteps {
      
      	@ParameterizedTest
          @MethodSource("YamlData")
          public void search(String searchInfo,String exceptName)   {
              String name = searchpage.inputSearchInfo(searchInfo).getAll().get(0);
              assertThat(name,equalTo(exceptName));
          }
      
          static Stream<Arguments> YamlData() throws IOException {
          ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
          Config data = mapper.readValue(Config.class.getResourceAsStream("/demo2.yaml"), Config.class);
          List<Arguments> list = new ArrayList<>();
          Arguments arguments = null;
          for (String key : data.testdata.keySet()) {
              Object value = data.testdata.get(key);
              arguments = arguments(key, value);
              list.add(arguments);
          }
          return Stream.of(list.get(0),list.get(1),list.get(2));
         }
      
      为了保证运行通过,可以先简单打印验证一下:
      在这里插入图片描述

    5.2 测试步骤的数据驱动

    对于测试步骤的数据驱动主要针对两点:

    • 定位符:
      我们做App自动化的时候可以把定位符合定位器直接写在PO中,也可以将其剥离出来,写在类似yaml的文件中,定义好格式个对象的映射关系即可完成定位符的数据驱动。
    • 行为流:
      与定位符的剥离思想一致,行为流原本也是写在PO中的各个方法,这些行为流和定位符是紧密关联的,因此也可以剥离出来,和定位符在一起组成测试步骤的数据驱动。

    好比下面这样的,以雪球App的搜索场景为例:

    public class SearchPage extends BasePage{
    	//定位符
        private By inputBox = By.id("search_input_text");
        private By clickStock = By.id("name");
        private By cancel = By.id("action_close");
    	//行为流
    	//搜索股票
        public SearchPage search(String sendText){ 
            sendKeys(inputBox,sendText);
            click(clickStock); 
            return this;
        }
    	//取消返回
    	public App cancel(){
           click(cancel);
           return new App();
        }
    }
    

    :测试步骤的数据驱动是指把PO中变化的量剥离出来,不是对用例里的调用步骤进行封装
    在上面已经提到过不要在测试用例内完成大量的数据驱动:
    用例通过PO的调用是能够非常清晰展现出业务执行场景的,业务才是用例的核心;一旦在用例里使用了大量数据驱动,如调用各种yaml、csv等数据文件,会造成用例可读性变差,维护复杂度变高;

    5.2.1 设计思路

    首先来考虑我们的剥离到yaml中的数据结构

    • 做测试步骤的数据局驱动我们希望可以将一个用例中的步骤方法清晰的展示出来,在对应的方法中包括了方法对应的定位符和行为流,这样能和PO中的结构保持一致,更易读易维护;如下:
      search:
          steps:
            - id: search_input_text
              send: pdd
            - id: name
      cancel:
          steps:
            - id: action_close
      
    • 另外我们还要考虑扩展性,之前提到了还有测试断言的数据驱动,另外还有一点没提到的是,框架的健壮程度还要考虑被测系统(Android,IOS)的通用性、版本变更、元素定位符的多样性等;这样考虑的话就应该有多个分类,一个分类中包含了PO中的所有方法,一个分类中包含了版本、系统等信息等,如下(SearchPage.yaml):
      #方法
      methods:
        search:
          steps:
            - id: search_input_text
              send: pdd
            - id: name
        cancel:
      	steps:
      	  - id: action_close
      
      #定位符对应系统、版本信息
      elements:
        search_input_text:
          element:
          ...
      
      #断言
      asserts:
        search:
        	assert:
        	...
        cancel:
          assert:
          ...
      
    • 按照上述的思路,以搜索步骤为例,我们需要一个Model类,用来映射不同的数据模块(方法、版本、断言),对不同的模块需要一一对应的类,类的成员变量结构与yaml文件中的结构保持一致:
      1)创建PageObjectModel
      import java.util.HashMap;
      public class PageObjectModel {
      	public HashMap<String, PageObjectMethod> methods = new HashMap<>();
          public HashMap<String, PageObjectElement> elements = new HashMap<>();
      	public HashMap<String, PageObjectAssert> asserts = new HashMap<>();
      }
      
      2)创建对应数据模块的类PageObjectMethod
      public class PageObjectMethod {
          public List<HashMap<String, String>> getSteps() {
              return steps;
          }
      
          public void setSteps(List<HashMap<String, String>> steps) {
              this.steps = steps;
          }
      
          public List<HashMap<String,String>> steps = new ArrayList<>();
      }
      
      3)实现解析yaml数据的方法,完成PO中行为流的封装;
      • 首先按照之前介绍过的通过jackson来解析yaml数据,我们需要文件的地址,另外我们还需要知道当前执行的方法,用来去yaml中取方法对应的定位符行为流,所以初步设想应该有methodpath两个参数:
      public void parseSteps(String method,String path){
              ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
              try {
                  PageObjectModel model = mapper.readValue(BasePage.class.getResourceAsStream(path),PageObjectModel.class);
                  parseStepsFromYaml(model.methods.get(method));
              }catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
      • 上面的方法中可以看到调用了一个parseStepsFromYaml方法,这个方法是将从yaml中获取到的数据进行处理,拿到对应方法定位符再拿到定位符紧跟的行为流完成对应的操作步骤(点击、输入、获取属性等);之所以将这个方法单独抽离出来,是因为后面会对parseSteps重载,方便复用,后面会介绍到。
        如下:我们要通过methods里的search方法拿到对应的步骤steps里的id,在根据id下的send值进行输入操作
      methods:
       search:
        steps:
         - id: search_input_text
           send: pdd
        - id: name
      
      private void parseStepsFromYaml(PageObjectMethod steps){ //获取方法名method
              steps.getSteps().forEach(step ->{
                  WebElement element = null;
                  if (step.get("id") != null){
                      element = findElement(By.id(id));
                  }else if (step.get("xpath") != null){
                      element = findElement(By.id(step.get("xpath")));
                  }else if (step.get("aid") != null){
                      element = findElement(MobileBy.AccessibilityId(step.get("aid")));
                  if (step.get("send") != null){
                      element.sendKeys(step.get("send"));
                  }else if (step.get("get") != null){
                        findElement(by).getAttribute(get);
                  }
                  else {
                      element.click();  //默认操作是点击
                  }
              });
          }
      
      4)这个时候再回到我们的PO里,就变成了这个样子,看一下PO是不是一下子变得简洁了许多:
      public class SearchPage extends BasePage{
      	//行为流
      	//搜索股票
          public SearchPage search(String sendText){ 
              parseSteps("search","/com.xueqiu.app/page/SearchPage.yaml");
              return this;
          }
      	//取消返回
      	public App cancel(){
             parseSteps("cancel","/com.xueqiu.app/page/SearchPage.yaml");
             return new App();
          }
      }
      

    到这里,测试步骤的数据驱动算是完成了一个基本模板,还有很多可以优化的地方,比如上面的SearchPagePO中,parseSteps的两个参数methodpath都是有规律可循的:

    • method和当前执行的方法名是定义好保持一致的
    • 当前PO所对应的yaml文件的path是固定的

    下面针对这个点做个小优化

    5.2.2 框架优化

    这里将会对上一节中的parseSteps方法进行优化,减少重复性工作

    • 先来解决方法名method的问题,来看Thread的一个方法:Thread.currentThread().getStackTrace()
      利用这个方法可以打印出当前方法执行的全部过程,写单测来验证,将每一步的方法名都打印出来:

      void testMethod(){
              Arrays.stream(Thread.currentThread().getStackTrace()).forEach(stack ->{
                  System.out.println(stack.getMethodName());
              });
              System.out.println("当前调用我的方法是:"+Thread.currentThread().getStackTrace()[2].getMethodName());
          }
      
      @Test
      void getMethodName(){
          testMethod();
          }
      

      执行结果:

      getStackTrace
      testMethod   //当前执行的方法
      getMethodName  //调用testMethod的方法
      invoke0
      invoke
      invoke
      invoke
      invokeMethod
      proceed
      //...这里省略中间很多不重要的部分
      execute
      execute
      startRunnerWithArgs
      startRunnerWithArgs
      prepareStreamsAndStart
      main
      当前执行的方法是:getMethodName
      

      从结果中可以看到,当方法被调用时,调用它的方法名会在输出结果的索引2位置,因此通过此方法就可以成功的拿到我们所需的method参数

    • 再来解决yaml文件路径的path参数,这里可以借助java.lang.Class.getCanonicalName()方法,此方法可以返回当前类名,包括类所在的包名,如下:

      @Test
      void getPath(){
          System.out.println(this.getClass().getCanonicalName());
      }
      
      //打印结果
      com.xueqiu.app.testcase.TestSteps
      
    • 稍加改造就可以变成地址信息:

      @Test
      void getPath(){
          System.out.println(this.getClass().getCanonicalName());
          String path = "/com.xueqiu.app" + this.getClass().getCanonicalName().split("app")[1].replace(".", "/") + ".yaml";
          System.out.println(path);
      }
      

      打印结果:

      com.xueqiu.app.testcase.TestSteps
      /com.xueqiu.app/testcase/TestSteps.yaml
      

      这样我们就将当前类的信息转变成了一个地址信息,后面我们只需要将对应的yaml文件以和类相同的命名相同路径结构存放在resources目录下即可

      • 现在methodpath参数的问题都解决了,在来看现在的parseSteps方法:
      //解析步骤
      public void parseSteps(String method) {
          ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
          String path = "/com.xueqiu.app" + this.getClass().getCanonicalName().split("app")[1].replace(".", "/") + ".yaml";
          try {
              PageObjectModel model = mapper.readValue(this.getClass().getResourceAsStream(path),PageObjectModel.class);
              parseStepsFromYaml(model.methods.get(method));
          }catch (IOException e) {
              e.printStackTrace();
          }
      }
      
      public void parseSteps(){
          String method = Thread.currentThread().getStackTrace()[2].getMethodName();
          parseSteps(method);
      }
      
      • 此时再次回到SearchPage的PO中,可以看到更加的简洁了,甚至变成了“傻瓜操作”:
      public class SearchPage extends BasePage{
      
          public SearchPage search(){ 
              parseSteps(); 
              return this;
          }
      
          public App cancel(){
              parseSteps();
              return new App();
          }
      }
      
      send参数化处理
      • 看似好像大功告成,又出现了新的问题,不知道大家注意到没有,search方法其实是需要send值的,而现在我们的send值是写死在yaml中的,这反而违背了我们参数化和数据驱动的原则:
      methods:
        search:
          steps:
            - id: search_input_text
              send: pdd  #send的内容被写死在了这里
            - id: name
      
      • 所以我们需要继续解决这个问题,将send的值进行参数化

      1) 既然是参数化,那就要把send的值变成参数,这里用$sendText来表示是参数

      methods:
        search:
          steps:
            - id: search_input_text
        #      send: pdd
              send: $sendText #表示参数化
            - id: name
      

      2)在search方法中使用HashMap将用例传递过来的测试数据保存至其中,用来传递到parseSteps步骤解析方法中。

      public SearchPage search(String sendText){ 
          HashMap<String,Object> map = new HashMap<>();
          map.put("sendText",sendText);
          setParams(map);
          parseSteps(); 
          return this;
      }
      

      3)再在parseSteps方法所处的类中添加HashMap类型的params变量,用来接收PO传过来的sendText测试数据

      private static HashMap<String,Object> params = new HashMap<>();
      
      public HashMap<String, Object> getParams() {
          return params;
      }
      //测试步骤参数化
      public void setParams(HashMap<String, Object> params) {
          this.params = params;
      }
      

      4)最后修改parseStepsFromYaml方法中的send值获取方式,将占位的参数$sendText替换成实际传递过来的测试数据sendText

      if (step.get("send") != null){
              String send = step.get("send").replace("$sendText",params.get("sendText").toString());
              element.sendKeys(send);
      }
      
    getAttribute实现

    在文章前面提到过获取元素属性,在自动化测试过程中,经常要获取元素属性来作为方法的返回值,以供我们进行其他操作或断言,其中text是我们最常获取的属性,下面来实现此方法的数据驱动

    在上面的搜索股票场景下,加上一步获取股票的价格信息

    • 先看一下思路,按照之前的设计,在yaml中的定位符后面跟着的就是行为流,假定有一个getCurrentPrice方法,通过get text来获取text属性,写法如下:

      getCurrentPrice:
          steps:
            - id: current_price
              get: text
      
    • 这个时候就可以在parseStepsFromYaml方法中加入属性获取的解析逻辑,通过get来传递要获取的属性

      if (step.get("send") != null){
              String send = step.get("send").replace("$sendText",params.get("sendText").toString());
              element.sendKeys(send);
      }else if (step.get("get") != null){
              String attribute = element.getAttribute(step.get("get"));
                      }
      
    • 接着我们到SearchPagePO中实现getCurrentPrice方法,这个时候就会发现一个问题:

      public Double getCurrentPrice(){
              parseSteps();
             // return ???;
             }
      

      没错,text属性获取到了,可以没有回传出来,getCurrentPrice方法没有return值;我们要将parseStepsFromYaml获取到的属性值通过一个“中间商"给传递到getCurrentPrice方法中,然后再return到用例中供我们断言使用

      • 语言描述比较晦涩,下面我以一个市场供需买卖的场景来说明整个设计流程:
        在这里插入图片描述

      1)产生市场需求yaml中定义好数据结构

      methods:
        search:
          steps:
            - id: search_input_text
              send: $sendText
            - id: name
      
        getCurrentPrice:
          steps:
            - id: current_price
              get: text
              dump: price
      
        cancel:
          steps:
            - id: action_close
      

      2) 实现“中间商”,这个“中间商”就是一个HashMap,将它取名为result

      private static HashMap<String,Object> result = new HashMap<>();
      
      //测试步骤结果读取
      public static HashMap<String, Object> getResult() {
          return result;
      }
      

      3)供应商根据市场需求产生产品并提供给中间商,获取属性并将属性值存入result

      if (step.get("send") != null){
              String send = step.get("send").replace("$sendText",params.get("sendText").toString());
              element.sendKeys(send);
      }else if (step.get("get") != null){
              String attribute = element.getAttribute(step.get("get"));
              result.put(step.get("dump"),attribute);
                      }
      

      4)消费者根据自己的需求中间商那里拿到商品,从resultgetprice的值

      public Double getCurrentPrice(){
          parseSteps();
          return Double.valueOf(getResult().get("price").toString());
      }
      

      这样就成功完成了这个交易场景的闭环,股票价格price被成功返回至用例中

    5.3 断言的数据驱动

    有了上面的铺垫,断言的数据驱动就显得简单多了,我个人有时候也简单的把它归为测试数据的驱动中

    • 因为每个测试数据在传入用例跑完后,都会对应有断言来进行结构判定,因此将测试数据对应的断言数据在一个yaml文件中,写入一个数组里,再同测试数据一起获取传入到用例中
      -
       - didi
       - 100d
      -
       - alibaba
       - 120d
      -
       - sougou
       - 80d
      
      • 回到最初的测试数据数据驱动,把数据获取传入
      @ParameterizedTest
      @MethodSource("searchYamlData")
      void search(String searchInfo,String exceptPrice ){
          Double currentPrice = searchPage.search(searchInfo).getCurrentPrice();
          assertThat(currentPrice,greaterThanOrEqualTo(Double.parseDouble(exceptPrice)));
      }
      
      static Stream<Arguments> searchYamlData() throws IOException {
          Arguments arguments = null;
          List<Arguments> list = new ArrayList<>();
          ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
      
          String path1 = "/com.xueqiu.app" + TestSearch.class.getCanonicalName().split("app")[1].replace(".","/") + ".yaml";
          Object[][] searchData = mapper.readValue(TestSearch.class.getResourceAsStream(path1), Object[][].class);
          for (Object[] entrySet : Arrays.asList(searchData)){
              String key = Arrays.asList(entrySet).get(0).toString();
              String value =  Arrays.asList(entrySet).get(1).toString();
              arguments = arguments(key,value);
              list.add(arguments);
          }
          return Stream.of(list.get(0),list.get(1),list.get(2));
      }
      

    注:其实这里应该说还是测试数据驱动,并不能算是断言的驱动,真正想做成断言的驱动还需要封装类似测试步骤驱动的形式,目前我没有做这层封装,因为在使用中发现断言的类型很多,直接在用例里面写也很方便易读,加上目前时间精力也有限,待后续需要的时候再继续补充~

    6、运行效果

    说的再多,不如实际跑一下,检验一下框架封装后的实际运行效果

    在这里插入图片描述

    • 用例运行结果:
      在这里插入图片描述

    7、写在最后

    折腾了这么久,总算是“大功告成”了,之所以加个引号,是因为这个仅仅是个开始,只能算是初具雏形,像文章中提到的被测系统切换、版本切换、多元素查找等都还未实现,后续会持续学习更新;
    我本人也是在学习踩坑中,本文主要问自己的学习笔记总结,经验少,能力弱,基础差,可能有很多错误或表述不恰当的地方;倘若有幸被哪位读者大佬看到,希望多多包涵,也请不要吝啬您的指教,欢迎指出,我会虚心讨教,谢谢~

    展开全文
  • 这个问题是我在做支付宝自动对账功能时发现的,因为支付宝对账接口下载的对账单是zip压缩文件形式返回的,所以要实现自动对账功能需要在我调用支付宝对账接口下载完zip文件之前先启动一个线程去监控我用于存放zip...
  • 今天在执行junit代码的时候,发现junit一直在执行(时间太长就会自动结束,产生提示信息为Could not open JDBC Connection ),也不报错,也不结束,一直提示: Logging initialized using 'class org.apache.ibatis....
  • 由于需要做自动化测试,所以需要比较完善的单元测试。但是又因为某些测试的执行依赖另外一个测试产生的结果,所以希望所写的test case按照自己希望的顺序来执行。 随后博主查阅资料发现了FixMethodOrder注解,可以...
  • 解决报错问题: Runner org.junit.internal.runners.ErrorReportingRunner does not support ...进行spring单元测试的时候产生报错,且spring自动装配的组件的值为null 解决:导包导错了,应该将org.junit.j...
  • 回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。自动回归测试将大幅降低系统测试、维护升级等阶段的成本。回归测试作为软件生命周期的一个组成部分,在整个软件测试...
  • 利用 Ant 和 JUnit 进行开发

    千次阅读 2004-10-10 11:43:00
    11 月内容: 从类开始 自动化单元测试 集成到构建中 了解测试的工作原理 并非完全无痛 实现 24x7 参考资料 关于作者 软件开发习惯中一个细微更改都可能会对软件质量产生巨大改进。将单元测试合并到开发
  • 自定义异常 ...2:自动产生类中的方法:右击鼠标->source->Generate Constructors from SuperClass... 导包: 在工程目录下建文件夹lib->将jar包拖进来->右击工程->properties ->Java Build Pa
  • 自动化测试

    2014-04-23 20:36:00
    从基本的由一类似于是xUnit的单元测试框架(NUnit,JUnit,CppUnit,HttpUnit)开始, 每一个测试需要完成一些设置在它开始之前,最后还需要做一些清理工作,并且产生详细的报告与结果。自动化测试的抽象层图是这样的: ...
  • 自动化测试软件分类

    千次阅读 2017-01-19 17:23:13
    从基本的由一类似于是xUnit的单元测试框架(NUnit,JUnit,CppUnit,HttpUnit)开始, 每一个测试需要完成一些设置在它开始之前,最后还需要做一些清理工作,并且产生详细的报告与结果。自动化测试的抽象层图是这样的: ...
  • Mybatis的JDBC提交设置/关闭mysql自动提交 可能大家会遇到有时对数据的 cuid操作不需要进行提交就能成功执行,有时候需要进行提交。这样不可控的行为是我们所不能接 受的。那么下面将会描述问题、介绍问题的...
  • 二、产生的原因 虽然idea中自动生成test单元测试类, pom文件也生成导入了 但是里面自动生成的环境并不支持@RunWith;所以 需要重新单独加入junit依赖; 这样就能够正常导入@RunWith注解的包了. 三、注解的规范使用 ...
  • 单元测试:这一层主要由产品开发同学负责, 比如使用Junit开发方法的单元测试用例, 通常这些用例都是静态的, 测试数据通过各种Mock技术产生。  接口测试:这一层主要由测试同学负责,比如我们需要对Http, HSF, TOP/...
  • 说实在的,大家应该取原站点看呀:支持原创,强烈的额。...转载产生效益。   另外补充了下源码中没有的: 1.改成了junit。 2.加了maven仓位配置。 public class AppiumHelloTest { private AppiumD...
  • Selenium测试框架的报告工具

    千次阅读 2014-07-27 10:03:21
    使用Selenium自动化测试工具的好处就是它支持很多种编程语言开发测试脚本,而都有对应的成熟的测试框架,这些测试框架提供灵活的测试引擎来帮助执行测试,并产生测试报告。例如,java编程语言的JUnit和TestNG,.NET...
  • 实战 TestNG 监听器

    2016-01-19 19:49:00
    TestNG 是一个开源的自动化测试框架,其灵感来自 JUnit 和 NUnit,但它引入了一些新功能,使其功能更强大,更易于使用。TestNG 的设计目标是能够被用于进行各种类型测试:单元测试、功能测试,端到端测试、集成测试...
  • TestNG

    2018-01-31 15:54:59
    TestNG 是一个开源的自动化测试框架,其灵感来自 JUnit 和 NUnit,但它引入了一些新功能,使其功能更强大,更易于使用。TestNG 的设计目标是能够被用于进行各种类型测试:单元测试、功能测试,端到端测试、集成测试...
  • testng监听器

    千次阅读 2016-07-14 10:21:04
    TestNG 是一个开源的自动化测试框架,其灵感来自 JUnit 和 NUnit,但它引入了一些新功能,使其功能更强大,更易于使用。TestNG 的设计目标是能够被用于进行各种类型测试:单元测试、功能测试,端到端测试、集成测试...
  • 由于单元测试通常发生在错误产生之后不久,因此通过单元测试发现错误然后进行修正的代价通常比较小。单元测试是如此重要,以至于一些极限编程爱好者主张任何未经测试的代码都应该被自动删除。JUnit是Jav
  • 一直都在写代码,却不知道代码怎么测试,后来由于工程需要,测试是必须的一件事情的...在AS里面,当新建一个android工程时,会在src目录下自动产生一个main目录,还有一个androidTest目录,在androidTest目录里面会有
  • IDEA SpringBoot单元测试

    2020-04-03 19:19:56
    IDEA SpringBoot单元测试 1、准备阶段 首先选中要测试的项目,...这里我选择的是Junit5,class name是自动生成的,在Member中可以选择要进行测试的方法,选中后点击OK,即可产生相应的测试方法 3、测试类的编写注意事...
  • Idea配置Maven

    2020-11-19 15:39:59
    选择Enable Auto Import以后修改Pom.xml的依赖后就会自动导入jar包了。 4.IDea通过Maven创建Web工程 3.Mavne常用命令 clean:清除编译产生的target文件夹内容 compile:该命令可以对src/main/java目录的下的...

空空如也

空空如也

1 2 3
收藏数 47
精华内容 18
关键字:

自动产生junit