testCollection()和 testStream()的区别(•̀ᴗ•́)و ̑̑

可以给我一个🆓的大拇哥吗?👍😚

读前扫盲

1. 立即执行 vs 惰性执行

立即执行(Eager Evaluation)
  • 定义: 代码在定义时立即执行,结果会立即计算并存储在内存中。
  • 特点:
    • 操作是立即完成的,结果可以直接使用。
    • 适用于数据量较小或需要立即获取结果的场景。
    • 可能会占用较多内存,尤其是处理大量数据时。
  • 示例:
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    List<Integer> squares = new ArrayList<>();
    for (int num : numbers) {
        squares.add(num * num); // 立即计算并存储结果
    }
    System.out.println(squares); // 输出: [1, 4, 9, 16, 25]
    
    • 在这个例子中,squares 列表会立即被填充计算结果。
惰性执行(Lazy Evaluation)
  • 定义: 代码在定义时不会立即执行,只有在需要结果时才会触发计算。
  • 特点:
    • 操作是延迟执行的,只有在终端操作(如 collect()forEach())时才会触发计算。
    • 适用于处理大量数据或无限数据的场景。
    • 节省内存,因为数据是按需计算的。
  • 示例:
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> squareStream = numbers.stream()
                                          .map(num -> num * num); // 惰性操作,不会立即计算
    squareStream.forEach(System.out::println); // 终端操作,触发计算
    
    • 在这个例子中,map() 操作是惰性的,只有在 forEach() 被调用时才会真正计算平方值。
对比
特性立即执行惰性执行
执行时机代码定义时立即执行代码定义时不执行,终端操作时触发执行
内存占用可能占用较多内存节省内存,数据按需计算
适用场景数据量较小或需要立即获取结果数据量较大或需要延迟计算
示例List.add()Collections.sort()Stream.map()Stream.filter()

2. 命令式操作 vs 声明式操作

命令式操作(Imperative Programming)
  • 定义: 通过明确的步骤和指令来完成任务,关注“如何做”(How to do)。
  • 特点:
    • 代码通常包含循环、条件判断和状态修改。
    • 适合处理复杂的业务逻辑。
    • 代码可读性较低,尤其是嵌套较多时。
  • 示例:
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    List<Integer> evenNumbers = new ArrayList<>();
    for (int num : numbers) {
        if (num % 2 == 0) { // 明确的条件判断
            evenNumbers.add(num); // 明确的状态修改
        }
    }
    System.out.println(evenNumbers); // 输出: [2, 4]
    
    • 在这个例子中,我们明确地编写了循环和条件判断来实现过滤操作。
声明式操作(Declarative Programming)
  • 定义: 通过描述任务的目标来完成任务,关注“做什么”(What to do)。
  • 特点:
    • 代码通常更简洁,逻辑更清晰。
    • 适合处理数据转换和过滤等操作。
    • 通常依赖于高阶函数(如 map()filter()reduce())。
  • 示例:
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    List<Integer> evenNumbers = numbers.stream()
                                       .filter(num -> num % 2 == 0) // 声明式过滤
                                       .collect(Collectors.toList());
    System.out.println(evenNumbers); // 输出: [2, 4]
    
    • 在这个例子中,我们通过 filter() 方法声明了过滤条件,而不需要显式编写循环和条件判断。
对比
特性命令式操作声明式操作
关注点如何做(How to do)做什么(What to do)
代码风格包含循环、条件判断和状态修改简洁,通常使用高阶函数
可读性较低,尤其是嵌套较多时较高,逻辑清晰
适用场景复杂业务逻辑数据转换、过滤、聚合等操作
示例for 循环、if 语句Stream.map()Stream.filter()

3. 结合集合和流的例子

命令式 + 立即执行(集合)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = new ArrayList<>();
for (int num : numbers) {
    squares.add(num * num); // 立即计算并存储结果
}
System.out.println(squares); // 输出: [1, 4, 9, 16, 25]
声明式 + 惰性执行(流)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
                               .map(num -> num * num) // 惰性操作
                               .collect(Collectors.toList()); // 终端操作,触发计算
System.out.println(squares); // 输出: [1, 4, 9, 16, 25]

4. 总结

概念立即执行惰性执行命令式操作声明式操作
核心思想立即计算结果延迟计算,按需触发关注如何做(How to do)关注做什么(What to do)
适用场景数据量较小或需要立即获取结果数据量较大或需要延迟计算复杂业务逻辑数据转换、过滤、聚合等操作
代码风格直接操作数据使用流式 API包含循环、条件判断和状态修改简洁,通常使用高阶函数
示例List.add()Collections.sort()Stream.map()Stream.filter()for 循环、if 语句Stream.map()Stream.filter()

正文开始

辨析两个方法 testCollection()testStream()

二者在功能上非常相似,它们都用于生成一组动态测试用例(DynamicTest)。然而,集合(Collection)和流(Stream)在 Java 中有本质的区别,尽管在这个特定的例子中它们的效果看起来是一样的。

1. 集合(Collection)和流(Stream)的区别

特性集合(Collection流(Stream
数据结构集合是一个存储数据的容器(如 ListSet 等),数据是实际存储在内存中的。流不是数据结构,而是一个用于处理数据的抽象管道。数据可以是延迟加载的,甚至可以是无限的。
数据操作集合的操作是立即执行的(eager evaluation),例如调用 add()remove() 会立即修改集合。流的操作是惰性执行的(lazy evaluation),只有在终端操作(如 collect()forEach())时才会触发计算。
数据来源集合的数据通常是静态的,需要在创建时明确指定所有元素。流的数据可以是动态生成的,甚至可以是无限的(例如通过 Stream.generate()Stream.iterate())。
并行处理集合本身不支持并行处理,需要手动实现多线程操作。流天然支持并行处理,只需调用 parallel() 方法即可。
功能性集合的操作通常是命令式的(imperative),需要显式编写循环和条件判断。流的操作是声明式的(declarative),可以通过链式调用实现复杂的数据处理逻辑。

2. 在动态测试中的应用

在你的代码中,testCollection()testStream() 都返回一组动态测试用例,但它们的使用场景和灵活性有所不同:

testCollection()
@TestFactory
Collection<DynamicTest> testCollection() {
    return Arrays.asList(
        dynamicTest("Test 1", () -> assertEquals(2, 1 + 1)),
        dynamicTest("Test 2", () -> assertEquals(4, 2 * 2))
    );
}
  • 特点:
    • 使用 Arrays.asList() 创建一个静态的集合(List)。
    • 所有的测试用例在集合创建时就已经确定。
    • 适合测试用例数量固定且已知的场景。
  • 优点:
    • 简单直观,适合初学者。
    • 集合的操作是立即执行的,易于调试。
  • 缺点:
    • 灵活性较低,无法动态生成测试用例。
    • 如果测试用例数量很大,可能会占用较多内存。
testStream()
@TestFactory
Stream<DynamicTest> testStream() {
    return Stream.of(
        dynamicTest("Test 3", () -> assertEquals(6, 3 + 3)),
        dynamicTest("Test 4", () -> assertEquals(8, 4 * 2))
    );
}
  • 特点:
    • 使用 Stream.of() 创建一个流。
    • 测试用例可以通过流的方式动态生成。
    • 适合测试用例数量不确定或需要动态生成的场景。
  • 优点:
    • 灵活性高,可以结合 Stream 的 API(如 map()filter()flatMap() 等)动态生成测试用例。
    • 支持惰性求值,适合处理大量数据或无限数据。
    • 天然支持并行处理,可以提高测试效率。
  • 缺点:
    • 语法相对复杂,需要熟悉 Stream 的 API。
    • 惰性求值可能导致调试困难。

testCollection()testStream() 的效果确实是一样的,因为它们都返回了固定数量的测试用例。然而,它们的灵活性和适用场景不同

  • 如果你只需要生成少量固定的测试用例,使用 Collection 更简单直观。
  • 如果你需要动态生成测试用例(例如从文件、数据库或 API 中读取数据),或者需要处理大量数据,使用 Stream 会更灵活和高效。

4. 更复杂的例子:动态生成测试用例

以下是一个更复杂的例子,展示了 Stream 的灵活性:

@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
    // 假设我们有一组输入和预期输出
    List<Integer> inputs = Arrays.asList(1, 2, 3, 4);
    List<Integer> expectedOutputs = Arrays.asList(1, 4, 9, 16);

    // 使用 Stream 动态生成测试用例
    return inputs.stream()
        .map(input -> dynamicTest(
            "Square of " + input, // 测试名称
            () -> {
                int index = inputs.indexOf(input);
                assertEquals(expectedOutputs.get(index), input * input); // 测试逻辑
            }
        ));
}

在这个例子中:

  • 测试用例是动态生成的,基于 inputsexpectedOutputs 列表。
  • 使用 Streammap() 方法将每个输入映射为一个 DynamicTest
  • 这种方式非常适合处理动态数据或大量数据。

5. 总结

场景使用 Collection使用 Stream
测试用例数量固定适合,简单直观也可以,但略显复杂
测试用例需要动态生成不适合非常适合
处理大量数据不适合,可能占用大量内存适合,支持惰性求值和并行处理
代码复杂度较高

可以给我一个🆓的大拇哥吗?👍😚

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值