使用Prompt工程进行测试驱动开发

TDD 拥护者会告诉你,在软件完全开发之前收集需求时就需要首先编写测试。而魔鬼的辩护人则会持相反的观点。无论你在TDD光谱中的观点是什么,事实证明,(使用ChatGPT)快速工程化对于测试和开发你的代码非常有帮助。这个例子是Go语言。

ChatGPT中文站
https://medium.com/@revanrgh/tdd-first-then-the-code-951a9239ce38

软件开发的本质基本上是将您的意图转换成代码。意图是基于业务需求、约束、外部因素、软件架构师的疯狂行为和妄动(如果有的话)来确定的,可以采用声明式编程(领域特定语言或伪代码)的形式,也可以采用 TDD 和单元测试的形式。

例如,考虑下面的人为规范。

我不会深入探讨测试驱动开发的 TDD 教条。如果您不完全掌握问题领域,TDD 不会拯救您。理解问题领域比解决方案更重要。此外,我把重构最终代码(TDD 的方式)的任务留给读者自己完成。提示:尝试通过学习 Go 的测试来学习 TDD。

You are Go language programmer

Given JSON string,
Repeat
When value is array,
Then rewrite the array starting from right to left or bottom to top,
basically reversing the display of values
Input: "array": ["a", "b", 20, false, "c"]
Output: "array": ["c", false, 20, "b", "a"]
When value is object
Then rewrite the object values in reverse order
Input: "f": {
"g": "grape",
"h": "horse"
}
Output: "f": {
"h": "horse"
"g": "grape"
}
Done
Output:
Generate a Go program that accepts a JSON string in the main program and
display the output as JSON string given the spec above

我尝试了很多提示和迭代,但是ChatGPT无法得到这个特定需求的正确代码。

为了直接讲述故事的要点,我将仅强调ChatGPT在这个实验中失败了的地方。

package main

import (
"encoding/json"
"fmt"
)

func reverseArray(arr []interface{}) {
for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
arr[i], arr[j] = arr[j], arr[i]
}
}

func reverseObject(obj map[string]interface{}) {
keys := make([]string, 0, len(obj))
for key := range obj {
keys = append(keys, key)
}

for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
keys[i], keys[j] = keys[j], keys[i]
}

newObj := make(map[string]interface{})
for _, key := range keys {
newObj[key] = obj[key]
delete(obj, key)
}

for key, value := range newObj {
obj[key] = value
}
}

func reverseJSON(jsonData interface{}) {
switch jsonData := jsonData.(type) {
case []interface{}:
reverseArray(jsonData)
case map[string]interface{}:
reverseObject(jsonData)
for _, value := range jsonData {
reverseJSON(value)
}
}
}

func main() {
jsonStr := `{
"array": ["a", "b", 20, false, "c"],
"f": {
"g": "grape",
"h": "horse"
}
}`

var data interface{}
err := json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
fmt.Println("Error:", err)
return
}

reverseJSON(data)

outputJSON, err := json.MarshalIndent(data, "", " ")
if err != nil {
fmt.Println("Error:", err)
return
}

fmt.Println(string(outputJSON))
}

输出:

{
"array": [
"c",
false,
20,
"b",
"a"
],
"f": {
"g": "grape",
"h": "horse"
}
}

倒序显示数组在这个基本情况下是没有问题的,但如果是一个对象则不行(输出与输入相同)。

"f": {
"g": "grape",
"h": "horse"
}

下一次迭代正确显示了对象,但副作用是数组情况失败了!

package main

import (
"encoding/json"
"fmt"
)

func reverseObject(jsonObj json.RawMessage) json.RawMessage {
var data map[string]json.RawMessage
err := json.Unmarshal(jsonObj, &data)
if err != nil {
panic(err)
}

reversedData := make(map[string]json.RawMessage)

for key, value := range data {
var reversedValue json.RawMessage

if isObject(value) {
reversedValue = reverseObject(value)
} else {
reversedValue = value
}

reversedData[key] = reversedValue
}

reversedJSON, err := json.Marshal(reversedData)
if err != nil {
panic(err)
}

return reversedJSON
}

func isObject(value json.RawMessage) bool {
var obj map[string]json.RawMessage
err := json.Unmarshal(value, &obj)
fmt.Println("isObject, ", err)
return err == nil
}

func main() {
// JSON input
jsonStr := `{
"i": {
"k": {
"m": "monkey",
"l": "lion"
},
"j": "jupiter"
},
"f": {
"h": "horse",
"g": "grape",
"a": [1,2,3]
},
"array": ["a", "b", 20, false, "c"]
}`

// Parse the JSON input
var jsonObj json.RawMessage
err := json.Unmarshal([]byte(jsonStr), &jsonObj)
if err != nil {
panic(err)
}

// Reverse the order of key-value pairs in the JSON object
reversedJSON := reverseObject(jsonObj)

// Print the reversed JSON string
fmt.Println(string(reversedJSON))
}

我添加了fmt.Println用于调试(相当于JavaScript中的console.log)。

isObject,  <nil>
isObject, <nil>
isObject, json: cannot unmarshal string into Go value of type map[string]json.RawMessage
isObject, json: cannot unmarshal string into Go value of type map[string]json.RawMessage
isObject, json: cannot unmarshal string into Go value of type map[string]json.RawMessage
isObject, <nil>
isObject, json: cannot unmarshal string into Go value of type map[string]json.RawMessage
isObject, json: cannot unmarshal string into Go value of type map[string]json.RawMessage
isObject, json: cannot unmarshal array into Go value of type map[string]json.RawMessage
isObject, json: cannot unmarshal array into Go value of type map[string]json.RawMessage
{"array":["a","b",20,false,"c"],"f":{"a":[1,2,3],"g":"grape","h":"horse"},"i":{"j":"jupiter","k":{"l":"lion","m":"monkey"}}}

为了美观打印,

{
"array": ["a", "b", 20, false, "c"],
"f": {
"a": [1, 2, 3],
"g": "grape",
"h": "horse"
},
"i": {
"j": "jupiter",
"k": {
"l": "lion",
"m": "monkey"
}
}
}

在调试了几个小时改编自两个迭代的代码后,我成功地处理了数组和对象的情况,https://go.dev/play/p/sNnS1D66Gte

GitHub代码:https://github.com/ibmendoza/chatgpt-go/blob/main/jsonreverse.go

package main

import (
"encoding/json"
"fmt"
"reflect"
)

func reverseObject(jsonObj json.RawMessage) json.RawMessage {
var data map[string]json.RawMessage
err := json.Unmarshal(jsonObj, &data)
if err != nil {
panic(err)
}

reversedData := make(map[string]json.RawMessage)

for key, value := range data {
var reversedValue json.RawMessage

if isObject(value) {
reversedValue = reverseObject(value)
} else {

if isArray(value) {
//fmt.Println("array,", string(value))

output, err := processArray(string(value))
if err != nil {
fmt.Println("Error:", err)
panic(err)
}

//fmt.Println(output)
reversedValue = output //reverseArray(value)
} else {
reversedValue = value
}
}

reversedData[key] = reversedValue
}

reversedJSON, err := json.Marshal(reversedData)
if err != nil {
//fmt.Println("reverseObject", err)
panic(err)
}

return reversedJSON
}

func reverseArray(arr []interface{}) {
for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
arr[i], arr[j] = arr[j], arr[i]
}
}

func isArray(value json.RawMessage) bool {
var arr []json.RawMessage
err := json.Unmarshal(value, &arr)
//fmt.Println("isArray, ", err)
return err == nil
}

func isObject(value json.RawMessage) bool {
var obj map[string]json.RawMessage
err := json.Unmarshal(value, &obj)
//fmt.Println("isObject, ", err)
return err == nil
}

//***

func processValue(value interface{}) {
fmt.Println("processValue,", value)
switch v := value.(type) {
case []interface{}:
reverseArray(v)
for _, item := range v {
if reflect.TypeOf(item).Kind() == reflect.Map {
processValue(item)
}
}

/* //replace with reverseObject
case map[string]interface{}:
reverseObject(v)
for _, item := range v {
if reflect.TypeOf(item).Kind() == reflect.Map || reflect.TypeOf(item).Kind() == reflect.Slice {
processValue(item)
}
}
}
*/

//case map[string]interface{}:
case map[string]json.RawMessage:
//case json.RawMessage:
//reverseObject(v)
for _, item := range v {
if reflect.TypeOf(item).Kind() == reflect.Map || reflect.TypeOf(item).Kind() == reflect.Slice {
processValue(item)
}
}
}
}

func processArray(jsonStr string) (json.RawMessage, error) {
var data interface{}
err := json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
return nil, err
}

processValue(data)

result, err := json.Marshal(data)
if err != nil {
return nil, err
}
return result, nil
//return string(result), nil
}

func main() {
// JSON input
jsonStr := `{
"i": {
"k": {
"m": "monkey",
"l": "lion"
},
"j": "jupiter"
},
"f": {
"h": "horse",
"g": "grape",
"a": [1,2,3, {"four": 4, "five": 5}]
},
"array": ["a", "b", 20, false, "c", {"six": 6, "seven": 7}]
}`

// Parse the JSON input
var jsonObj json.RawMessage
err := json.Unmarshal([]byte(jsonStr), &jsonObj)
if err != nil {
panic(err)
}

// Reverse the order of key-value pairs in the JSON object
reversedJSON := reverseObject(jsonObj)

// Print the reversed JSON string
fmt.Println(string(reversedJSON))
}

产出:

processValue, [1 2 3 map[five:5 four:4]]
processValue, map[five:5 four:4]
processValue, [a b 20 false c map[seven:7 six:6]]
processValue, map[seven:7 six:6]
{"array":[{"seven":7,"six":6},"c",false,20,"b","a"],"f":{"a":[{"five":5,"four":4},3,2,1],"g":"grape","h":"horse"},"i":{"j":"jupiter","k":{"l":"lion","m":"monkey"}}}

漂亮的打印:

{
"array": [{
"seven": 7,
"six": 6
}, "c", false, 20, "b", "a"],
"f": {
"a": [{
"five": 5,
"four": 4
}, 3, 2, 1],
"g": "grape",
"h": "horse"
},
"i": {
"j": "jupiter",
"k": {
"l": "lion",
"m": "monkey"
}
}
}

故事的要点是,

  • 您可以使用ChatGPT(或其他任何产品/项目)以清晰易懂的方式,通过Given-When-Then的半结构化方法或伪代码指定您的意图。如果您的指示相对严格,ChatGPT足够智能以解析您的意图,并最终正确执行。
  • 伪代码/智能提示具有抽象细节和释放你的样板、低级代码的强大效果。
  • ChatGPT不会取代开发者,因为它的上下文是有限的。人类仍然在上下文,编排和其他高级细节方面占据主导地位,而AI将只是人类的从属。
  • DSL和伪代码尤其适用于基于AI的开发,特别是TDD。

如果您有类似的迅速工程经验,请随时在评论中发表。

2023-10-20 17:03:39 AI中文站翻译自原文