第一步: 拿到需求文檔、UI交互圖(原型圖)、數據庫表設計文檔、接口文檔
1問:為什么要拿到這些文檔資料呢?
1答:
①.《需求文檔》,明確定義了:各個表單字段的限制條件;相關場景邏輯校驗;
②.《UI交互圖》,明確定義了:各單頁面需展示的數據;頁面之間的交互;
③.《數據表設計文檔》,結合UI圖和需求文檔,明確定義了:表字段規則、表N多N關系(一對一、一對多、多對多);
④.《接口文檔》,結合需求文檔和UI和數據表,明確定義了:接口名,各個入參值,各個返回值,和其他相關信息;
2細節:
①.大多數公司,針對這四類文檔資料都整理的不規范,或者沒能及時更新;
②.這會導致接口測試用例的編寫沒有一個絕對可靠的需求來源;
③.因為,接口測試用例本質是針對各個表字段的校驗;
④.所以需求文檔里對各個表單字段的限制條件,產品人員務必都要寫清楚,不要遺漏限制條件;
⑤.而接口文檔里針對各個用例場景的返回值,文檔里務必都要及時填寫和更新;
⑥.針對接口文檔返回值的字段:比如code=0表示登錄成功,code=1表示密碼錯誤;code=2表示無網絡;code=3表示賬號錯誤,等等類似code值有不同含義;
3注意:
①.所以,在寫接口測試用例之前,務必要先核對需求文檔和接口文檔,以最正確的需求文檔和接口文檔,來編寫接口用例,才能得到最正確的結果;
②.先實現單接口解耦;后續按照業務場景組合多個接口;
第二步: 拿到個人編寫的接口用例模板,針對特定單接口編寫接口用例
1重點:要及時更新接口用例模板.xlsx,保證用例模板的准確性;
2假設有一個后台系統banner廣告位子模塊,以特定單接口-createBanner-新增banner,來講解編寫單接口的接口用例的操作流程:
(1)新建一個xlsx后綴的excel文檔,excel名改為:banner.xlsx
(2)打開banner.xlsx,第一個sheet頁名稱改為:創建banner
(3)打開接口用例模板.xlsx,把名稱為【接口測試demo】的sheet頁里的所有內容復制到banner.xlsx的名稱為【創建banner】的空sheet頁;
(4)在名稱為【創建banner】的sheet頁里,做如下步驟的操作:
(4-1)針對第一行各單元格數據(第一行各單元格是用來填寫相關參數名和用於標識的參數名):
①按照實際需求來靈活配置:操作入參名所在的單元格;操作返回參數名所在的單元格;
入參名都用紅色字體表示;返回參數名都用綠色字體表示;
比如:修改某個入參名,刪除多余的一個入參名所在的一個列,新增一個列來填寫一個新的入參名;
比如:修改某個返回參數名,刪除多余的一個返回參數名所在的一個列,新增一個列來填寫一個新的返回參數名;
②跟入參名跟返回參數名所在的單元格無關的單元格,都保持原位置和原單元格名稱即可。
( 當然也支持任意移動位置和更改單元格名,但不建議更改單元格名因為更改了單元格名后代碼也要跟着變動)
(4-2)針對第二行各單元格數據(第二行各單元格是用來填寫默認值):
①單元格【驗證字段】的值:不填,為空;
②單元格【接口地址】的默認值值是接口相對路徑,結合實際情況比如更改為:/api/b.banner/creBanner,用於拼接該接口請求的絕對路徑;
③單元格【請求方式】的默認值值是請求方式,結合實際情況比如更改為:post;
④單元格【編號】的值:不填,為空;
⑤單元格【返回值和斷言結果的所在行下標】的值:不填,為空;
⑥單元格【用例類型】的值:不填,為空;
⑦單元格【用例目的】的值:不填,為空;
⑧單元格【用例名】的值:不填,為空;
⑨單元格【返回值】的值:不填,為空;
⑩單元格【斷言結果】的值:不填,為空;
細節1:如果入參名所在的單元格和返回參數名所在的單元格,單元格值可以重復且出現頻率高,那就可以填寫默認值;
細節2:如果入參名所在的單元格和返回參數名所在的單元格,單元格值不可以重復只能是唯一值,那就單元格值為空;
(4-3)針對第三行及大於第三行的各單元格數據(用於填寫具體值):
結合接口用例模板.xlsx的名為【接口測試用例模板】sheet頁稱為A,在banner.xlsx的名為【創建banner】sheet頁稱為B,進行靈活填寫;
操作步驟:
①針對某個接口字段,復制A的三列數據【用例類型】【用例目的】【示范的用例名】,復制給B的三列數據【用例類型】【用例目的】【用例名】;
②然后刪除不需要的用例名對應的行數據;
③更改各條剩下的用例名,比如更改字段名和相關數字等數據;(基本99%都要替換掉從模板復制過來的數據);
④B的這列數據【接口地址】,務必都填寫DF;
⑤B的這列數據【請求方式】,務必都填寫DF;
⑥B的這列數據【編號】,務必都填寫文本形式的數字且數字遞增,值從01開始(文本形式的數字可以再excel里設置);
⑦B的這列數據【返回值和斷言結果的所在行下標】,務必都填寫數字且數字遞增,值從2開始;
⑧B的這列數據【驗證字段】,靈活按照實際入參名來填寫;
⑨B的兩列數據【code】【data】都務必結合接口文檔和需求文檔,填寫正確的值;(若哪些值覺得不合理,后期可以讓產品或開發人員進行修改);
⑩B的兩列數據【code】【msg】都務必結合接口文檔和需求文檔,填寫正確的值;(若哪些值覺得不合理,后期可以讓產品或開發人員進行修改);
細節1:DF表示默認值,取值於第二行各自的單元格值(在腳本里有做相關轉化和校驗);
細節2:&$,表示一個變量,由腳本內部賦值。比如: variable = {"${count}": 666} ;
細節3:B的兩列數據【返回值】【斷言結果】都默認不填寫,這兩列數據都是由相關腳本返回的值;
細節4: 相關顏色的標注,可結合實際來靈活填寫;
細節5: 列數據【data】和列數據【msg】不可能同時有值;
細節6:待補充列表字段【預期值】,把【code】+【data】&【code】+【msg】這樣組合為一個dict,回傳寫入【banner(包含接口返回值和斷言結果).xlsx】;
第三步: 結合項目框架,做相關流程的腳本操作
(1)第一,執行必須操作的步驟(按項目二級目錄的排序順序來執行)
1. 在二級模塊名【/data】內:編寫腳本d_add_banner.py; (在子類D_add_banner的父類屬性variable可結合banner.xlsx內的接口用例賦值情況,來重寫該variable屬性值;)
2. 在二級模塊名【/excel】內:存放banner.xlsx;
3.在二級模塊名【/expectedResult】內:編寫腳本e_add_banner ; (第一次編寫子類E_add_banner時,直接繼承且不重寫任何一個父類方法;調試期間,可按照實際重寫父類相關方法;);
4. 在二級模塊名【/model】內:編寫腳本m_add_banner.py; (第一次編寫子類M_add_banner.py時,直接繼承且不重寫任何一個父類方法;調試期間,可按照實際重寫父類相關方法;)
5. 在二級模塊名【/optimize】內:編寫腳本o_add_banner.py; (第一次編寫子類O_add_banner.py時,直接繼承且不重寫父類的sleep方法;調試期間,可按照實際重寫父類的sleep方法;)
6. 在二級模塊名【/validate】內:編寫腳本v_add_banner.py;(第一次編寫子類V_add_banner.py時,直接繼承且不重寫父類的compareResult方法;調試期間,可按照實際重寫父類的compareResult方法;)
7. 在二級模塊名【/writeCellValue】內:編寫腳本w_add_banner.py;
細節:各步驟對應的類都相對解耦,數據源基本都來自同個上游接口--d_add_banner.py內的子類D_add_banner所調用的父類方法excel_data()的返回值;
(2)第二,再執行非必須操作的步驟(按項目二級目錄的排序順序來執行)
1. 在二級模塊名【/fileAttribute】內:編寫腳本f_add_banner.py;
2. 在二級模塊名【/public】內:存放圖片視頻等相關測試數據;
(3)第三,針對單接口createBanner,調試接口請求
1. 在二級模塊名【/controller】內:編寫腳本c_add_banner.py; (第一次編寫C_add_banner.py時,務必對某個特定的父類方法進行重寫,比如:父類方法add,父類方法update;)
細節1: 相關入參值都采取參數化;
細節2:在【if __name__ == '__main__':】下方區域,進行單元測試的調試;
細節3:用於調試的數據源data,可以在【d_add_banner.py內的子類D_add_banner所調用的父類方法excel_data()的返回值】這邊獲取,獲取符合要求的其中一條數據來當做數據源;
(4)第四,針對單接口createBanner,結合ddt,遍歷執行所有的接口測試用例
1. 在二級模塊名【/testcase】內:編寫腳本test_001_add_banner_testcase.py;
細節1:可直接復制其余現成的腳本,用ctrl+R快捷鍵統一替換相關關鍵字;
細節2:針對不同接口,腳本需增加/減少特定的代碼;(比如【修改】接口可能比【新增接口】需要多調用sleep方法,防止程序執行過快導致接口請求異常返回報錯信息)
細節3:成功執行該腳本后,會在二級模塊名【/excel】里生成對應的【banner(包含接口返回值和斷言結果).xlsx】,會包含每條接口用例的返回值和斷言結果;
(5)第五,核對生成的數據
1. 查看【banner(包含接口返回值和斷言結果).xlsx-【創建banner】sheet頁的 返回值和斷言結果】,大概看一下返回值的數據是否正確;如果有錯誤的返回值,則繼續排查和優化相關腳本;
(6)第六,執行單一入口函數
步驟:
1.配置根目錄run.py相關參數信息;
2.執行run.py
3.生成html格式的測試報告;並,發送相關報告至指定郵箱;
后期拓展:
1.部署線上jenkins服務,部署本地/線上python環境,部署本地/線上wamp環境,CI持續集成;
2.熟悉相關linux命令;優化相關腳本邏輯;
3.部署線上禪道服務,實現主要功能:實時寫入bug&獲取bug清單&更改bug狀態,下載最新包含符合篩選條件的bug的excel文檔;
python相關核心腳本如下:
1 # coding:utf-8 2 ''' 3 @file: test_002_update_banner_testcase.py 4 @author: jingsheng hong 5 @ide: PyCharm 6 @createTime: 2019年07月29日 10點21分 7 @contactInformation: 727803257@qq.com 8 ''' 9 10 11 import unittest 12 import ddt 13 from data.b.banner.d_update_banner import D_update_banner 14 from controller.b.banner.c_update_banner import C_update_Banner 15 from expectedResult.b.banner.e_update_banner import E_update_banner 16 from validate.b.banner.v_update_banner import V_update_banner 17 from optimize.b.banner.o_update_banner import O_update_banner 18 from writeCellValue.b.banner.w_update_banner import W_update_banner 19 20 excel_data = D_update_banner().excel_data() 21 22 @ddt.ddt 23 class Test_update_banner(unittest.TestCase): 24 '''【更新banner】接口的接口測試用例集合''' 25 26 def setUp(self): 27 pass 28 29 def tearDown(self): 30 pass 31 32 @ddt.data(*excel_data) 33 def test_update_banner(self,data): 34 O_update_banner(data).sleep() 35 O_update_banner(data).printAllParamsLog() 36 expectResult = E_update_banner(data).update() 37 actualResult = C_update_Banner(data).update() 38 W_update_banner(data).writeReturnValue(actualResult) 39 O_update_banner(data).printLog(expectResult,actualResult) 40 compareResult = V_update_banner(expectResult,actualResult).compareResult() 41 assertResult = V_update_banner(expectResult,actualResult).assertResult() 42 W_update_banner(data).writeAssertResult(assertResult) 43 self.assertTrue(compareResult) 44 45 if __name__ =="__main__": 46 unittest.main()
php相關控制層腳本如下:
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Administrator 5 * Date: 2019/03/25 6 * Time: 19:02 7 */ 8 9 namespace app\api\controller\b; 10 11 use app\api\controller\ApiCommon; 12 use app\api\controller\utils\File; 13 use app\api\model\ModelBanner; 14 use app\api\validate\V_Banner; 15 use think\Db; 16 use think\Response; 17 18 class Banner extends ApiCommon 19 { 20 21 /** 后台查詢banner列表(可傳id獲取單個banner信息) 22 * @return \think\Response 23 */ 24 public function showBannerA(){ 25 $id = input('param.id'); 26 $page = input('param.page'); 27 $last = input('param.last'); 28 //實例化content,查詢all 29 $mBanner = new ModelBanner(); 30 $list = $mBanner->showBannerA($id,$page,$last); 31 $count = $mBanner->countBanner(); 32 list_upload_image_path_format($list,'image'); 33 if ($list){ 34 $data = [ 35 'code'=>0, 36 'count'=>$count==0?'':$count, 37 'data'=>$list, 38 ]; 39 return response($data,0,array(),'json'); 40 }else{ 41 return api_list_not_more(); 42 } 43 } 44 //Banner排序接口 45 public function sortBanner(){ 46 //獲取到的id和sort值 47 $param = input('param.'); 48 if (empty($param)){ 49 return api_param_error(); 50 } 51 $all=[]; 52 for ($i = 0;$i<count($param['id']);$i++){ 53 //驗證參數 54 $validate = new V_Banner(); 55 if (!$validate->scene('sort')->check(['sort'=>$param['sort'][$i]])) { 56 return api_param_error($validate->getError()); 57 } 58 $info['id']=$param['id'][$i]; 59 $info['sort']=$param['sort'][$i]; 60 $all[] = $info; 61 } 62 $banner = new ModelBanner(); 63 $res = $banner->saveAll($all); 64 if ($res){ 65 return api_success('操作成功'); 66 }else{ 67 return api_error(); 68 } 69 } 70 71 /** 刪除banner(傳id刪除單個banner或傳數組批量刪除) 72 * @return \think\Response 73 */ 74 public function delBanner(){ 75 $id = input('param.id'); 76 $mBanner = new ModelBanner(); 77 $banner =$mBanner->delBanner($id); 78 if ($banner){ 79 return api_success('成功刪除'); 80 }else{ 81 return api_error(); 82 } 83 } 84 /** 創建banner 85 * @return Response 86 */ 87 public function creBanner(){ 88 //獲取內容信息 id title img_path status type 89 $info = input('param.'); 90 //驗證參數 91 $validate = new V_Banner(); 92 if (!$validate->scene('create')->check($info)) { 93 return api_param_error($validate->getError()); 94 } 95 if (strpos($info['sort'],'.')){ 96 return api_error('排序只能是整數'); 97 } 98 $banner = new ModelBanner(); 99 $sort = $banner->where('sort',$info['sort'])->find(); 100 if ($sort){ 101 return api_error('此排序數字已經存在'); 102 } 103 $file = new File(); 104 $files = $file->upload('Banner','image',true); 105 //判斷是否上傳文件 106 if ($files instanceof Response) { 107 return api_error('沒有上傳圖片'); 108 } 109 //檢查是否上傳成功 110 if ($files[0]['is_success']) { 111 // 啟動事務 112 Db::startTrans(); 113 try { 114 $info['image'] = $files[0]['file_path']; 115 116 $res = $banner->save($info); 117 if ($res){ 118 // 提交事務 119 Db::commit(); 120 return api_success('創建成功!'); 121 } 122 } catch (\Exception $e) { 123 // 回滾事務 124 Db::rollback(); 125 return api_success('創建失敗!'); 126 } 127 }else{ 128 return api_error($files[0]['error_msg']); 129 } 130 131 } 132 /** 修改banner 133 * @return \think\Response 134 */ 135 public function upBanner(){ 136 137 //獲取內容信息 id title img_path status type 138 $info = input('param.'); 139 //驗證參數 140 $validate = new V_Banner(); 141 if (!$validate->scene('update')->check(input('param.'))) { 142 return api_param_error($validate->getError()); 143 } 144 if (strpos($info['sort'],'.')){ 145 return api_error('排序只能是整數'); 146 } 147 $banner = new ModelBanner(); 148 $sort = $banner->where('sort',$info['sort'])->find(); 149 if ($sort){ 150 if (!$banner->where('sort',$info['sort'])->find($info['id'])){ 151 return api_error('此排序數字已經存在'); 152 } 153 } 154 $file = new File(); 155 $files = $file->upload('Banner','image',true); 156 157 //判斷是否上傳文件 158 if (!$files instanceof Response) { 159 //判斷是否符合大小和格式 160 if (!$files[0]['is_success']) { 161 return api_error($files[0]['error_msg']); 162 } 163 $info['image'] = $files[0]['file_path']; 164 } 165 // 啟動事務 166 Db::startTrans(); 167 try { 168 $mBanner = new ModelBanner(); 169 $res = $mBanner->save($info,['id'=>$info['id']]); 170 if ($res){ 171 // 提交事務 172 Db::commit(); 173 return api_success('修改成功!'); 174 } 175 } catch (\Exception $e) { 176 // 回滾事務 177 Db::rollback(); 178 return api_error('修改失敗!'); 179 } 180 } 181 182 183 184 /** banner上下線功能 185 * @return Response 186 */ 187 public function togStatus(){ 188 $id = input('param.id'); 189 if (empty($id)){ 190 return api_param_error(); 191 } 192 $mBanner = new ModelBanner(); 193 $result=$mBanner->togStatus($id); 194 if($result){ 195 return api_success("操作成功!"); 196 }else{ 197 return api_error(); 198 } 199 200 } 201 202 /** 測試 203 * @return array|false|\PDOStatement|string|\think\Model 204 */ 205 function f(){ 206 $id = input('param.'); 207 $banner = new ModelBanner(); 208 $item = $banner->find($id); 209 return $item; 210 } 211 212 public function image(){ 213 // $arr = input('param.'); 214 // dump($arr); 215 // return $arr; 216 return input('param.'); 217 return $_POST['title']; 218 return $_FILES; 219 $files = request()->file('file'); 220 return $files; 221 } 222 223 }
待更新 ...