• 使用AssertJ让单元测试和TDD更加简单


    文章目录

    使用AssertJ让单元测试和TDD更加简单

    前言

    在编写单元测试时,一个重要的工作就是编写断言(Assertion),而JUnit自带的断言机制和Hamcrest的assertThat都不那么好用。

    利用AssertJ,可以让单元测试更加简单,让TDD过程更加顺畅。

    AssertJ的优点:

    • 通用的assertThat “流式断言”,让编写断言更加简单快捷。
    • API丰富,对各种类型的断言都非常友好。

    环境

    本文的测试环境:

    • JUnit4
    • assertj-core
    • Maven
    • Java8

    添加assertJ的Maven依赖:

    
      org.assertj
      assertj-core
      3.17.0
      test
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在测试类中引入AssertJ:

    import static org.assertj.core.api.Assertions.*;
    
    • 1

    断言类型

    常用的断言类型

    常用的断言类型包括:

    • isSameAs: 同一个对象。
    • isEuqalsTo: 值相等。
    • isCloseTo: 值相近。
    • isTrue:为真,isFalse 为假。
    • containsExactly:包含全部元素,顺序也保持一致。
    • containsOnly:包含全部元素,顺序不需要保持一致。
    • contains:包含给定的元素。
    • hasSize:长度或大小或元素个数为N个。
    • isEmpty: 是否为空。
    • isNull:是否为null。
    • isInstanceOf:是否为指定类型。

    断言的辅助方法

    断言的辅助方法:

    • extracting: 提取,根据类名或方法引用或字段名反射调用,或根据lambda表达式调用。

    • mathes: 匹配,根据lambda表达式调用。

    • atIndex: 获取指定位置/索引的元素。

    • offset: 偏移量。

    • withPercentage:百分比。

    • tuple:将一组属性的值包装成元组。

    • entry:将键值对({K,V})包装成Map.Entry。

    AssertJ例子

    官网:

    基本类型

    参见:

    说明:

    • 对可以精确匹配的值(比如字符串、整数),用isEqualTo()来比较,对不能精确匹配的值(比如浮点数),用isCloseTo()比较。

    • 对布尔值,用isTrue()isFalse()来比较。

      @Test
      public void test_value_equals() {
      String hello = “hello”.toUpperCase();
      assertThat(hello).isEqualTo(“HELLO”);

      int secondsOfDay = 24 * 60 * 60;
      assertThat(secondsOfDay).isEqualTo(86400);
      }

      @Test
      public void test_value_close() {
      double result = 0.1 + 0.1 + 0.1; // 0.30000000000000004
      assertThat(result).isCloseTo(0.3, offset(0.0001)); // 误差值
      assertThat(result).isCloseTo(0.3, withPercentage(0.01)); // 误差百分比
      }

      @Test
      public void test_boolean() {
      boolean flag = “Kubernetes”.length() > 8;
      assertThat(flag).isTrue();

      boolean flag2 = “Docker”.length() > 8;
      assertThat(flag2).isFalse();
      }

    单个对象

    参见:

    说明:

    • 判断是否为同一个对象用isSameAs()

    • 如果重写了euqals和hashcode方法,也可以用isEqualTo来判断对象是否相同。

    • 如果只是判断对象的值是否相等,则可以用extracting提取后再判断,或用matches来用lambda表达式判断。

    • 判断是否为null用isNull()isNotNull()

      @Test
      public void test_object_null_or_not_null() {
      Person p1 = new Person(“William”, 34);
      assertThat(p1).isNotNull();

      Person p2 = null;
      assertThat(p2).isNull();
      
      • 1
      • 2

      }

      @Test
      public void test_object_same_as_other() {
      Person p1 = new Person(“William”, 34);
      Person p2 = p1;
      assertThat(p1).isSameAs(p2);

      Person p3 = new Person("John", 35);
      assertThat(p1).isNotSameAs(p3);
      
      • 1
      • 2

      }

      @Test
      public void test_object_equals() {
      Person p1 = new Person(“William”, 34);
      Person p2 = new Person(“William”, 34);

      assertThat(p1).isNotSameAs(p2);
      assertThat(p1).isNotEqualTo(p2); // 如果用isEqualTo判断,则必须要重写equals方法
      
      // extracting method reference
      assertThat(p1).extracting(Person::getName, Person::getAge).containsExactly("William", 34);
      assertThat(p1).extracting(Person::getName, Person::getAge).containsExactly(p2.getName(), p2.getAge());
      
      // extracting field
      assertThat(p1).extracting("name", "age").containsExactly("William", 34);
      assertThat(p1).extracting("name", "age").containsExactly(p2.getName(), p2.getAge());
      
      // matches
      assertThat(p1).matches(x -> x.getName().equals("William") && x.getAge() == 34);
      assertThat(p1).matches(x -> x.getName().equals(p2.getName()) && x.getAge() == p2.getAge());
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      }

    数组

    参见:

    说明:

    • isNull来判断数组是否为null。

    • isEmpty来判断数组是否为空(不包含任何元素)。

    • hasSize来判断数组的元素个数。

    • contains 判断数组中包含指定元素;用containsOnly判断数组中包含全部元素,但是顺序可以不一致;用cotainsExactly判断数组中包含全部元素且顺序需要一致。

    • 如果数组中的元素为对象,则需要通过extracting提取出对象的属性值,再来判断;如果提取出对象的多个属性值时,可以用tuple将多个属性值包装成元组

    • atIndex来获取指定位置/索引的元素

      @Test
      public void test_array_null_or_empty() {
      String[] nullNames = null;
      assertThat(nullNames).isNull();

      String[] emptyNames = {};
      assertThat(emptyNames).isEmpty();
      assertThat(emptyNames).hasSize(0);
      
      • 1
      • 2
      • 3

      }

      @Test
      public void test_array_contains() {
      String[] names = {“Python”, “Golang”, “Docker”, “Java”};

      assertThat(names).contains("Docker");
      assertThat(names).doesNotContain("Haddop");
      
      assertThat(names).containsExactly("Python", "Golang", "Docker", "Java"); // 完全匹配,且顺序也一致
      assertThat(names).contains("Java", "Docker", "Golang", "Python"); // 完全匹配,顺序可以不一致
      
      assertThat(names).contains("Docker", atIndex(2)); // names[2]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      }

      @Test
      public void test_array_object_contains() {
      Person[] names = {new Person(“William”, 34),
      new Person(“John”, 36),
      new Person(“Tommy”, 28),
      new Person(“Lily”, 32)};

      assertThat(names).extracting(Person::getName)
        .containsExactly("William", "John", "Tommy", "Lily");
      
      assertThat(names).extracting("name", "age")
        .containsExactly(tuple("William", 34),
                         tuple("John", 36),
                         tuple("Tommy", 28),
                         tuple("Lily", 32));
      
      assertThat(names).extracting(x -> x.getName(), x -> x.getAge())
        .containsExactly(tuple("William", 34),
                         tuple("John", 36),
                         tuple("Tommy", 28),
                         tuple("Lily", 32));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      }

    集合

    List

    参见:

    List的断言与数组的断言类似。

    @Test
    public void test_list_contains() {
        List names = Arrays.asList(new Person("William", 34),
                                           new Person("John", 36),
                                           new Person("Tommy", 28),
                                           new Person("Lily", 32));
    
        assertThat(names).extracting(Person::getName)
          .containsExactly("William", "John", "Tommy", "Lily");
    
        assertThat(names).extracting("name", "age")
          .containsExactly(tuple("William", 34),
                           tuple("John", 36),
                           tuple("Tommy", 28),
                           tuple("Lily", 32));
    
        assertThat(names).extracting(x -> x.getName(), x -> x.getAge())
          .containsExactly(tuple("William", 34),
                           tuple("John", 36),
                           tuple("Tommy", 28),
                           tuple("Lily", 32));
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    Map

    参见:

    说明:

    • 可以对key进行断言:

      • containsKeys:包含指定key。
      • containsOnlyKeys:包含全部key,对顺序无要求。
    • 可以对value进行断言:

      • containsValues:包含指定value。
    • 可以对entry进行断言:

      • contains:包含指定entry。
      • containsOnly:包含全部entry,对顺序无要求。
    • 注意,HashMap中的entry无序,而TreeMap中的entry有序。因此TreeMap可用containsExactly判断是否包含全部Entry,且顺序也保持一致。

      @Test
      public void test_hash_map_contains() {
      Person william = new Person(“William”, 34);
      Person john = new Person(“John”, 36);
      Person tommy = new Person(“Tommy”, 28);
      Person lily = new Person(“Lily”, 32);
      Person jimmy = new Person(“Jimmy”, 38);

      Map map = new HashMap<>();
      map.put("A1001", william);
      map.put("A1002", john);
      map.put("A1003", tommy);
      map.put("A1004", lily);
      
      Map map2 = new HashMap<>();
      map2.put("A1001", william);
      map2.put("A1002", john);
      map2.put("A1003", tommy);
      map2.put("A1004", lily);
      
      // contains keys
      assertThat(map).containsKeys("A1003");
      assertThat(map).containsOnlyKeys("A1001", "A1002", "A1003", "A1004"); // 需要包含全部的key
      assertThat(map).doesNotContainKeys("B1001");
      
      // contains values
      assertThat(map).containsValues(tommy);
      assertThat(map).doesNotContainValue(jimmy);
      
      // contains entries
      assertThat(map).containsEntry("A1003", tommy);
      assertThat(map).containsAllEntriesOf(map2);
      assertThat(map).contains(entry("A1003", tommy));
      // 需要包含全部的entry
      assertThat(map).containsOnly(entry("A1001", william),
                                   entry("A1002", john),
                                   entry("A1003", tommy),
                                   entry("A1004", lily));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

      }

      @Test
      public void test_tree_map_contains() {
      Person william = new Person(“William”, 34);
      Person john = new Person(“John”, 36);
      Person tommy = new Person(“Tommy”, 28);
      Person lily = new Person(“Lily”, 32);

      Map map = new TreeMap<>();
      map.put("A1001", william);
      map.put("A1002", john);
      map.put("A1003", tommy);
      map.put("A1004", lily);
      
      // 需要包含全部的entry,且顺序也一致
      assertThat(map).containsExactly(entry("A1001", william),
                                      entry("A1002", john),
                                      entry("A1003", tommy),
                                      entry("A1004", lily));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      }

      @Test
      public void test_map_extracting() {
      Person william = new Person(“William”, 34);
      Person john = new Person(“John”, 36);
      Person tommy = new Person(“Tommy”, 28);
      Person lily = new Person(“Lily”, 32);

      Map map = new TreeMap<>();
      map.put("A1001", william);
      map.put("A1002", john);
      map.put("A1003", tommy);
      map.put("A1004", lily);
      
      assertThat(map).extracting("A1001").isEqualTo(william);
      assertThat(map).extracting("A1001")
        .extracting("name", "age")
        .containsExactly("William", 34);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      }

    Set

    Set的断言与List的断言类似。

    异常

    参见:

    说明:

    • try catch 来捕捉可能抛出的异常。

    • isInstanceOf来判断异常类型。

    • hasMessageContaining来模糊匹配异常信息,用hasMessage来精确匹配异常信息。

    • hasCauseInstanceOf 来判断异常原因(root cause)的类型。

    • 对checked exception和runtime exception的断言是一样的。

      public class ExceptionExampleTest {

      public String readFirstLine(String fileName) throws IncorrectFileNameException {
          try (Scanner file = new Scanner(new File(fileName))) {
              if (file.hasNextLine()) {
                  return file.nextLine();
              }
          } catch (FileNotFoundException e) {
             throw new IncorrectFileNameException("Incorrect file name: " + fileName, e);
          }
          return "";
      }
      
      /**
       * java custom exception:
       * https://www.baeldung.com/java-new-custom-exception
        */
      
      public class IncorrectFileNameException extends Exception {
          public IncorrectFileNameException(String message) {
              super(message);
          }
      
          public IncorrectFileNameException(String message, Throwable cause) {
              super(message, cause);
          }
      }
      
      
      @Test
      public void test_exception_ArithmeticException() {
          try {
              double result = 1 / 0;
          } catch(Exception e) {
              // check exception type
              assertThat(e).isInstanceOf(ArithmeticException.class);
      
              // check exception message
              assertThat(e).hasMessageContaining("/ by zero");
          }
      }
      
      @Test
      public void test_exception_ArrayIndexOutOfBoundsException() {
          try {
              String[] names = {"William", "John", "Tommy", "Lily"};
              String name = names[4];
          } catch (Exception e) {
              // check exception type
              assertThat(e).isInstanceOf(ArrayIndexOutOfBoundsException.class);
      
              // check exception message
              assertThat(e).hasMessage("4");
          }
      }
      
      @Test
      public void test_exception_IllegalStateException() {
          try {
              List names = Arrays.asList("William", "John", "Tommy", "Lily");
              Iterator iter = names.iterator();
              iter.remove();
          } catch (Exception e) {
              // check exception type
              assertThat(e).isInstanceOf(IllegalStateException.class);
          }
      }
      
      
      @Test
      public void test_custom_exception() {
          try {
              String line = readFirstLine("./unknown.txt");
          } catch (Exception e) {
              // check exception type
              assertThat(e).isInstanceOf(IncorrectFileNameException.class);
      
              // check exception message
              assertThat(e).hasMessageContaining("Incorrect file name");
      
              // check cause type
              assertThat(e).hasCauseInstanceOf(FileNotFoundException.class);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82

      }

    Optional

    参见:

    说明:

    • isPresent来判断Optional是否为空。

    • 如果Optional中的值是基本类型,用hasValue判断。

    • 如果Optional中的值为对象,用hasValueSatisfying判断。

      public class OptionalExampleTest {

      @Test
      public void test_optional_null_or_empty() {
          Optional op1 = Optional.ofNullable(null);
      
          assertThat(op1).isNotPresent();
          assertThat(op1).isEmpty();
      
          Optional op2 = Optional.empty();
          assertThat(op2).isNotPresent();
          assertThat(op2).isEmpty();
      }
      
      @Test
      public void test_optional_basic_type() {
          Optional op1 = Optional.ofNullable("hello");
          assertThat(op1).isPresent().hasValue("hello");
      
          Optional op2 = Optional.ofNullable(365);
          assertThat(op2).isPresent().hasValue(365);
      }
      
      @Test
      public void test_optional_object() {
          Optional p1 = Optional.ofNullable(new Person("William", 34));
      
          assertThat(p1).isPresent()
                  .hasValueSatisfying(x -> {
                      assertThat(x).extracting(Person::getName, Person::getAge)
                              .containsExactly("William", 34);
                  });
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31

      }

  • 相关阅读:
    MIT 6.NULL The Missing Semester of Your CS Education(1)
    java:监听器Listener
    Spring Boot企业级开发教程课后习题——第1章Spring Boot开发入门
    python获取文件夹下所有图片目录
    防火墙基础之路由器与防火墙单臂路由和DHCP接口地址池的配置
    基于SSM的航班订票管理系统的设计与实现
    python---协程与任务详解
    MyBatis——【第三章】管理关系映射及spring集成
    检查异常处理方式:1,抛出;2,捕获
    SpringCloud使用Nginx代理、Gateway网关以后如何获取用户的真实ip
  • 原文地址:https://blog.csdn.net/m0_67401761/article/details/126553636