您好,登錄后才能下訂單哦!
這篇“怎么測試驅動ChatGPT編程”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“怎么測試驅動ChatGPT編程”文章吧。
nodeJS用下面的yaml描繪的數據結構得到一個新的數組:
base: steps: 10 batch_size: 1 poly: - template_prompt: template: > a cat, ${ chara } ${ facial_expressions } meta: - chara: # 這里改成了數組 - Abyssinian, - cat_in_boots, facial_expressions: - (smile:1.5), - (smile:1.2), - smile, steps: 20
可能的輸出:
[
{
steps: 20,
prompt: 'a cat,\nAbyssinian,\n(smile:1.5),\n',
batch_size: 1
},
{
steps: 20,
prompt: 'a cat,\nAbyssinian,\n(smile:1.2),\n',
batch_size: 1
},
{
steps: 20,
prompt: 'a cat,\nAbyssinian,\nsmile,\n',
batch_size: 1
},
{
steps: 20,
prompt: 'a cat,\ncat_in_boots,\n(smile:1.5),\n',
batch_size: 1
},
{
steps: 20,
prompt: 'a cat,\ncat_in_boots,\n(smile:1.2),\n',
batch_size: 1
},
{
steps: 20,
prompt: 'a cat,\ncat_in_boots,\nsmile,\n',
batch_size: 1
},
]
要求:
假設上面的yaml轉成json的轉換代碼我已經寫完了
我需要遍歷poly下的所有的頂層元素
遍歷過程中,要處理template_prompt元素的子元素:
從template中讀取作為模版。
讀取meta中的屬性,因為屬性可能每次都不一樣,是不確定的,所以不能硬編碼。
然后基于meta中的屬性,把template作為 string literal 解析,這個解析代碼我已經有了,假設名為render_string_template,可以不實現,留一個函數接口即可。
要遍歷組合meta中的每一個屬性組形成一個數組,
每一個屬性組可能只需要看做一個對象,當且僅當每一個屬性值都為單值
每一個屬性組可能也需要展開,當且僅當任何一個屬性值有多值,比如 facial_expressions 有一個值,chara有兩個值,那么應該生成1*2也就是兩組屬性放入這個數組中,這個數組和template會被傳入render_string_template函數,最后會獲得兩個prompt字符串
將生成的個prompt字符串數組和template_prompt元素之外的其他元素合并成一個對象,要求在同一級別。prompt字符串數組有幾個元素,就會合并成幾個對象,并放入一個新數組中,我們稱之為ploys。
繼續遍歷,直到遍歷完所有頂層元素,所有元素都放入了polys中。polys是一個一維數組。
將ploys中的每一個元素與base中的屬性合成一個新的對象,base的屬性展開與prompt屬性同級,當ploys中的每一個元素的屬性名與base中的屬性名相同時,覆蓋base中的屬性。這些新對象組合出的數組就是我要的數組
果然就得到了預期的結果。
這一個動作,讓我打開了思路,用輸入+輸出框住它生成的邊界還是挺好用的。輸入+輸出框住邊界?這不就是測試嗎?
從我們的體驗來看,確實啊,ChatGPT生成的是有點不穩定。《The End of Programming》說的沒錯,底層確實有點混亂與瘋狂的味道,起碼不太穩定。但這事也就聽起來很嚇人,說實在的,人就比ChatGPT穩定多少呢?我這個人比較粗心大意,我寫代碼的時候也經常腦子一抽,寫出一些事后看自己都想抽自己的腦殘錯誤,所以我自打聽說了TDD,很快就變成了堅定地TDD原教旨主義者,沒有TDD的世界對我們這種人來說本來就是混亂與瘋狂的,要說駕馭軟件開發過程中的混亂與瘋狂,那你是問對人了。
那么回顧一下TDD是什么?下面是一個復雜版
基本上就是,先寫一個測試用例,然后執行,得到期望的失敗,什么是期望的失敗呢,比如說,你寫了一個add函數,接受兩個參數,然后你寫了一個add(1,1),你期望的失敗可能是返回某個值,他不等于2,實際你執行的時候呢,報錯,說add函數不存在,這就不是你期望的失敗。你要調整環境到你期望的失敗,然后開始寫實現,寫完實現再執行,如果測試不通過了,就接著改實現,直到通過。如果通過了,就問自己要不要重構,如果要重構,就改實現,改完的還要通過測試,如果不想重構了,那就寫下一個測試用例。總之就是這么個往復循環的流程。
如果上面那個復雜版讓你覺得很暈呢,可以看這個簡單版:
紅表示測試執行失敗,綠表示測試執行通過,重構就不用解釋了。所以永遠是綠了重構,重構完可能紅可能綠,紅了就讓他變綠。只有變綠后,可以開始下一次循環。
上面這些圖講的是TDD的過程,我個人覺得更核心的是,在實現代碼的這個過程中,你同時生產了一套自動化測試,他可以在你將來不管是加新功能還是修Bug還是重構,都不會陷入按下葫蘆起來瓢的窘境。那么在ChatGPT的時代,這個方法還能用嗎?總感覺哪里需要修改一下,不過沒關系,我們先走起來,邊走邊想。
那么既然是TDD,應該先寫測試,但是我實現都寫完了啊。算了,這也是大多數項目的現狀,那就先補個測試吧。但是補測試是個很煩的事情啊,我靈機一動,要不……讓ChatGPT給我搞?它能行嗎?誰知道呢,試試又不花錢。 于是我就說:
給下面代碼設計一些測試用例:
module.exports = function render_my_yaml_dsl(data){ const vm = require('vm'); - // 定義一個函數用于渲染字符串模板 function render_string_template(originalScriptTemplate, intention) { // console.log(intention); // 省略渲染代碼 const execScriptTemplate = "`" + originalScriptTemplate + "`"; - const script = new vm.Script(execScriptTemplate); const execScript = script.runInNewContext(intention); // console.debug(execScript); return execScript; } - // 定義一個函數用于展開屬性組合 function expand_attributes(attributes) { const result = []; attributes.forEach(obj => { Object.values(obj).forEach(val => { if (Array.isArray(val)) { val.forEach(v => { result.push({ ...obj, [Object.keys(obj).find(key => obj[key] === val)]: v }); }); } else { result.push(obj); } }); }); return result; } - const polys = []; - for (const poly of data.poly) { const { template_prompt, ...other } = poly; const { template, meta } = template_prompt; - const variableGroups = expand_attributes(meta); const prompts = variableGroups.map(variables => render_string_template(template, variables)); - for (const prompt of prompts) { polys.push({ prompt, ...other }); } } - const result = polys.map(poly => ({ ...data.base, ...poly })); return result; }
是的就是這么任性,代碼直接扔給他。然后我得到了下面的回復:
以下是一些可能的測試用例:
當輸入參數 data 為空對象時,函數是否能正常運行并返回一個空數組?
當輸入參數 data.poly 為空數組時,函數是否能正常運行并返回一個空數組?
當輸入參數 data.poly 不包含任何屬性組合時,函數是否能正常運行并返回一個包含原始數據的數組?
當輸入參數 data.poly 包含一個屬性組合時,函數是否能正常運行并返回一個包含根據屬性組合渲染的數據的數組?
當輸入參數 data.poly 包含多個屬性組合時,函數是否能正常運行并返回一個包含根據所有屬性組合渲染的數據的數組?
當模板中的字符串模板包含模板變量時,函數是否能正常運行并渲染模板變量?
當模板中的字符串模板包含 JavaScript 代碼時,函數是否能正常運行并執行 JavaScript 代碼?
當屬性組合中包含數組時,函數是否能正常運行并正確展開屬性組合?
當屬性組合中包含對象時,函數是否能正常運行并正確展開屬性組合?
當輸入參數 data.base 中包含對象時,函數是否能正常運行并將 data.base 和渲染的屬性組合合并為一個對象?
我嘆了口氣,我在期望什么,果然是這么泛泛的片湯話嗎?這玩意還用你告訴我?而且,你這里面還有些不是我想要的啊。唉……心中抱怨了一陣后,我還是不死心,我繼續說:
分別準備一些數據
這個要求仿佛打開了潘多拉的盒子,我得到了下面的輸出:
以下是可能的數據準備:
空對象:
const data = {};
空的 poly 數組:
const data = { poly: [] };
包含一個屬性組合的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${name}: ${price}', meta: [ { name: 'color', value: ['red', 'blue'] }, { name: 'price', value: [10, 20] } ] }, id: 1 } ] };
包含多個屬性組合的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${name}: ${price}', meta: [ { name: 'color', value: ['red', 'blue'] }, { name: 'price', value: [10, 20] } ] }, id: 1 }, { template_prompt: { template: '${name} (${size}): ${price}', meta: [ { name: 'size', value: ['S', 'M', 'L'] }, { name: 'price', value: [30, 40, 50] } ] }, id: 2 } ] };
包含模板變量和 JavaScript 代碼的字符串模板的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${name}: ${price + tax}', meta: [ { name: 'color', value: ['red'] }, { name: 'price', value: [10] }, { name: 'tax', value: [2] } ] }, id: 1 } ] };
包含數組和對象的屬性組合的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${name} (${color.name}): ${price}', meta: [ { color: { name: 'red', code: '#ff0000' }, price: [10] }, { color: { name: 'blue', code: '#0000ff' }, price: [20] } ] }, id: 1 } ] };
包含屬性值為數組,但未包含數組元素的屬性組合的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${name} (${color}): ${price}', meta: [ { color: ['red'], price: [10] }, { color: ['blue'], price: [20] } ] }, id: 1 } ] };
包含模板變量為屬性值的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${props.name}: ${props.price}', meta: [ { props: { name: 'Product B', price: 30 } } ] }, id: 1 } ] };
包含 render_string_template 函數中 console.log 調用的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${name}: ${price}', meta: [ { name: 'color', value: ['red', 'blue'] }, { name: 'price', value: [10, 20] } ] }, id: 1 } ] }; console.log(render_my_yaml_dsl(data));
包含 expand_attributes 函數中未包含數組的屬性組合的數據:
const data = { base: { id: 1, name: 'Product A' }, poly: [ { template_prompt: { template: '${name} (${color}): ${price}', meta: [ { color: 'red', price: [10] }, { color: 'blue', price: [20] } ] }, id: 1 } ] };
這一刻,我仿佛聽到了什么出現裂紋的聲音,是我的飯碗嗎^_^。
我曾經愚蠢的以為,當我們有了ChatGPT這么強大的工具后,我們只需要寫測試就可以了,讓他去寫實現,而我沒有發現這個邏輯里面一個愚蠢的漏洞,一個AI都能寫實現了,為什么他寫不了測試呢?反過來講,有很多客戶問我有沒有自動生成自動化測試的工具,因為他們不想寫測試,現在也一樣尷尬了。好消息是,有,壞消息是,實現他也能寫。
激動了一會,我漸漸冷靜下來了,沒事沒事,這測試沒法直接用啊,對吧,你看這測試難道還能扔給一個框架讓他自動運行嗎,那不是還要寫一個專用框架………………想到這,我意識到……好像……我在第一篇的時候干過這事……然后我懷著忐忑的心,寫下了下面的prompt:
我們的文件夾結構如下所示
. ├── index.js └── test ├── cases │ ├── 1.input.json │ ├── 1.output.json │ ├── 2.input.json │ ├── 2.output.json │ ├── 3.input.json │ ├── 3.output.json │ ├── 4.input.json │ └── 4.output.json └── index.js
index.js 是一個函數,大概如下:
module.exports = function render_my_yaml_dsl(data){ //.... }
test/cases下是所有的測試數據,input和output為一組,比如1.input.json存儲了一個測試的描述和given數據,1.output.json存儲了一個測試的expect數據。兩者在.input或.output之前的字符串完全相等為一組。 input.json數據結構為:
{ "desc": "測試用例描述", "data": { //具體的測試數據 } }
output.json則全部為具體的測試數據 基于這個文件夾結構寫一個測試框架,放在test/index.js里,要求:
可以遍歷test/cases下的文件,分組執行且互相不干擾,也就是其中一組失敗了也會繼續運行下一組,只是會打印出錯誤信息。
可以在上層文件夾執行node test
來執行
讀取文件的fs,要使用const fs = require('fs/promise')引入。
用assert庫進行斷言
每執行一個測試,要打印一個提示,提示的部分內容要來自input.json 中的desc 給出這個代碼
然后我得到了下面的代碼:
可以使用以下代碼實現測試框架:
const assert = require('assert'); const fs = require('fs/promises'); const path = require('path'); const render_my_yaml_dsl = require('../index'); const testDir = path.join(__dirname, 'cases'); async function runTests() { const files = await fs.readdir(testDir); for (const file of files) { if (!file.endsWith('.input.json')) { continue; } const caseName = path.basename(file, '.input.json'); const input = await fs.readFile(path.join(testDir, `${caseName}.input.json`), 'utf8'); const output = await fs.readFile(path.join(testDir, `${caseName}.output.json`), 'utf8'); const { desc, data } = JSON.parse(input); console.log(`Running test "${desc}"`); try { const result = render_my_yaml_dsl(data); assert.deepStrictEqual(result, JSON.parse(output)); console.log(`Test "${desc}" passed`); } catch (error) { console.error(`Test "${desc}" failed`); console.error(error); } } } runTests();
解釋:
首先引入 assert 和 fs 模塊,并且導入需要測試的函數 render_my_yaml_dsl。
定義測試文件夾路徑 testDir。
創建一個異步函數 runTests 來遍歷測試文件夾下的所有測試用例。
使用 fs.readdir 異步讀取測試文件夾下的所有文件,然后遍歷文件數組,只選擇以 .input.json 結尾的文件,跳過其他文件。
通過 path.basename 函數獲取測試用例的名稱,然后使用 fs.readFile 異步讀取該測試用例的輸入和輸出文件,并使用 JSON.parse 解析 JSON 格式的數據。
在控制臺中打印正在運行的測試用例的描述 desc。
使用 try...catch 語句運行測試用例,并使用 assert.deepStrictEqual 函數斷言測試結果是否與期望值相等。如果測試通過,打印成功信息,否則打印錯誤信息。
最后調用 runTests 函數運行測試。
這一刻我終于確信,那確實是飯碗出現裂紋的聲音。(雖然上面的只有輸入,但是后來我讓他給出輸出,他也給出了,反正測試框架都有了,大不了跑一遍就有了所有的輸出了嘛,所以這不是啥大問題。)
有了這個框架之后,我工作流程大概變成了這么個節奏:
告訴他,我要擴展新功能,然后扔給他舊代碼,接著告訴他這里是新新功能需要的輸入,我期望的輸出是什么。邊界是什么,現在給我代碼。
然后執行新加的測試,
如果新測試不通過,就讓他重新生成.
如果新加測試通過了,但是舊的測試廢了,就把就廢了測試配上代碼給他,告訴他代碼有Bug,這是以前的輸入,期望的輸出是什么,你現在的輸出是什么,讓他改代碼。 整個過程就很像TDD的紅-綠循環,雖然重構沒有了,但是紅綠循環還是有的。 而更過分的是,一開始新功能需要的測試用例我都懶得自己寫,我就大概告訴他要搞個什么樣的擴展,給他代碼和舊得測試用例結構,讓他給我寫個新的測試用例。然后就給我寫出來了。(也不總能很完美,但是就是需要改也比以前快了不知道多少,關鍵不用去想那些繁瑣的細節也是提供了一定程度的情緒價值。)
按照我的工作流程畫個人在回路是這樣的:
以上就是關于“怎么測試驅動ChatGPT編程”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。