当前位置: 首页 > 工具软件 > truth > 使用案例 >

Android 单元测试Truth的使用

田马鲁
2023-12-01

概述

2. Maven依赖

3.简介

4.入门

5.标准的Java断言

5.1。对象断言

5.2。整数,浮点和双断言

5.3。大十进制断言

5.4。布尔断言

5.5。字符串断言

5.6。数组断言

5.7。可比断言

5.8。迭代断言

5.9。Map断言

5.10。异常断言

5.11。类断言

6. Java 8断言

6.1。可选断言

6.2。流断言

7.Guava断言

7.1。可选断言

7.2。多Map断言

7.3。多集断言

7.4。表断言

8.自定义失败消息和标签

9.扩展

10.结论


1.概述

Truth是一种流畅,灵活的开源测试框架,旨在使测试断言和失败消息更具可读性。

在本文中,我们将探索Truth框架的关键功能,并通过示例来展示其功能。

2. Maven依赖

首先,我们需要将添加TruthTruth-java8扩展到我们的pom.xml:

1

2

3

4

5

6

7

8

9

10

11

<dependency>

    <groupId>com.google.truth</groupId>

    <artifactId>truth</artifactId>

    <version>0.32</version>

</dependency>

<dependency>

    <groupId>com.google.truth.extensions</groupId>

    <artifactId>truth-java8-extension</artifactId>

    <version>0.32</version>

    <scope>test</scope>

</dependency>

 

 

或者项目工程下build.gradle 

dependencies {
    androidTestImplementation 'com.google.truth:truth:1.0'
}
  

 

您可以在Maven Central上找到Truth和Truth-java8-extension的最新版本。

3.简介

Truth允许我们为各种类编写可读的断言和失败消息:

  • 标准Java –基元,数组,字符串,对象,集合,可抛出对象,类等。

  • Java 8 – 可选和Stream实例

  • 番石榴 – 可选,Multimap,Multiset和Table对象

  • 自定义类型 –通过扩展Subject类,我们将在后面看到

通过Truth和Truth8类,该库提供了实用的方法,可用于编写对主题(即被测试的值或对象)起作用的断言。

一旦知道了该主题,真相就可以在编译时推断该主题已知的命题。这使它可以返回围绕我们的值的包装器,这些包装器声明特定于该特定主题的命题方法。

例如,在对列表进行断言时,Truth返回IterableSubject实例,该实例定义诸如contains()和containsAnyOf()等方法。在Map上声明时,它返回一个MapSubject,该方法声明诸如containsEntry()和containsKey()之类的方法。

4.入门

要开始编写断言,让我们首先导入Truth的入口点:

1

2

import static com.google.common.truth.Truth.*;

import static com.google.common.truth.Truth8.*;

 

现在,让我们编写一个简单的类,在下面的一些示例中将使用它们:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class User {

    private String name = "John Doe";

    private List<String> emails

      = Arrays.asList("contact@baeldung.com", "staff@baeldung.com");

 

    public boolean equals(Object obj) {

        if (obj == null || getClass() != obj.getClass()) {

            return false;

        }

 

        User other = (User) obj;

        return Objects.equals(this.name, other.name);

    }

 

    // standard constructors, getters and setters

}

 

注意自定义的equals()方法,在该方法中,我们声明两个User对象(如果它们的名称相同)是相等的。

5.标准的Java断言

在本节中,我们将看到有关如何为标准Java类型编写测试断言的详细示例。

5.1。对象断言

真相提供了主题包装器,用于对对象执行断言。Subject也是库中所有其他包装器的父级,并声明用于确定Object(在我们的情况下为User)是否等于另一个对象的方法:

1

2

3

4

5

6

7

@Test

public void whenComparingUsers_thenEqual() {

    User aUser = new User("John Doe");

    User anotherUser = new User("John Doe");

 

    assertThat(aUser).isEqualTo(anotherUser);

}

 

或等于列表中的给定对象:

1

2

3

4

5

6

@Test

public void whenComparingUser_thenInList() {

    User aUser = new User();

 

    assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null));

}

 

或如果不是:

1

2

3

4

5

6

@Test

public void whenComparingUser_thenNotInList() {

    // ...

 

    assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three"));

}

 

是否为null:

1

2

3

4

5

6

7

8

9

10

11

12

13

@Test

public void whenComparingUser_thenIsNull() {

    User aUser = null;

 

    assertThat(aUser).isNull();

}

 

@Test

public void whenComparingUser_thenNotNull() {

    User aUser = new User();

 

    assertThat(aUser).isNotNull();

}

 

或者它是特定类的实例:

1

2

3

4

5

6

@Test

public void whenComparingUser_thenInstanceOf() {

    // ...

 

    assertThat(aUser).isInstanceOf(User.class);

}

 

Subject类中还有其他断言方法。要发现所有内容,请参阅主题文档

在以下各节中,我们将重点介绍 Truth支持的每种特定类型的最相关方法。但是,请记住,Subject类中的所有方法也可以应用。

5.2。整数,浮点和双断言

可以比较Integer,Float和Double实例的相等性:

1

2

3

4

5

6

@Test

public void whenComparingInteger_thenEqual() {

    int anInt = 10;

 

    assertThat(anInt).isEqualTo(10);

}

 

如果更大:

1

2

3

4

5

6

@Test

public void whenComparingFloat_thenIsBigger() {

    float aFloat = 10.0f;

 

    assertThat(aFloat).isGreaterThan(1.0f);

}

 

或更小:

1

2

3

4

5

6

@Test

public void whenComparingDouble_thenIsSmaller() {

    double aDouble = 10.0f;

 

    assertThat(aDouble).isLessThan(20.0);

}

 

此外,还可以检查Float和Double实例以查看它们是否在预期的精度范围内:

1

2

3

4

5

6

7

8

9

10

11

12

13

@Test

public void whenComparingDouble_thenWithinPrecision() {

    double aDouble = 22.18;

 

    assertThat(aDouble).isWithin(2).of(23d);

}

 

@Test

public void whenComparingFloat_thenNotWithinPrecision() {

    float aFloat = 23.04f;

 

    assertThat(aFloat).isNotWithin(1.3f).of(100f);

}

 

5.3。大十进制断言

除了常见的断言之外,还可以忽略这种类型的规模来进行比较:

1

2

3

4

5

6

@Test

public void whenComparingBigDecimal_thenEqualIgnoringScale() {

    BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);

 

    assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0));

}

 

5.4。布尔断言

仅提供了两种相关方法,isTrue()和isFalse():

1

2

3

4

5

6

@Test

public void whenCheckingBoolean_thenTrue() {

    boolean aBoolean = true;

 

    assertThat(aBoolean).isTrue();

}

 

5.5。字符串断言

我们可以测试字符串是否以特定文本开头:

1

2

3

4

5

6

@Test

public void whenCheckingString_thenStartsWith() {

    String aString = "This is a string";

 

    assertThat(aString).startsWith("This");

}

 

另外,我们可以检查字符串是否包含给定的String,是否以期望值结尾或是否为空。这些和其他方法的测试用例在源代码中可用。

5.6。数组断言

我们可以检查Array,看它们是否等于其他数组:

1

2

3

4

5

6

7

@Test

public void whenComparingArrays_thenEqual() {

    String[] firstArrayOfStrings = { "one", "two", "three" };

    String[] secondArrayOfStrings = { "one", "two", "three" };

 

    assertThat(firstArrayOfStrings).isEqualTo(secondArrayOfStrings);

}

 

或如果它们为空:

1

2

3

4

5

6

@Test

public void whenCheckingArray_thenEmpty() {

    Object[] anArray = {};

 

    assertThat(anArray).isEmpty();

}

 

5.7。可比断言

除了测试Comparable是否大于或小于另一个实例之外,我们还可以检查它们是否至少为给定值:

1

2

3

4

5

6

@Test

public void whenCheckingComparable_thenAtLeast() {

    Comparable<Integer> aComparable = 5;

 

    assertThat(aComparable).isAtLeast(1);

}

 

此外,我们可以测试它们是否在特定范围内:

1

2

3

4

5

6

@Test

public void whenCheckingComparable_thenInRange() {

    // ...

 

    assertThat(aComparable).isIn(Range.closed(1, 10));

}

 

或在特定列表中:

1

2

3

4

5

6

@Test

public void whenCheckingComparable_thenInList() {

    // ...

 

    assertThat(aComparable).isIn(Arrays.asList(4, 5, 6));

}

 

我们还可以根据类的compareTo()方法测试两个Comparable实例是否等效。

首先,让我们修改User类以实现Comparable接口:

1

2

3

4

5

6

7

public class User implements Comparable<User> {

    // ...

     

    public int compareTo(User o) {

        return this.getName().compareToIgnoreCase(o.getName());

    }

}

 

现在,让我们断言两个具有相同名称的用户是等效的:

1

2

3

4

5

6

7

8

9

10

@Test

public void whenComparingUsers_thenEquivalent() {

    User aUser = new User();

    aUser.setName("John Doe");

 

    User anotherUser = new User();

    anotherUser.setName("john doe");

 

    assertThat(aUser).isEquivalentAccordingToCompareTo(anotherUser);

}

 

5.8。迭代断言

除了声明Iterable实例的大小(无论它是空的还是没有重复的)外,对Iterable的最典型的声明是它包含一些元素:

1

2

3

4

5

6

@Test

public void whenCheckingIterable_thenContains() {

    List<Integer> aList = Arrays.asList(4, 5, 6);

 

    assertThat(aList).contains(5);

}

 

它包含另一个Iterable的任何元素:

1

2

3

4

5

6

@Test

public void whenCheckingIterable_thenContainsAnyInList() {

    List<Integer> aList = Arrays.asList(1, 2, 3);

 

    assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10));

}

 

并且该主题具有相同的元素,并且顺序相同,就像另一个元素一样:

1

2

3

4

5

6

7

8

9

@Test

public void whenCheckingIterable_thenContainsExactElements() {

    List<String> aList = Arrays.asList("10", "20", "30");

    List<String> anotherList = Arrays.asList("10", "20", "30");

 

    assertThat(aList)

      .containsExactlyElementsIn(anotherList)

      .inOrder();

}

 

并使用自定义比较器订购:

1

2

3

4

5

6

7

8

9

@Test

public void givenComparator_whenCheckingIterable_thenOrdered() {

    Comparator<String> aComparator

      = (a, b) -> new Float(a).compareTo(new Float(b));

 

    List<String> aList = Arrays.asList("1", "012", "0020", "100");

 

    assertThat(aList).isOrdered(aComparator);

}

 

5.9。Map断言

除了断言Map实例是否为空或具有特定大小之外,我们可以检查它是否具有特定条目:

1

2

3

4

5

6

7

@Test

public void whenCheckingMap_thenContainsEntry() {

    Map<String, Object> aMap = new HashMap<>();

    aMap.put("one", 1L);

 

    assertThat(aMap).containsEntry("one", 1L);

}

 

如果它具有特定的密钥:

1

2

3

4

5

6

@Test

public void whenCheckingMap_thenContainsKey() {

    // ...

 

    assertThat(map).containsKey("one");

}

 

或者它具有与另一个Map相同的条目:

1

2

3

4

5

6

7

8

9

10

11

@Test

public void whenCheckingMap_thenContainsEntries() {

    Map<String, Object> aMap = new HashMap<>();

    aMap.put("first", 1L);

    aMap.put("second", 2.0);

    aMap.put("third", 3f);

 

    Map<String, Object> anotherMap = new HashMap<>(aMap);

 

    assertThat(aMap).containsExactlyEntriesIn(anotherMap);

}

 

5.10。异常断言

仅为异常对象提供了两种重要的方法。

我们可以编写针对异常原因的断言:

1

2

3

4

5

6

7

8

9

@Test

public void whenCheckingException_thenInstanceOf() {

    Exception anException

      = new IllegalArgumentException(new NumberFormatException());

 

    assertThat(anException)

      .hasCauseThat()

      .isInstanceOf(NumberFormatException.class);

}

 

或其讯息:

1

2

3

4

5

6

7

8

9

@Test

public void whenCheckingException_thenCauseMessageIsKnown() {

    Exception anException

      = new IllegalArgumentException("Bad value");

 

    assertThat(anException)

      .hasMessageThat()

      .startsWith("Bad");

}

 

5.11。类断言

对于类断言,只有一种重要的方法可以用来测试一个类是否可分配给另一个:

1

2

3

4

5

6

@Test

public void whenCheckingClass_thenIsAssignable() {

    Class<Double> aClass = Double.class;

 

    assertThat(aClass).isAssignableTo(Number.class);

}

 

6. Java 8断言

可选和流是Truth支持的仅有的两种Java 8类型。

6.1。可选断言

有三种重要的方法可以验证Optional。

我们可以测试它是否具有特定的值:

1

2

3

4

5

6

@Test

public void whenCheckingJavaOptional_thenHasValue() {

    Optional<Integer> anOptional = Optional.of(1);

 

    assertThat(anOptional).hasValue(1);

}

 

如果存在该值:

1

2

3

4

5

6

@Test

public void whenCheckingJavaOptional_thenPresent() {

    Optional<String> anOptional = Optional.of("Baeldung");

 

    assertThat(anOptional).isPresent();

}

 

或如果不存在该值:

1

2

3

4

5

6

@Test

public void whenCheckingJavaOptional_thenEmpty() {

    Optional anOptional = Optional.empty();

 

    assertThat(anOptional).isEmpty();

}

 

6.2。流断言

Stream的断言与Iterable的断言非常相似。

例如,我们可以测试特定的Stream是否包含相同顺序的Iterable的所有对象:

1

2

3

4

5

6

7

8

@Test

public void whenCheckingStream_thenContainsInOrder() {

    Stream<Integer> anStream = Stream.of(1, 2, 3);

 

    assertThat(anStream)

      .containsAllOf(1, 2, 3)

      .inOrder();

}

 

有关更多示例,请参考Iterable Assertions部分。

7.Guava断言

在本节中,我们将看到Truth中支持的Guava类型的断言示例。

7.1。可选断言

对于Guava Optional也有三种重要的断言方法。该hasValue的()和isPresent()方法的行为完全一样与Java 8 可选。

但不是的isEmpty()断言一个可选的不存在,我们使用isAbsent() :

1

2

3

4

5

6

@Test

public void whenCheckingGuavaOptional_thenIsAbsent() {

    Optional anOptional = Optional.absent();

 

    assertThat(anOptional).isAbsent();

}

 

7.2。多图断言

Multimap和标准Map断言非常相似。

一个显着的区别是,我们可以在Multimap中获取键的多个值,并对这些值进行断言。

这是一个示例,测试“ one”键的值是否具有两个大小:

1

2

3

4

5

6

7

8

9

10

@Test

public void whenCheckingGuavaMultimap_thenExpectedSize() {

    Multimap<String, Object> aMultimap = ArrayListMultimap.create();

    aMultimap.put("one", 1L);

    aMultimap.put("one", 2.0);

 

    assertThat(aMultimap)

      .valuesForKey("one")

      .hasSize(2);

}

 

有关更多示例,请参考“ Map断言”部分。

7.3。多对象断言

多对象对象的断言包括用于Iterable的断言,以及一种用于验证键是否具有特定出现次数的额外方法:

1

2

3

4

5

6

7

@Test

public void whenCheckingGuavaMultiset_thenExpectedCount() {

    TreeMultiset<String> aMultiset = TreeMultiset.create();

    aMultiset.add("baeldung", 10);

 

    assertThat(aMultiset).hasCount("baeldung", 10);

}

 

7.4。表断言

除了检查它的大小或它的空白处,我们还可以检查一个表以验证它是否包含给定行和列的特定映射:

1

2

3

4

5

6

7

@Test

public void whenCheckingGuavaTable_thenContains() {

    Table<String, String, String> aTable = TreeBasedTable.create();

    aTable.put("firstRow", "firstColumn", "baeldung");

 

    assertThat(aTable).contains("firstRow", "firstColumn");

}

 

或者它包含特定的单元格:

1

2

3

4

5

6

@Test

public void whenCheckingGuavaTable_thenContainsCell() {

    Table<String, String, String> aTable = getDummyGuavaTable();

 

    assertThat(aTable).containsCell("firstRow", "firstColumn", "baeldung");

}

 

此外,我们可以检查它是否包含给定的行,列或值。请参阅相关测试用例的源代码。

8.自定义失败消息和标签

当断言失败时,Truth会显示易读的消息,准确指出问题所在。但是,有时有必要向这些消息添加更多信息,以提供有关所发生情况的更多详细信息。

Truth使我们能够自定义那些失败消息:

1

2

3

4

5

6

@Test

public void whenFailingAssertion_thenCustomMessage() {

    assertWithMessage("TEST-985: Secret user subject was NOT null!")

      .that(new User())

      .isNull();

}

 

运行测试后,我们得到以下输出:

1

2

TEST-985: Secret user subject was NOT null!:

  Not true that <com.baeldung.testing.truth.User@ae805d5e> is null

 

同样,我们可以添加一个自定义标签,该标签将在错误消息中的主题之前显示。当对象没有有用的字符串表示形式时,这可能会派上用场:

1

2

3

4

5

6

7

8

@Test

public void whenFailingAssertion_thenMessagePrefix() {

    User aUser = new User();

 

    assertThat(aUser)

      .named("User [%s]", aUser.getName())

      .isNull();

}

 

如果运行测试,我们将看到以下输出:

1

2

Not true that User [John Doe]

  (<com.baeldung.testing.truth.User@ae805d5e>) is null

 

9.扩展

扩展Truth意味着我们可以添加对自定义类型的支持。为此,我们需要创建一个类:

  • 扩展Subject类或其子类之一

  • 定义一个接受两个参数的构造函数– FailureStrategy和我们的自定义类型的实例

  • 声明一个SubjectFactory类型的字段,Truth将使用该字段创建我们自定义主题的实例

  • 实现一个静态的assertThat()方法,该方法接受我们的自定义类型

  • 公开我们的测试断言API

现在我们知道了如何扩展Truth,让我们创建一个添加对User类型的对象的支持的类:

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

public class UserSubject

  extends ComparableSubject<UserSubject, User> {

 

    private UserSubject(

      FailureStrategy failureStrategy, User target) {

        super(failureStrategy, target);

    }

 

    private static final

      SubjectFactory<UserSubject, User> USER_SUBJECT_FACTORY

      = new SubjectFactory<UserSubject, User>() {

 

        public UserSubject getSubject(

          FailureStrategy failureStrategy, User target) {

            return new UserSubject(failureStrategy, target);

        }

    };

 

    public static UserSubject assertThat(User user) {

        return Truth.assertAbout(USER_SUBJECT_FACTORY).that(user);

    }

 

    public void hasName(String name) {

        if (!actual().getName().equals(name)) {

            fail("has name", name);

        }

    }

 

    public void hasNameIgnoringCase(String name) {

        if (!actual().getName().equalsIgnoreCase(name)) {

            fail("has name ignoring case", name);

        }

    }

 

    public IterableSubject emails() {

        return Truth.assertThat(actual().getEmails());

    }

}

 

现在,我们可以静态导入自定义主题的assertThat()方法并编写一些测试:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Test

public void whenCheckingUser_thenHasName() {

    User aUser = new User();

 

    assertThat(aUser).hasName("John Doe");

}

 

@Test

public void whenCheckingUser_thenHasNameIgnoringCase() {

    // ...

 

    assertThat(aUser).hasNameIgnoringCase("john doe");

}

 

@Test

public void givenUser_whenCheckingEmails_thenExpectedSize() {

    // ...

 

    assertThat(aUser)

      .emails()

      .hasSize(2);

}

 

10.结论

在本教程中,我们探索了Truth给我们编写更具可读性的测试和失败消息的可能性。

我们展示了支持Java和番石榴类型,定制的失败消息最流行的断言方法,并扩展Truth定制主题。

与往常一样,可以在Github上找到本文的完整源代码。

 

 类似资料: