17|如何与LLM结对编程

AI 学习笔记 | 极客时间 | 徐昊 · AI 时代的软件工程学习笔记

🏆 课程原文链接

AI 模版

列出测试场景

功能需求
====
{requirement}

任务
====
请根据上面的功能需求,列出需要测试的场景。描述场景,并给出相关的测试数据。

编写测试代码

请生成 junit junpiter 测试
要求:
每个场景对应一个测试方法
使用 Kotlin 编写
使用 TDD 中的 should style 编写测试

操作演示

我换了一个简单的例子「双色球」,与 GPT4 结对编程。

功能需求
====
双色球
红球: 1-33 选 6 个
蓝球: 1-16 选 1 个
随机生成一组双色球号码

任务
====
请根据上面的功能需求,列出需要测试的场景。描述场景,并给出相关的测试数据。

GPT4

测试场景1: 正确的号码范围生成
描述: 测试号码生成是否都在正确的范围内,即红球号码为1-33,蓝球号码为1-16。
测试数据: 无具体数据,需要检查生成的号码是否满足以上条件。

测试场景2: 红球号码个数
描述: 测试生成的红球是否恰好为6个。
测试数据: 无具体数据,需要检查生成的红球号码个数是否为6。

测试场景3: 蓝球号码个数
描述: 测试生成的蓝球是否恰好为1个。
测试数据: 无具体数据,需要检查生成的蓝球号码个数是否为1。

测试场景4: 号码重复性
描述: 测试生成的红球号码之间是否存在重复。
测试数据: 无具体数据,检查生成的红球号码是否互不相同。

测试场景5: 号码随机性
描述: 测试生成的号码组合每次是否随机,即多次生成的号码应该不同。
测试数据: 执行多次号码生成函数,记录并比较结果。

测试场景6: 异常输入处理
描述: 测试在输入超出指定范围(如红球选号1-34或者0,蓝球选号0或17)的情况下系统的反应。
测试数据: 输入异常范围的号码,检查系统是否能正确拒绝并给出错误提示。

测试场景7: 性能测试
描述: 测试生成一组号码所需的时间是否符合性能要求。
测试数据: 检查生成号码的响应时间是否符合既定标准。

测试场景8: 持久性测试
描述: 长时间运行号码生成功能,检查其稳定性和可靠性。
测试数据: 在设定的长时间运行周期内,周期性地生成号码,并监控功能稳定性。

测试场景9: 兼容性测试
描述: 测试号码生成功能在不同的平台和设备上是否能正常工作。
测试数据: 选择多个平台和设备,分别执行号码生成操作,并检查结果。

测试场景10: 用户界面测试
描述: 测试用户界面是否直观,以及号码生成结果的显示是否清晰易懂。
测试数据: 用户实际操作,检查界面设计是否用户友好。
以上是基于功能需求列出的一些测试场景,通过这些场景的测试,可以确保双色球号码生成器的正确性和稳定性。

「测试场景1: 正确的号码范围生成」按红蓝球分解为更具体的场景
「测试场景6: 异常输入处理」生成红球、篮球的方法均不需要输入参数
「测试场景7: 性能测试」不需要
「测试场景8: 持久性测试」不需要
「测试场景9: 兼容性测试」不需要
「测试场景10: 用户界面测试」不需要

GPT4

了解,我们将「测试场景1: 正确的号码范围生成」进行细化,并移除不需要的场景。

细化测试场景1-红球号码范围:
描述: 确保每个生成的红球号码都在1到33之间。
测试数据: 无具体数据,需要验证生成号码是否都在1到33的范围内。

细化测试场景1-蓝球号码范围:
描述: 确保生成的蓝球号码在1到16之间。
测试数据: 无具体数据,需要验证生成的蓝球号码是否在1到16的范围内。

测试场景2: 红球号码个数
描述: 确保每次生成的红球个数为6个。
测试数据: 生成一组号码,统计红球数量是否为6。

测试场景3: 蓝球号码个数
描述: 确保每次生成的蓝球个数为1个。
测试数据: 生成一组号码,统计蓝球数量是否为1。

测试场景4: 号码重复性
描述: 确保生成的红球号码之间没有重复。
测试数据: 生成一组号码,检查红球号码是否唯一。

测试场景5: 号码随机性
描述: 确保每次生成的号码组合具有随机性。
测试数据: 生成若干组号码,检查结果是否具有随机性,各组之间不重复。

这样我们有了具体针对红球和蓝球号码范围的细化测试场景,并排除了不适用的测试场景。

请生成 junit junpiter 测试
要求:
每个场景对应一个测试方法
使用 Kotlin 编写
使用 TDD 中的 should style 编写测试

GPT4

当然,这里提供的是JUnit Jupiter与Kotlin结合的测试代码示例,使用了应该(should)风格命名,用以反映测试的行为:

 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
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.Collections

internal class DoubleColorBallTest {
    
    @Test
    fun `should generate red balls within range 1 to 33`() {
        val redBalls = generateRedBalls()
        assertTrue(redBalls.all { it in 1..33 })
    }

    @Test
    fun `should generate exactly 6 red balls`() {
        val redBalls = generateRedBalls()
        assertEquals(6, redBalls.size)
    }
    
    @Test
    fun `should generate a blue ball within range 1 to 16`() {
        val blueBall = generateBlueBall()
        assertTrue(blueBall in 1..16)
    }

    @Test
    fun `should generate exactly one blue ball`() {
        val blueBall = generateBlueBall()
        // Assuming generateBlueBall returns a list for consistency, otherwise adjust as needed.
        assertEquals(1, blueBall.size)
    }
    
    @Test
    fun `should ensure no duplicate red balls`() {
        val redBalls = generateRedBalls()
        assertEquals(redBalls.toSet().size, redBalls.size)
    }
    
    @Test
    fun `should produce random outcome each time`() {
        val firstAttempt = Combination(generateRedBalls(), generateBlueBall())
        val secondAttempt = Combination(generateRedBalls(), generateBlueBall())
        
        assertTrue(firstAttempt != secondAttempt)
    }

    // Utility methods to simulate the number generation (these should be replaced by the actual implementation.)
    private fun generateRedBalls(): List<Int> = (1..33).shuffled().take(6)
    private fun generateBlueBall(): List<Int> = (1..16).shuffled().take(1)

    // Data class to represent a combination of red and blue balls (again for demonstration.)
    data class Combination(val redBalls: List<Int>, val blueBalls: List<Int>)
}

请注意这里的 generateRedBalls 和 generateBlueBall 方法需要替换为您具体的号码生成逻辑实现。用于 generateBlueBall 返回值假设也返回为集合,以便与 generateRedBalls 保持一致。如果蓝球只有一个数字而不是集合,那么就需要相应地调整测试代码以反映这一点。

这个代码仅作为示例,实际测试编写时,你需要根据自己的应用逻辑编写对应的测试实现。

@Test
fun `should generate a blue ball within range 1 to 16`() {
    val blueBall = generateBlueBall()
    assertTrue(blueBall in 1..16) // Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.
}

GPT4

如果generateBlueBall()函数返回的是单个蓝球数字,而不是集合,那么在Kotlin中你不需要使用in操作符来检查列表,而应该是直接检查其是否在范围内。此外,错误消息提示你需要指定类型参数T。在这种情况下,T应该是指Int,因为蓝球号码是一个整数。 下面是修正后的测试函数:

1
2
3
4
5
6
7
8
@Test
fun `should generate a blue ball within range 1 to 16`() {
    val blueBall = generateBlueBall()
    assertTrue(blueBall in 1..16) // Now checks a single Int value, not a list.
}

// Assuming that generateBlueBall() returns an Int like this:
private fun generateBlueBall(): Int = (1..16).random()

在这段代码中,generateBlueBall()是假定返回一个Int类型的值。我使用了random()函数,它是Kotlin标准库中随机选择集合中元素的快捷方式。这个方法默认适用于所有提供了整数范围的情况。

这样修正后的测试代码应该可以解决类型推断的问题。再次强调,根据你绝对号码生成功能的实际实现,这里的实用函数需要进行相应的调整。

assertEquals(1, blueBall.size)

val firstAttempt = Combination(generateRedBalls(), generateBlueBall())
val secondAttempt = Combination(generateRedBalls(), generateBlueBall())

都报错了

GPT4

The issues you’re experiencing are likely due to type mismatches between what is being returned by the generateBlueBall function and what the test expects.

  1. For assertEquals(1, blueBall.size), if generateBlueBall returns a single Int value instead of a list, there won’t be a size property to access, because size is a property of collections, not numbers.

  2. For the Combination class to work properly, you must ensure that the generateBlueBall method returns the same type as the blueBalls property within the Combination data class.

Let’s fix these issues assuming that generateBlueBall generates a single integer, and generateRedBalls generates a list of integers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
fun `should generate a blue ball within range 1 to 16`() {
    val blueBall = generateBlueBall()
    assertTrue(blueBall in 1..16) // Checks a single integer value.
}

// For firstAttempt and secondAttempt, since we're assuming generateBlueBall returns a single Int, the combination will be altered as well.
@Test
fun `should produce random outcome each time`() {
    val firstAttempt = Combination(generateRedBalls(), listOf(generateBlueBall()))
    val secondAttempt = Combination(generateRedBalls(), listOf(generateBlueBall()))
    
    assertFalse(firstAttempt.redBalls == secondAttempt.redBalls && 
                 firstAttempt.blueBalls == secondAttempt.blueBalls)
}

// Assuming generateRedBalls returns a List<Int>, and generateBlueBall returns an Int.
private fun generateRedBalls(): List<Int> = (1..33).shuffled().take(6)
private fun generateBlueBall(): Int = (1..16).random()

// Updated the Combination data class to accept a list for both red and blue balls for consistency.
data class Combination(val redBalls: List<Int>, val blueBalls: List<Int>)

Ensure that the Combination class’s properties align with what is being returned from generateRedBalls and generateBlueBall.

We’ve also changed the assertion in the random outcome test to assertFalse for checking that not both the generated red and blue balls are identical, given that it’s theoretically possible for two random generations to produce the same red balls, but the odds are against the blue ball also being the same, given enough iterations. This highlights the probabilistic nature of randomness checks in tests.

@Test
fun `should generate exactly one blue ball`() {
    val blueBall = generateBlueBall()
    // Assuming generateBlueBall returns a list for consistency, otherwise adjust as needed.
    assertEquals(1, blueBall.size) // 报错
}

GPT4

如果你的测试用例 should generate exactly one blue ball 报错,并且 generateBlueBall 函数返回单个蓝球号码即一个 Int 而非集合,那么对于 .size 的调用是不正确的。在 Kotlin 中,.size 是用于集合的属性,比如列表(List),对于单个数值是没有这个属性的。

针对单个 Int 的断言不应该检查 size,而是应该确认返回的是有效的单个蓝球号码。以下是正确的测试方法:

1
2
3
4
5
6
7
8
@Test
fun `should generate exactly one blue ball`() {
    val blueBall = generateBlueBall()
    assertTrue(blueBall in 1..16) // 检查生成的蓝球号码是否在1到16的范围内
}

// 假设 generateBlueBall 函数的实现是返回一个 Int 类型的值。
private fun generateBlueBall(): Int = (1..16).random() // 从1到16中选择一个随机数。

在这段代码中,我们根据 generateBlueBall 函数是否生成一个介于1到16之间的号码来进行断言。这是对 generateBlueBall 函数正确行为的测试。

如果你的 generateBlueBall 函数确实是以列表形式返回一个号码,并且你希望保持这一设计(例如,为了统一的接口或其他目的),那么你需要调整你的函数和测试,使得函数返回一个单元素的列表,并且在测试中正确地调用 .size 来检查列表的大小。这里的示例假设 generateBlueBall 返回一个 Int 类型的值,根据你之前的信息这个假设似乎是合理的。如果不是这种情况,你应该调整代码以符合你 generateBlueBall 函数的实际返回类型。

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

internal class DoubleColorBallTest {

    @Test
    fun `should generate red balls within range 1 to 33`() {
        val redBalls = generateRedBalls()
        assertTrue(redBalls.all { it in 1..33 })
    }

    @Test
    fun `should generate exactly 6 red balls`() {
        val redBalls = generateRedBalls()
        assertEquals(6, redBalls.size)
    }

    @Test
    fun `should generate a blue ball within range 1 to 16`() {
        val blueBall = generateBlueBall()
        assertTrue(blueBall in 1..16) // Checks a single integer value.
    }

    @Test
    fun `should ensure no duplicate red balls`() {
        val redBalls = generateRedBalls()
        assertEquals(redBalls.toSet().size, redBalls.size)
    }

    @Test
    fun `should produce random outcome each time`() {
        val firstAttempt = Combination(generateRedBalls(), listOf(generateBlueBall()))
        val secondAttempt = Combination(generateRedBalls(), listOf(generateBlueBall()))

        assertFalse(firstAttempt.redBalls == secondAttempt.redBalls &&
                firstAttempt.blueBalls == secondAttempt.blueBalls)
    }

    // Utility methods to simulate the number generation (these should be replaced by the actual implementation.)
    private fun generateRedBalls(): List = (1..33).shuffled().take(6)
    // Assuming that generateBlueBall() returns an Int like this:
    private fun generateBlueBall(): Int = (1..16).random()

    // Data class to represent a combination of red and blue balls (again for demonstration.)
    data class Combination(val redBalls: List, val blueBalls: List)
}

这是整理后的测试代码
请按照需求,生成 DoubleColorBall 以及相关类,可以通过这组测试。

GPT4

Based on the provided test cases for the DoubleColorBallTest class, we need to create a class named DoubleColorBall and any other necessary classes such as Combination. Let’s define these classes according to the expected behavior inferred from the tests.

 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
class DoubleColorBall {

    /**
     * This method generates a list of 6 unique red ball numbers ranging from 1 to 33.
     * @return A List<Int> containing 6 unique numbers.
     */
    fun generateRedBalls(): List<Int> = (1..33).shuffled().take(6)

    /**
     * This method generates a blue ball number ranging from 1 to 16.
     * @return An Int representing the blue ball number.
     */
    fun generateBlueBall(): Int = (1..16).random()

    /**
     * Combines generated red balls and a blue ball into a valid Double Color Ball combination.
     * @return A Combination object representing a valid Double Color Ball combination.
     */
    fun generateCombination(): Combination {
        return Combination(generateRedBalls(), listOf(generateBlueBall()))
    }
}

/**
 * A data class representing a combination of red and blue balls, where the red balls are a list of 6 unique numbers
 * and the blue balls is a list with a single number (though it's a bit unusual to represent a single number as a list).
 * @param redBalls A List<Int> representing the red balls.
 * @param blueBalls A List<Int> representing the blue balls.
 */
data class Combination(val redBalls: List<Int>, val blueBalls: List<Int>)

With this implementation, the DoubleColorBall class should be able to produce random red and blue balls according to the requirements of the provided tests.

This implementation assumes:

  • generateRedBalls() produces a list of 6 unique integers within the range of 1 to 33.
  • generateBlueBall() produces a single integer within the range of 1 to 16.
  • generateCombination() combines the red and blue balls into an instance of the Combination data class.

Please note that I’ve opted to represent the blue ball in the Combination class as a list to keep the usage consistent with the test cases. However, it is slightly odd to represent a single blue ball number as a list. If the real-world usage does not require this, it might be more appropriate to change the blueBalls property to a single Int to better represent the intended usage. This would also necessitate changing the test cases accordingly.

使用 DoubleColorBall 替换 DoubleColorBallTest 相关代码

GPT4

Absolutely, the DoubleColorBallTest class can be updated to use a DoubleColorBall object for generating the numbers. Here is how the test class might look with these adjustments:

 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
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

internal class DoubleColorBallTest {

    // Instantiate a DoubleColorBall object to use its methods for generating numbers
    private val doubleColorBall = DoubleColorBall()

    @Test
    fun `should generate red balls within range 1 to 33`() {
        val redBalls = doubleColorBall.generateRedBalls()
        assertTrue(redBalls.all { it in 1..33 })
    }

    @Test
    fun `should generate exactly 6 red balls`() {
        val redBalls = doubleColorBall.generateRedBalls()
        assertEquals(6, redBalls.size)
    }

    @Test
    fun `should generate a blue ball within range 1 to 16`() {
        val blueBall = doubleColorBall.generateBlueBall()
        assertTrue(blueBall in 1..16)
    }

    @Test
    fun `should ensure no duplicate red balls`() {
        val redBalls = doubleColorBall.generateRedBalls()
        assertEquals(redBalls.toSet().size, redBalls.size)
    }

    @Test
    fun `should produce random outcome each time`() {
        val firstAttempt = doubleColorBall.generateCombination()
        val secondAttempt = doubleColorBall.generateCombination()

        assertFalse(firstAttempt == secondAttempt)
    }
}

class DoubleColorBall {
    fun generateRedBalls(): List<Int> = (1..33).shuffled().take(6)
    fun generateBlueBall(): Int = (1..16).random()
    fun generateCombination(): Combination {
        // Adjust the generateCombination method to match the Combination class definition
        return Combination(generateRedBalls(), listOf(generateBlueBall()))
    }
}

data class Combination(val redBalls: List<Int>, val blueBalls: List<Int>)

I’ve made a few changes compared to your initial example:

  1. A DoubleColorBall object is instantiated at the start of your DoubleColorBallTest class.
  2. I changed the utility methods in the test class to use the generateRedBalls(), generateBlueBall(), and generateCombination() methods from DoubleColorBall.
  3. The generateCombination() method creates a new Combination instance by combining the lists of red and blue balls produced by the other functions.

Please ensure the actual implementation of DoubleColorBall and Combination matches what is expected in the test suite.

聊天结束

手动修改

AI 创建 Combination 是为了测试号码随机性,删除了奇怪的代码,最后手动修改代码如下:

DoubleColorBall

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class DoubleColorBall {

    /**
     * This method generates a list of 6 unique red ball numbers ranging from 1 to 33.
     * @return A List<Int> containing 6 unique numbers.
     */
    fun generateRedBalls(): List<Int> = (1..33).shuffled().take(6)

    /**
     * This method generates a blue ball number ranging from 1 to 16.
     * @return An Int representing the blue ball number.
     */
    fun generateBlueBall(): Int = (1..16).random()
}

DoubleColorBallTest

 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
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

internal class DoubleColorBallTest {

    private val doubleColorBall = DoubleColorBall()

    @Test
    fun `should generate red balls within range 1 to 33`() {
        val redBalls = doubleColorBall.generateRedBalls()
        assertTrue(redBalls.all { it in 1..33 })
    }

    @Test
    fun `should generate exactly 6 red balls`() {
        val redBalls = doubleColorBall.generateRedBalls()
        assertEquals(6, redBalls.size)
    }

    @Test
    fun `should generate a blue ball within range 1 to 16`() {
        val blueBall = doubleColorBall.generateBlueBall()
        assertTrue(blueBall in 1..16)
    }

    @Test
    fun `should ensure no duplicate red balls`() {
        val redBalls = doubleColorBall.generateRedBalls()
        assertEquals(redBalls.toSet().size, redBalls.size)
    }

}

总结

需要认真检查 AI 列出的测试场景,不然会出现奇怪的代码!

comments powered by Disqus