JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。远比使用main测试方便

添加JUnit支持

  1. idea安装JUnit Generator插件,设置的其他设置中设置版本JUnit4
  2. 添加Junit依赖

    1. <groupId> junit</groupId>
    2. <artifactId>junit</artifactId>
    3. <version>4.13.1</version>
  3. 选择src下与main同级的test目录,右键点击:**Mark Directory as/Test Resources Root **将test目录转为单元测试目录

  4. 在test的子目录下创建单元测试类

    JUnit基础语法

  • 在测试类下编写一个测试方法,类似main()的作用,所有需要测试的东西都写测试方法里,就跟main一样
  • @Test类可以有很多个

@Test
public voidtest(){....}
除测试类方法名可以自定义,注解,public,void一个都不能省略,否则报错
测试方法的返回值必须为void
可以有多个测试方法
**
在编写单元测试的时候,我们要遵循一定的规范:

  1. 单元测试代码本身必须非常简单,能一下看明白,决不能再为测试代码编写测试;
  2. 每个单元测试应当互相独立,不依赖运行的顺序;
  3. 测试时不但要覆盖常用测试用例,还要特别注意测试边界条件,例如输入为0null,空字符串""等情况。

    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 s;
      
      } return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase(); } }

@ParameterizedTest @MethodSource void testCapitalize(String input, String result) { assertEquals(result, StringUtils.capitalize(input)); }

static List testCapitalize() { return List.of( // arguments: Arguments.arguments(“abc”, “Abc”), // Arguments.arguments(“APPLE”, “Apple”), // Arguments.arguments(“gooD”, “Good”)); }

**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目录