一、软件测试种类:
- 单元测试:主要是用于测试程序模块,确保代码运行正确。单元测试是由开发者编写并进行运行测试。一般使用的测试框架是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的关系如下:
传递性依赖
比如一个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是传递性依赖。
第一直接依赖和第二直接依赖的范围决定了传递性依赖的范围,如下图所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递依赖范围
从上图中,我们可以发现这样的规律:
- 当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;
- 当第二直接依赖的范围是test的时候,依赖不会得以传递;
- 当第二直接依赖的范围是provided的时候,只传递第一直接依赖范围也为provided的依赖,切传递依赖的范围同样为provided;
- 当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile列外,此时传递性依赖范围为runtime