JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。远比使用main测试方便
添加JUnit支持
- idea安装
JUnit Generator
插件,设置的其他设置中设置版本JUnit4
添加Junit依赖
<groupId> junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
选择src下与main同级的test目录,右键点击:
**Mark Directory as/Test Resources Root **
将test目录转为单元测试目录- 在test的子目录下创建单元测试类
JUnit基础语法
- 在测试类下编写一个测试方法,类似main()的作用,所有需要测试的东西都写测试方法里,就跟main一样
- @Test类可以有很多个
@Test
public void
test(){....}
除测试类方法名可以自定义,注解,public,void一个都不能省略,否则报错
测试方法的返回值必须为void
可以有多个测试方法
**
在编写单元测试的时候,我们要遵循一定的规范:
- 单元测试代码本身必须非常简单,能一下看明白,决不能再为测试代码编写测试;
- 每个单元测试应当互相独立,不依赖运行的顺序;
- 测试时不但要覆盖常用测试用例,还要特别注意测试边界条件,例如输入为
0
,null
,空字符串""
等情况。Assertion断言:是否符合预期
assertEquals(expected, actual)
是最常用的测试方法,它在Assertion
类中定义。
expected为期望的返回值,actual为真实值,如果真实值==期望值,就正常输出,否则**额外输出**
错误信息
真实值和期望值可以是一个方法,也可以是一个表达式
比如有一个int 返回值的score方法,返回的是计算得到的成绩
static boolean dd(int a){ return a>=60; }
assertEquals(true,a( score() ) );
- 如果成绩>=60,a()返回true,真实值符合期望值。就正常输出其他语句
- 否则额外输出
java.lang.AssertionError:
Expected :true
Actual :false
其他断言方法,用法差不多
assertTrue()
: 期待结果为true
assertFalse()
: 期待结果为false
assertNotNull()
: 期待结果为非null
assertArrayEquals()
: 期待结果为数组并与期望数组每个元素的值均相等
有一个要注意的
使用浮点数时,由于浮点数无法精确地进行比较,因此,我们需要调用assertEquals(double expected, double actual,
double delta
)
这个重载方法,指定一个误差值
Fixture:自动准备与清理
@…Each
测试时我们经常需要创建一个类的对象,如果有很多个@Test,我们需要创建很多对象,用完后还需要清理
- 我们可以通过
@BeforeEach
进行初始化,使用@AfterEach
进行清理- 为所有需要对象的类**创建一个共用的方法,该方法标记为
@beforeEach
。**该方法作用是创建对象@AfterEach
同理
- 为所有需要对象的类**创建一个共用的方法,该方法标记为
每一个beforeEach和AfterEach都会在每一个@Test之前或者之后执行
public class CalculatorTest { Calculator calculator; @BeforeEach public void setUp() { this.calculator = new Calculator(); } @AfterEach public void tearDown() { this.calculator = null; } @Test void testAdd() { assertEquals(100, this.calculator.add(100)); } } public class Calculator{public long add(){...}}
可以看出,首先声明一个对象。每运行一个@Test时@before方法会为其初始化一个对象。@Test中要使用对象直接**
this.对象
**即可。表示使用beforeEach为当前test创建的对象(这里不用也正确结果)@BeforeEach和after的方法写法尽量按照3-10这样写
@…All
JUnit还提供了@BeforeAll
和@AfterAll
- 它们在运行所有@Test前后运行。在所有
@Test
方法运行前后仅运行一次,因此,它们只能初始化静态变量- 事实上,
@BeforeAll
和@AfterAll
也只能标注在静态方法上。
- 事实上,
- 一般用于比较繁琐的资源初始化与清理。如初始化数据库
例如
public class DatabaseTest {
static Database db;
@BeforeAll
public static void initDatabase() {
db = createDb(...);
}
@AfterAll
public static void dropDatabase() {
...
}
}
each与all区别
- 对于实例变量,使用each。对于静态变量,使用all
- each创建的是不同实例。 all创建的实例是唯一的,会影响所有@test
- 每一个test内的成员变量都是独立的,不同test的成员变量不能互相访问
异常测试
JUnit提供assertThrows()
来捕获一个指定的异常,该方法有2个参数 ```java public class Factorial { public static long fact(long n) {
} }if (n < 0) { throw new IllegalArgumentException(); } long r = 1; for (long i = 1; i <= n; i++) { r = r * i; } return r;
@Test void testNegative() { assertThrows(IllegalArgumentException.class, new Executable() { @Override public void execute() throws Throwable { Factorial.fact(-1); } }); }
简化: @Test void testNegative() { assertThrows(IllegalArgumentException.class, () -> { Factorial.fact(-1); }); }
1. **对需要检查的地方设置异常处理:这里我们检查参数是否异常`throw new IllegalArgumentException() //不合法参数异常` 如果检查的数>20 ,则超出long范围,则改为抛出`ArithmeticException()`**
1. **asserThrows()会捕获一个异常(第一个参数) 第二个参数为可能产生异常的代码,我们****可以采用匿名内部类/Lambda的方法将可能异常代码封装为一个内部类(第二个参数)**
- 注意`assertThrows()`在捕获到指定异常时表示通过测试,未捕获到异常,或者捕获到的异常类型不对,均表示测试失败。
<a name="dKhJY"></a>
# 条件测试:按条件选择是否测试该Test类
**enable都表示只在某种情况下运行,disable表示不在某种情况下运行**
- 不运行该@Test:@Test前追加`@Disable` 虽然没有运行,但是结果会显示
`Tests run: 68, Failures: 2, Errors: 0, Skipped: 5`
- 不同平环境测试(如不同系统,不同java版本)
- 不同系统 `@DisabledOnOs和@EnableOnOs` 参数有 `OS.WINDOWS` `OS.MAC` `OS.LINUX` `OS.....` os表示系统
- 不同java版本`@DisabledOnJre` 参数为`JRE.JAVA_?` ?表示版本号
- 不同系统位数:`@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")`表示只在64位系统上运行 os.arch不是archlinux系统的意思,是系统构架的意思
- 需要传入指定参数才运行`@EnabledIfEnvironmentVariable`
如`@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")`表示传入DEBUG=true时才运行
<a name="RjLwq"></a>
# 获取系统变量
`System.getProperty()`可以获取系统变量,如系统类型,java版本... 返回字符串
```java
public static void main(String[] args) {
Properties props = System.getProperties();
//可以直接sout(System.getProperty("java.version"));这种形式输出
System.out.println("Java的运行环境版本:"+props.getProperty("java.version"));
System.out.println("Java的运行环境供应商:"+props.getProperty("java.vendor"));
System.out.println("Java的运行环境供应商URL:"+props.getProperty("java.vendor.url"));
System.out.println("Java的安装路径:"+props.getProperty("java.home"));
System.out.println("Java的虚拟机规范名称:"+props.getProperty("java.vm.specification.name"));
System.out.println("Java的虚拟机实现版本:"+props.getProperty("java.vm.version"));
System.out.println("Java的虚拟机实现供应商:"+props.getProperty("java.vm.vendor"));
System.out.println("Java的虚拟机实现名称:"+props.getProperty("java.vm.name"));
System.out.println("Java的类格式版本号:"+props.getProperty("java.class.version"));
System.out.println("Java的类路径:"+props.getProperty("java.class.path"));
System.out.println("加载库时搜索的路径列表:"+props.getProperty("java.library.path"));
System.out.println("默认的临时文件路径:"+props.getProperty("java.io.tmpdir"));
System.out.println("一个或多个扩展目录的路径:"+props.getProperty("java.ext.dirs"));
System.out.println("操作系统的名称:"+props.getProperty("os.name"));
System.out.println("操作系统的构架:"+props.getProperty("os.arch"));
System.out.println("操作系统的版本:"+props.getProperty("os.version"));
System.out.println("分件分隔符:"+props.getProperty("file.separator"));//在 unix 系统中是"/"
System.out.println("路径分隔符:"+props.getProperty("path.separator"));//在 unix 系统中是":"
System.out.println("行分隔符:"+props.getProperty("line.separator"));//在 unix 系统中是"/n"
System.out.println("用户的账户名称:"+props.getProperty("user.name"));
System.out.println("用户的主目录:"+props.getProperty("user.home"));
System.out.println("用户的当前工作主目录:"+props.getProperty("user.dir"));
}
参数化测试
可以将一组参数用一个方法进行测试。即一个测试方法需要接收至少一个参数,然后,传入一组参数反复运行。
- 使用
@ParameterizedTest注解
进行参数化测试- 参数化测试注解即特殊的@Test,所以不需要再写@Test
@ValueSource(ints={...})
**定义一组参数,参数会自动传入测试方法中,是单参数测试(即一次只传一个参数) {}内为一组参数**@ParameterizedTest //替代@Test,使得测试方法必须接收一个以上参数 @ValueSource(ints = { -1, -5, -100 }) //定义一组参数 void testAbsNegative(int x) { //参数逐个进行测试,自动传入测试方法 assertEquals(-x, Math.abs(x)); //Math.abs是绝对值函数 }
参数化测试实际上并不会这么简单,往往是传入2个参数(即预期输出与实际输出)
这时候使用@MethodSource
注解或者是@CsvSource
进行传入参数
- @MethodSource:编写一个与测试方法同名的静态方法(也可以指定方法名,更麻烦)
- 该静态方法返回参数列表。还是一次只传入一个参数,但是参数可以是键值对的形式,这样就传入了2个值
```java
//该静态方法会把字符串的第一个字母变为大写,后续字母变为小写:
public class StringUtils {
public static String capitalize(String s) {
if (s.length() == 0) {
} return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase(); } }return s;
- 该静态方法返回参数列表。还是一次只传入一个参数,但是参数可以是键值对的形式,这样就传入了2个值
```java
//该静态方法会把字符串的第一个字母变为大写,后续字母变为小写:
public class StringUtils {
public static String capitalize(String s) {
if (s.length() == 0) {
@ParameterizedTest @MethodSource void testCapitalize(String input, String result) { assertEquals(result, StringUtils.capitalize(input)); }
static List
**Arguments是junit提供的一个api。具体作用不清楚,不过大概作用是在列表中存入一个键值对**<br />**此时List类型也为Arguments。创建一个Arguments键值对对象是**`**Arguments.arguments(?,?)**`
---
- @CsvSource简单很多。
```java
@ParameterizedTest
@CsvSource({ "abc, Abc", "APPLE, Apple", "gooD, Good" })
void testCapitalize(String input, String result) {
assertEquals(result, StringUtils.capitalize(input));
}
- 但是如果要测试成百上千的输入数据,就不适合把数据直接写代码里,应该写一个独立的csv文件存放数据,并且用
@CsvFileSource
标注csv文件路径(此时就不需要@CsvSource注解了) ```java @ParameterizedTest @CsvFileSource(resources = { “/test-capitalize.csv” }) void testCapitalizeUsingCsvFile(String input, String result) { assertEquals(result, StringUtils.capitalize(input)); }
csv文件:
apple, Apple
HELLO, Hello
JUnit, Junit
reSource, Resource
``
JUnit只在classpath中查找指定的CSV文件,因此,
test-capitalize.csv这个文件要放到
test`目录下,/即表示test目录