Java web——单元测试框架Junit

本文介绍了软件测试的不同种类,包括单元测试、集成测试和功能性测试,重点讲解了Junit单元测试的注解和断言方法。此外,还讨论了Junit3.X与Junit4.X的区别,并简单介绍了Maven作为项目管理和构建工具的作用及其常用命令。
摘要由CSDN通过智能技术生成

一、软件测试种类:

  • 单元测试:主要是用于测试程序模块,确保代码运行正确。单元测试是由开发者编写并进行运行测试。一般使用的测试框架是Junit或者 testNG。测试用例一般是针对 方法 级别的测试。
  • 集成测试:用于检测系统是否能正常工作。集成测试也是由开发者共同进行测试,与单元测试专注测试个人代码组件不同的是,集成测试是系统进行跨组件测试。
  • 功能性测试:是一种 质量保证过程 以及 基于 测试软件组件的规范下 的 由输入得到输出的一种黑盒测试。功能性测试通常由不同的测试团队进行测试,测试用例的编写要遵循组件规范,然后根据测试输入得到的实际输出与期望值进行对比,判断功能是否正确运行。

1.单元测试的优点:

  • 代码正确性可以得到保证(保证代码运行与我们预想的一样)
  • 程序运行出错时,有利于我们对错误进行查找(因为我们忽略我们测试通过的代码)
  • 有利于提升代码架构设计用于测试的用例应力求简单低耦合,因此编写代码的时候,开发者往往会为了对代码进行测试,将其他耦合的部分进行解耦处理) ······

二、Junit单元测试

  • 单元测试是指对代码中的最小可测试单元进行检查和验证,以便确保它们正常工作(一个单元可以是一个方法、类、包或者子系统)

1.内容

注解:

  • @Test :该注释表示,用其附着的公共无效方法(即用public修饰的void类型的方法 )可以作为一个测试用例;

  • @Before :该注释表示,用其附着的方法必须在类中的每个测试之前执行,以便执行测试某些必要的先决条件(多用于初始化);

  • @BeforeClass :该注释表示,用其附着的静态方法必须执行一次并在类的所有测试之前,发生这种情况时一般是测试计算共享配置方法,如连接到数据库;

  • @After :该注释表示,用其附着的方法在执行每项测试后执行,如执行每一个测试后重置某些变量,删除临时变量等(多用于释放资源);

  • @AfterClass :该注释表示,当需要执行所有的测试在JUnit测试用例类后执行,AfterClass注解可以使用以清理建立方法,如断开数据库连接,注意:附有此批注(类似于BeforeClass)的方法必须定义为静态;

  • @Ignore :该注释表示,当想暂时禁用特定的测试执行可以使用忽略注释,每个被注解为@Ignore的方法将不被执行。

/
* JUnit 注解示例
*/
@Test
public void testYeepay(){
  Syetem.out.println("用@Test标示测试方法!");
}
@AfterClass
public static void paylus(){
  Syetem.out.println("用@AfterClass标示的方法在测试用例类执行完之后执行!");
}

 

 

断言:

 

断言方法都来自 org.junit.Assert 类,其扩展了 java.lang.Object 类并为它们提供编写测试,以便检测故障

简而言之,我们就是通过断言方法来判断实际结果与我们预期的结果是否相同,如果相同,则测试成功,反之,则测试失败

  • void assertEquals([String message], expected value, actual value) :断言两个值相等,值的类型可以为int、short、long、byte、char 或者
  • java.lang.Object,其中第一个参数是一个可选的字符串消息;
  • void assertTrue([String message], boolean condition) :断言一个条件为真;
  • void assertFalse([String message],boolean condition) :断言一个条件为假;
  • void assertNotNull([String message], java.lang.Object object) :断言一个对象不为空(null);
  • void assertNull([String message], java.lang.Object object) :断言一个对象为空(null);
  • void assertSame([String message], java.lang.Object expected, java.lang.Object actual) :断言两个对象引用相同的对象;
  • void assertNotSame([String message], java.lang.Object unexpected, java.lang.Object actual) :断言两个对象不是引用同一个对象;
  • void assertArrayEquals([String message], expectedArray, resultArray) :断言预期数组和结果数组相等,数组的类型可以为int、long、short、char、byte 或者 java.lang.Object

2.JUnit 3.X 和 JUnit 4.X 的区别

JUnit 3.X:

  • 使用 JUnit 3.X 版本进行单元测试时,测试类必须要继承于 TestCase 父类
  • 测试方法需要遵循的原则:
    •  public的
    •  void的
    • 无方法参数
    • 方法名称必须以 test 开头
  • 不同的测试用例之间一定要保持完全的独立性,不能有任何的关联;
  • 要掌握好测试方法的顺序,不能依赖于测试方法自己的执行顺序
/
* 用 JUnit 3.X 进行测试
*/
import junit.framework.Assert;
import junit.framework.TestCase;
public class TestOperation extends TestCase {
  private Operation operation;
  public TestOperation(String name) { // 构造函数
    super(name);
  }
  @Override
  public void setUp() throws Exception { 
    // 在每个测试方法执行 [之前] 都会被调用,多用于初始化
    System.out.println("欢迎使用Junit进行单元测试...");
    operation = new Operation();
  }
  @Override
  public void tearDown() throws Exception { 
    // 在每个测试方法执行 [之后] 都会被调用,多用于释放资源
    System.out.println("Junit单元测试结束...");
  }
  public void testDivideByZero() {
    Throwable te = null;
    try {
      operation.divide(6, 0);
      Assert.fail("测试失败"); //断言失败
    } catch (Exception e) {
      e.printStackTrace();
      te = e;
    }
    Assert.assertEquals(Exception.class, te.getClass());
    Assert.assertEquals("除数不能为 0 ", te.getMessage());
  }
}

JUnit 4.X

  • 使用 JUnit 4.X 版本进行单元测试时,不用测试类继承TestCase父类
  • JUnit 4.X 版本,引用了注解的方式进行单元测试
  • JUnit 4.X 版本我们常用的注解包括:
    • @Before 注解:与JUnit 3.X 中的 setUp() 方法功能一样,在每个测试方法之前执行,多用于初始化

    • @After 注解:与 JUnit 3.X 中的 tearDown() 方法功能一样,在每个测试方法之后执行,多用于释放资源

    • @Test(timeout = xxx) 注解:设置当前测试方法在一定时间内运行完,否则返回错误

    • @Test(expected = Exception.class) 注解:设置被测试的方法是否有异常抛出。抛出异常类型为:Exception.class

/
* 用 JUnit 4.X 进行测试
*/
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestOperation {
  private Operation operation;
  @BeforeClass
  public static void globalInit() { // 在所有方法执行之前执行
    System.out.println("@BeforeClass标注的方法,在所有方法执行之前执行...");
  }
  @AfterClass
  public static void globalDestory() { // 在所有方法执行之后执行
    System.out.println("@AfterClass标注的方法,在所有方法执行之后执行...");
  }
  @Before
  public void setUp() { // 在每个测试方法之前执行
    System.out.println("@Before标注的方法,在每个测试方法之前执行...");
    operation = new Operation();
  }
  @After
  public void tearDown() { // 在每个测试方法之后执行
    System.out.println("@After标注的方法,在每个测试方法之后执行...");
  }
  @Test(timeout=600)
  public void testAdd() { // 设置限定测试方法的运行时间 如果超出则返回错误
    System.out.println("测试 add 方法...");
    int result = operation.add(2, 3);
    assertEquals(5, result);
  }
  @Test
  public void testSubtract() {
    System.out.println("测试 subtract 方法...");
    int result = operation.subtract(1, 2);
    assertEquals(-1, result);
  }
  @Test
  public void testMultiply() {
    System.out.println("测试 multiply 方法...");
    int result = operation.multiply(2, 3);
    assertEquals(6, result);
  }
  @Test
  public void testDivide() {
    System.out.println("测试 divide 方法...");
    int result = 0;
    try {
      result = operation.divide(6, 2);
    } catch (Exception e) {
      fail();
    }
    assertEquals(3, result);
  }
  @Test(expected = Exception.class)
  public void testDivideAgain() throws Exception {
    System.out.println("测试 divide 方法,除数为 0 的情况...");
    operation.divide(6, 0);
    fail("test Error");
  }
  public static void main(String[] args) {
  }
}

区别总结:

  • JUnit 3.X,那么在我们写的测试类的时候,一定要继承 TestCase 类,但是如果我们使用 JUnit 4.X,则不需继承 TestCase 类,直接使用注解就可以
  • 在 JUnit 3.X 中,还强制要求测试方法的命名为“ testXxxx ”这种格式;在 JUnit 4.X 中,则不要求测试方法的命名格式,但还是建议测试方法统一命名为“ testXxxx ”这种格式,简洁明了

        此外,在上面的两个示例中,我们只给出了测试类,但是在这之前,还应该有一个被测试类,也就是我们真正要实现功能的类。现在,将给出上面示例中被测试的类,即 Operation 类:

/
* 定义了加减乘除的法则
*/
public class Operation {
  public static void main(String[] args) {
    System.out.println("a + b = " + add(1,2));
    System.out.println("a - b = " + subtract(1,2));
    System.out.println("a * b = " + multiply(1,2));
    System.out.println("a / b = " + divide(4,2));
    System.out.println("a / b = " + divide(1,0));
  }
  public static int add(int a, int b) {
    return a + b;
  }
  public static int subtract(int a, int b) {
    return a - b;
  }
  public static int multiply(int a, int b) {
    return a * b;
  }
  public static int divide(int a, int b) {
    return a / b;
  }
}

三、Maven

1.定义

        Maven是一个项目管理和综合工具,其简化和标准化项目建设过程;Maven增加可重用性并负责建立相关的任务。

  • Maven为开发人员提供了构建一个完整的生命周期框架
  • 开发者团队可以自动完成项目的基础工具建设, Maven使用标准的目录结构和默认构建生命周期
  • 在多个开发者团队环境时, Maven可以设置按标准在非常短的时间里完成配置工作。由于大部分项目的设置都很简单, 并且可重复使用, Maven让开发人员的工作更轻松, 同时创建报表, 检查, 构建和测试自动化设置
  • Maven继承了Ant的项目构建功能, 并且提供了依赖关系, 项目管理的功能, 因此它是一个项目管理和综合工具
  • Maven核心的依赖管理, 项目信息管理, 中央仓库, 约定大于配置 的核心功能使得Maven成为当前Java项目构建和管理工具的标准选择
  • Maven不仅是构建工具,还是一个依赖管理工具和项目管理工具,它提供了中央仓库,能帮我自动下载构件
  • Maven的主要功能主要分为依赖管理系统、多模块构建、一致的项目结构、一致的构建模型和插件机制

2.Maven常用命令说明

  • mvn clean:表示运行清理操作(会默认把target文件夹中的数据清理)
  • mvn clean compile:表示先运行清理之后运行编译,会将代码编译到target文件夹中
  • mvn clean test:运行清理和测试
  • mvn clean package:运行清理和打包
  • mvn clean install:运行清理和安装,会将打好的包安装到本地仓库中,以便其他的项目可以调用
  • mvn clean deploy:运行清理和发布(发布到私服上面)

3.Maven使用

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tengj</groupId>
    <artifactId>springBootDemo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springBootDemo1</name>
</project>
  •  代码的第一行是XML头,指定了该xml文档的版本和编码方式
  •  project是所有pom.xml的根元素,它还声明了一些POM相关的命名空间及xsd元素

  • 根元素下的第一个子元素modelVersion指定了当前的POM模型的版本,对于Maven3来说,它只能是4.0.0 代码中最重要是包含了groupId,artifactId和version了。这三个元素定义了一个项目基本的坐标,在Maven的世界,任何的pom或者jar都是以基于这些基本的坐标进行区分的

  • groupId定义了项目属于哪个组,随意命名,比如谷歌公司的myapp项目,就取名为 com.google.myapp

  • artifactId定义了当前Maven项目在组中唯一的ID,比如定义hello-world

  • version指定了项目当前的版本0.0.1-SNAPSHOT,SNAPSHOT意为快照,说明该项目还处于开发中,是不稳定的

  • name元素声明了一个对于用户更为友好的项目名称,虽然这不是必须的,但还是推荐为每个POM声明name,以方便信息交流

4.依赖的配置

<project>
...
<dependencies>
    <dependency>
        <groupId>实际项目</groupId>
     <artifactId>模块</artifactId>
     <version>版本</version>
     <type>依赖类型</type>
     <scope>依赖范围</scope>
     <optional>依赖是否可选</optional>
     <!—主要用于排除传递性依赖-->
     <exclusions>
         <exclusion>
           <groupId>…</groupId>
          <artifactId>…</artifactId>
       </exclusion>
     </exclusions>
  </dependency>
<dependencies>
...
</project>

根元素project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:

  • grounpId、artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
  • type:依赖的类型。大部分情况下,该元素不必声明,其默认值为jar
  • scope:依赖的范围
  • optional:标记依赖是否可选
  • exclusions:用来排除传递性依赖

5.依赖范围

  • 依赖范围就是用来控制 依赖和三种classpath (编译classpath,测试classpath、运行classpath)的关系,Maven有如下几种依赖范围:
    • compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-code,在编译、测试和运行的时候都需要使用该依赖。

    • test: 测试依赖范围。使用次依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此依赖。典型的例子是Jnuit,它只有在编译测试代码及运行测试的时候才需要。

    • provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时候无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器以及提供,就不需要Maven重复地引入一遍。

    • runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

    • system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致,但是,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能构成构建的不可移植,因此应该谨慎使用。systemPath元素可以引用环境变量,如:

      • javax.sql jdbc-stdext 2.0 system ${java.home}/lib/rt.jar

    • import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。

上述除import以外的各种依赖范围与三种classpath的关系如下:

b22a77662980c4c5b50512ca59754ad9.png

传递性依赖

        比如一个account-email项目为例,account-email有一个compile范围的spring-code依赖,spring-code有一个compile范围的commons-logging依赖,那么commons-logging就会成为account-email的compile的范围依赖,commons-logging是account-email的一个传递性依赖

        有了传递性依赖机制,在使用Spring Framework的时候就不用去考虑它依赖了什么,也不用担心引入多余的依赖。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中

 依赖范围

        假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。

第一直接依赖和第二直接依赖的范围决定了传递性依赖的范围,如下图所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递依赖范围

a76e56f802a1ae5a8fbf6a7ad1fbc515.png

 

从上图中,我们可以发现这样的规律:

  • 当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;
  • 当第二直接依赖的范围是test的时候,依赖不会得以传递;
  • 当第二直接依赖的范围是provided的时候,只传递第一直接依赖范围也为provided的依赖,切传递依赖的范围同样为provided;
  • 当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile列外,此时传递性依赖范围为runtime

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值