前言
2021年,測試平台如雨后春筍般冒了出來,我就是其中一員,寫了一款pytest內核測試平台,在公司落地。分享出來后,有同學覺得挺不錯,希望能開源,本着“公司代碼不要傳到網上去,以免引起不必要麻煩”的原則,只能在家從頭寫一個,邊重新梳理代碼邊溫習鞏固知識點,以學習交流為目的,定義為“學習版”。
功能展示
登錄
接口自動化
接口自動化--Dashboard:
接口自動化--環境變量:
接口自動化--fixtures:
接口自動化--用例管理:
接口自動化--用例管理--編輯用例:
接口自動化--測試計划:
接口自動化--語法說明:
小工具
小工具--共享腳本:
小工具--HTTP狀態查詢:
后台管理
后台管理--用戶管理
后台管理--項目管理
本文開發內容
- 登錄,登出
- 首頁,修改密碼,個人信息
- 后台管理,用戶管理
- JWT認證
本文先打個基礎,既是測試平台基本結構,也可以作為CMS基礎框架,定制開發各種小型項目。
技術棧
- Node.js 12.16.3
- Vue 4.5.11
- Python 3.8
- Django 3.1.3
- Django REST framework 3.12.2
- SQLite 3
IDE編輯器推薦PyCharm旗艦版,既能寫Django也能寫Vue項目。數據庫使用Django自帶SQLite ,省去安裝MySQL和Navicat/Workbench麻煩,輕量級開發。SQLiteStudio為SQLite數據庫可視化工具,只需要下載即可,無需安裝,解壓就用::
由於會用到models.JSONField
,SQLite默認不兼容,所以需要下載sqlite3.dll
文件替換下:
https://www.sqlite.org/download.html
根據Python版本選擇,比如我的windows安裝的Python38-32,下載了sqlite-dll-win32-x86-3340100.zip
這個軟件包,解壓后將D:\Program Files (x86)\Python38-32\DLLs\sqlite3.dll
替換。
創建Vue項目
設置npm
淘寶鏡像:
npm config set registry https://registry.npm.taobao.org
安裝Vue CLI
:
npm install -g @vue/cli
創建teprunner-frontend
項目:
vue create teprunner-frontend
項目名字請隨意。
默認選項點擊回車進行創建:
編寫Vue代碼
添加靜態資源:
包括css樣式、字體樣式、圖標、logo。
推薦一個圖標下載網站:https://www.easyicon.net/。
編輯package.json
,安裝項目所需依賴:
axios
用於異步請求,發送http
給后端。element-ui
為餓了么開源前端框架,簡化了從頭寫html麻煩,高度復用,統一風格。vue-router
提供了路由跳轉,在上個時代,路由是在后端來控制的,把頁面渲染后返回給前端直接展示,前后端分離后,后端只負責返回數據,把控制權交給前端。
devDependencies
是寫代碼用到的依賴,這里把eslint
和prettier
標出來了,它們是用來做代碼靜態檢查的,配置后能給與代碼規范提示,幫你寫出更漂亮的代碼,同樣是在package.json
文件編輯:
接着執行npm install
進行安裝。有可能會出現下圖提示:
執行npm audit fix
就修復好了:
新建vue.config.js
文件,添加Vue項目配置:
args[0].title
給網頁設置了瀏覽器title。proxy
指定了后端接口根路徑為/api
,后端服務器訪問地址為http://127.0.0.1:8000/
,這是Django啟動后默認本地域名和端口。element-ui
默認頁面是會出現滾動條的,在登錄頁會顯得很丑,需要在public/index.html
加上樣式:
Vue程序執行入口是main.js
,把需要初始化加載的代碼寫在這里:
app會掛載到index.html
文件中div
:
這是整個Vue項目唯一的html
文件,其他組件都是掛載到這個div
下面的。其中有個App.vue
:
它叫做根組件,router-view
是一塊區域,用來展示路由匹配到的組件,也就是說所有路由匹配到的組件都會通過App.vue
根組件來展示。路由配置在router/index.js
文件中編輯:
第一層路由是/login
登錄和/
首頁,首頁只有菜單,沒有具體內容,顯示沒有意義,所以重定向到了后台管理的用戶管理。第二層路由是具體的功能模塊,作為子路由放在首頁路由下,比如后台管理。后台管理的子模塊用戶管理也放到了后台管理的子路由,根據url
訪問路徑定義父子路由關系。
為了在未登錄的情況下,不允許訪問首頁,需要再加上訪問攔截:
同時添加了meta.requireAuth
,用來設置哪些路由需要攔截,這里把首頁設置為True
:
登錄就不需要了。路由配置完成了,接着編寫頁面代碼,Vue項目的頁面只有index.html
一個html
文件,其他頁面都是放在views
文件夾下,新建一個views/login/index.vue
文件:
使用el-form
標簽添加用戶名、密碼、忘記密碼和登錄按鈕。:model
給表單綁定了數據對象,分別填充到form.username
、form.password
、form.rememberMe
:
:rules
定義了表單規則,比如是否必填:
登錄沒有做用戶名和密碼校驗,新增用戶時才會做校驗。
在創建登錄界面時,從localStorage
中移除userInfo
和token
,登錄信息保留7天:
點擊登錄按鈕會調用login
方法,發起登錄請求:
新建views/home/index.vue
,編寫首頁代碼:
<router-link>
提供了鏈接跳轉,左上角logo跳轉到首頁,頂部導航欄根據后端返回的authList
權限菜單進行顯示,因為后台管理只有管理員才能訪問。接着編寫右上角區域代碼:
包括修改密碼、個人信息和退出登錄,為了簡單一點,沒有弄頭像了。修改密碼使用el-dialog
做了個彈出框:
包括當前密碼、新密碼、確認新密碼。並添加了校驗規則:
修改密碼會調用/users/passwords/set
接口:
同時初始化菜單權限,從后端獲取authList
,並判斷是否有權限,沒有權限的話跳轉到登錄頁面:
首頁除了左上角logo,頂部導航欄,右上角個人信息,還有一個重要的版塊就是左側菜單。由於有了頂部導航欄,左側菜單如果也放到首頁來寫,由於層級關系會讓代碼顯得很臃腫,所以菜單是放到每個子模塊來做的。每個子模塊有左側菜單,也會存在很多重復容易的代碼,為了復用,就抽成組件,放到components
文件夾下:
用到了el-menu
標簽:
slot
是個插槽,相當於挖個坑在這,用的時候填一下坑,類似於模板。然后用el-breadcrumb
做了個面包屑,點擊可跳轉到相應路由。接着就把左側菜單應用到后台管理模塊上,新建views/console/index.vue
:
左側菜單搞定了,右側內容也是類似的,查詢、表格、分頁、增刪改查,也需要抽成組件:
再新建views/console/userManagement.vue
,編寫用戶管理代碼:
用到了el-form
和el-table
標簽。表格數據通過:data
綁定到了tableData
對象,調用后端接口后,從響應中拿數據填充:
新增用戶彈窗的入口也是放在這個文件中的:
dialogFormVisible
默認為False
不可見,點擊新增按鈕后,會設置為True
。新建views/console/addUser.vue
文件編寫用戶彈窗的代碼:
用戶管理userManagement.vue
和新增用戶addUser.vue
這兩個組件叫做父子組件,父組件如果想傳值給子組件,需要通過props
來實現:
watch
能監視傳值的狀態,及時渲染。
watch
不是必須的,等到做編輯用例和用例運行結果的時候,會更加體會到它的作用。
新增用戶時,會對用戶名和密碼做校驗:
nameValidator
和pwdValidator
是公共方法,定義在utils/const.js
文件中:
utils
文件夾下還有個commonMethods.js
文件,定義了一些公共js
方法:
本次前端代碼基本編寫完成了:
最后還有個axios.js
,它定義了異步請求實例:
添加了一個請求攔截器:
校驗header
需要包括jwt
請求頭:Authorization: Bearer
。還添加了一個響應攔截器:
對錯誤信息進行捕獲並彈框提示。
創建Django項目
安裝Django:
pip install --default-timeout=6000 -i https://pypi.tuna.tsinghua.edu.cn/simple django
創建teprunner-backend
項目:
django-admin startproject teprunnerbackend
項目名字請隨意。
注意這條命令的項目名字不能帶短橫線-
,如果想用短橫線,可以先去掉短橫線執行命令,再手動改回來,外層這個名字對項目沒有任何影響:
編寫Django代碼
安裝依賴包:
pip install --default-timeout=6000 -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
創建user
應用:
django-admin startapp user
配置teprunnerbackend/settings.py
:
django-cors-headers
為Django提供了跨域訪問的解決方案,需要配置ALLOWED_HOSTS
為*
,允許所有域訪問,並注冊INSTALLED_APPS
和MIDDLEWARE
。user
應用也需要在INSTALLED_APPS
注冊后才會生效。繼續改配置,把時區改為Asia/Shanghai
:
繼續:
Django自帶了一個權限管理系統,為了簡單一點,直接復用。不過需要對user
表進行自定義改造,所以通過配置里面的AUTH_USER_MODEL
指定為剛剛創建的user
應用的User
。REST_FRAMEWORK
是Django RESTful framework的配置項,同樣要進行自定義改造,所以這里通過配置DEFAULT_AUTHENTICATION_CLASSES
指定認證鑒權類為CustomJSONWebTokenAuthentication
,通過EXCEPTION_HANDLER
指定異常處理函數為custom_exception_handler
,通過DEFAULT_PAGINATION_CLASS
指定分頁類為CustomPagination
。JWT_AUTH
是jwt
的配置項,定義了過期時間為30天,允許刷新,刷新間隔,響應處理,header
前綴。最后補充了django-cors-headers
的3個配置。
接着配置teprunnerbackend/urls.py
:
把user
的url
都添加到api/users/
下面。新建user/urls.py
文件:
分別添加登錄、用戶增刪改查、重置密碼、角色列表、修改密碼幾個路徑。Django的視圖有兩個類型:類視圖和函數視圖。path()
只接受可調用對象,所以類視圖需要使用as_view()
進行轉化,比如views.UserLogin.as_view()
。函數視圖直接寫上函數名就可以了,比如views.update_password
。
打開user/models.py
文件,添加數據模型:
model
建立了代碼和數據庫的映射,這稱為orm
,對象關系映射。基礎表定義了共有的created_at
和updated_at
字段。auto_now_add
表示記錄新增時間,auto_now
表示記錄更新時間,都是自動進行,無需手動寫代碼來處理。用戶表繼承了Django自帶的AbstractUser
,REQUIRED_FIELDS
規定了哪些字段必填,username
和password
是隱式規定了必填的,不需要設置,默認email
也是必填,這里把它去掉。
Django默認表名會加上
django_
前綴,使用Meta.db_table
來定義沒有前綴的表名。
model
寫完了,執行以下命令同步到數據庫中,創建表結構:
python manage.py makemigrations
python manage.py migrate
打開SQLiteStudio
,選擇根目錄的db.sqlite3
文件:
看到表結構已經創建好了:
Role
有個models.JSONField
字段,為菜單權限JSON
,使用Django的fixtures
給項目添加初始化數據:
fixtures
名字是固定的,就像pytest的conftest.py
一樣,只認這個名字。user.json
存放數據:
包括管理員用戶、角色權限、管理員角色對照關系。其中角色權限數據共3條:
auth
里面定義了菜單,對應首頁的頂部導航欄的欄目,比如本文只添加了后台管理。access
表示角色是否有權限訪問,只有管理員的這條數據,access
為true
。通過以下命令把這些數據寫入數據庫中:
python manage.py loaddata user
Django會在
user.fixtures
目錄下自動找名字為user
的.json
、.xml
或.yaml
文件進行加載。
接着新建一個user/serializers.py
文件寫序列化的代碼。Django序列化是指,把數據庫的數據轉化為json
返回給前端,反序列化是指把前端傳過來的json
寫入數據庫。先寫登錄的序列化器:
繼承自serializers.ModelSerializer
,一般需要在Meta
定義兩個屬性,model
指定相應的模型,fields
指定所需要的的字段,這些字段就是json
的key
。圖中的roleName
不屬於User
表的字段,所以通過定義為serializers.SerializerMethodField()
,再定義get_roleName()
方法來取。serializer
寫好后,寫視圖,編輯user/views.py
:
由於是jwt
認證,所以這里繼承了JSONWebTokenAPIView
,提取請求參數,check_password()
簡單校驗了下請求的密碼和數據庫的密碼hash
值是否相等,后面的代碼是JSONWebTokenAPIView.post
方法現成的,重寫了一遍。ErrInvalidPassword
等是在user/errors.py
定義的錯誤響應:
這樣可讀性會更高。響應狀態碼也建議這么寫
status=status.HTTP_500_INTERNAL_SERVER_ERROR
,from rest_framework import status
已經定義好了所有狀態碼的常量。
新建user/utils.py
文件,編寫jwt_response_payload_handler
來定義登錄接口的響應結構:
返回token
、user
、auth
三個字段。custom_exception_handler
規范了一下異常響應信息。這2個方法都是在settings.py
中的REST_FRAMEWORK
配置過的,還有一項配置是分頁,新建user/pagination.py
文件:
繼承了PageNumberPagination
,指定了查詢參數名page
、perPage
,自定義了響應字段名currentPage
、items
、totalNum
、totalPage
,並添加了2個字段hasNext
和hasPrev
。
最后還有一個配置項,自定義認證鑒權,新增user/authentications.py
:
繼承了BaseJSONWebTokenAuthentication
。通過get_authorization_header
提取請求頭中的Authorization
字段,沒有就提示“缺失JWT請求頭”。后面的代碼是父類現成的。
至此,整個后端基本代碼都寫完了,jwt
認證也做好了:
后面的代碼就集中在serializers.py
和views.py
兩個文件,序列化器提供數據庫表字段和響應json
的序列化和反序列化,視圖使用序列化器,編寫業務處理代碼。按照這個思路,編寫用戶的增刪改查功能,先在serializers.py
文件中寫2個序列化器UserCreateSerializer
和UserPagingSerializer
:
由於新增用戶和用戶列表展示的字段不一樣,所以給同一個User
模型創建了2個序列化器。圖中標紅了代碼是把int
的id
值轉化為了str
類型,方便前端處理。is_staff
表示是否為管理員,這個名字是Django定的。再寫views.py
:
GenericViewSet
是Django REST framework提供了超級封裝的類視圖,一般不需要重寫,給queryset
和serializer_class
賦值就可以了。不過因為有些自定義規則,所以本項目進行了復寫。permission_classes
指定了接口訪問權限,IsAdminUser
表示必須管理員才能訪問,也是Django定義好的,和前面的is_staff
相對應:
類似的,我在user/permissions.py
新建了個IsTester
,用來控制某些功能只能測試使用:
本文還用不到這個。
重寫查詢用戶列表list
方法:
增加username
和nickname
的模糊查詢。
重寫新增用戶create
方法:
首先寫user
表,根據角色名是否包含管理員,判斷是否寫is_staff
字段,接着用入庫后產生的user_id
寫user_role
表。注意最后一行的status
,新增的話,狀態碼返回201
。
重寫修改用戶的put
方法:
和新增用戶的區別在於,更新user_role
表數據時,需要根據老角色和新角色,比較差異后,添加新增的,刪除廢舊的。
重寫刪除用戶的delete
方法:
同時刪除user
表和user_role
表。注意最后一行的status
,刪除的話,狀態碼返回204
。
另外還自定義了user_detail
方法,返回單個用戶信息:
GenericViewSet
的這些請求方法在user/urls.py
文件中配置映射關系:
<int:pk>
定義了url
中的整形參數,pk
為變量名,通過kwargs["pk"]
來取。
在新增用戶的時候,需要從角色列表中選擇角色,需要后端提供這樣的接口,使用ListAPIView
:
4行代碼搞定一個接口,這就是Django的好處,除了ListAPIView
,還有CreateAPIView
、RetrieveAPIView
、ListCreateAPIView
等,按需取用。
密碼重置接口用APIView
來實現:
定義了put
方法,從請求url
中獲取參數值user_id
,查詢user
對象后,調用預置的set_password
方法,把密碼重置為qa123456
。記得調用user.save()
把數據更新到數據庫。
除了類視圖,Django也提供了函數視圖,並且Django REST framework提供了函數視圖的方法裝飾器,可以像flask
框架一樣,感受寫純后端接口的體驗,按這個方法來寫修改密碼接口:
@api_view(['PUT'])
是Django REST framework提供的方法裝飾器。修改密碼時,會對jwt
進行解碼,獲取到user_id
,然后檢查老密碼是否和數據庫中的密碼hash
值一致。
前后端聯調
根據以上思路把前后端的代碼寫完以后,就可以把項目跑起來看看效果了。先啟動Django項目:
python manage.py runserver
接着啟動Vue項目:
npm run serve
訪問:
就能看到登錄頁面了,默認超管用戶名為admin
,密碼為qa123456
,登錄成功后可以嘗試走一遍功能檢查下:
- 點擊左上角logo,不會出現跳轉到空白頁。
- 點擊右上角信息,彈出下拉菜單,分別有修改密碼、個人信息、退出登錄。
- 點擊退出,返回登錄頁,重新登錄。
- 查詢右上角個人信息,包括用戶名、昵稱、角色。
- 通過右上角下拉菜單修改密碼,和老密碼不匹配會提示修改失敗,填寫正確信息會修改成功,自動跳轉到登錄頁面重新登錄。輸入老密碼登錄失敗,輸入新密碼登錄成功。
- 新增用戶,保持默認密碼,新增成功后,用
qa123456
登錄成功。 - 新增用戶,選擇自定義密碼,新增成功后,用
qa123456
登錄失敗,用自定義密碼登錄成功。 - 新增用戶,分別創建管理員、開發、測試3個角色用戶。
- 使用新用戶登錄,管理員用戶能登錄成功,開發和測試由於沒有后台管理權限,點擊登錄接口后會提示“無菜單權限”。
- 修改用戶,修改用戶名、密碼,修改測試角色用戶為管理員角色,重新登錄,能看到用戶名、密碼已更新為修改后的用戶名、密碼,並且管理員角色生效,能登進去看到后台管理功能。
- 輸入用戶名或昵稱,點擊搜索按鈕,測試模糊查詢功能正常,重置后清空搜索框,自動查詢一次列表。
- 點擊刪除按鈕,提示是否確認刪除,確認后刪除成功,檢查數據庫
user_role
表數據也被清理干凈。 - 切換分頁,刷新列表,選擇不同分頁條數,正常計算顯示相應的分頁總數。
- 找到自定義密碼的用戶,點擊重置密碼,重置成功后,重新登錄,使用自定義密碼登錄失敗,使用默認密碼
qa123456
登錄成功。 - 點擊左側菜單旁邊的面包屑按鈕,能收起和展開左側菜單。
由於時間關系,目前還沒有做角色管理功能,角色通過后端Django的
fixtures/user.json
進行數據初始化。
Postman搭建Mock Server
在寫前端代碼過程中,后端還沒有寫好,可以找一個服務模擬后端,提供響應數據進行前端調試,這項技術叫做Mock,這個服務稱為Mock Server。Mock Server可以使用JavaScript的mock.js
來搭建,也可以使用Python的FastAPI
,不嫌麻煩用Spring Boot
或者Nginx
做一個也可以。不過為了方便起見,選擇上手就能用的可以省不少心。一些網站會提供在線Mock服務,在網站上填寫url
和response body
,有個缺點是我找了一圈都沒有發現能設置響應狀態碼的,比如在調試axios.js
的響應攔截器時,就需要根據404
、500
來進行調試,看效果。尋尋覓覓,發現平時都在用的Postman,直接可以做Mock Server。首先登陸Postman,只有登陸后才能使用這個功能:
可以選擇用Google賬號登陸,也可以注冊一個。點擊左上角+ New
:
選擇Mock Server
。依次填寫請求方法、請求路徑、響應狀態碼、響應體:
點擊表格右上角的三個點還能添加請求體和接口描述:
接着點擊下一步:
填寫Mock Server的名字為hello
,其他選項默認,點擊Create Mock Server
進行創建:
關閉后,Mock Server就建好了:
接着從左側Collections
中找到這個接口,點擊打開:
此時還不能發送請求,需要在右上角選擇環境hello
:
發送請求成功:
其中url
是隱藏了的,點擊右上角環境旁邊的眼睛圖標查看:
修改已創建接口mock數據的入口在Examples
:
點擊Default
:
提供了新增時更直觀的操作界面,比如我把響應狀態碼改成了404
,響應體改成了{"msg": "hello not found"}
,點擊右上角Save Example
保存后,再次請求:
實際mock的狀態碼和響應體也更新了。
小結
本文是學習版pytest內核測試平台開發的入門篇,內容比較充實,全文字數上萬,一共截了103張圖,借助FastStone Capture
這個小工具,還算輕松,希望能讓讀者更直觀的看到平台功能和代碼邏輯。“編寫Vue代碼”和“編寫Django代碼”2個小節的內容是一氣呵成寫完的,沒有做修改,怕改了之后反而影響行文思路,不夠暢快。前端項目參考了一些開源項目如Tcloud
、FasterRunner
等,把代碼看懂后,自己重新組織了代碼和規范,在調試過程中,也學會了寫Vue,做學習版teprunner
時就從頭寫了一遍。后端代碼完全是我自己寫的,先學了一遍Django和Django REST framework官方教程,其中《Django認證系統並不雞肋反而很重要》這篇文章在騰訊雲+社區2020年度征文活動中,被評選為了最受喜愛作者獎,如果對Django認證系統不是很清楚的話,可以看看。最后,個人水平有限,有錯誤或不足之處,還請見諒。雖然測試平台不一定能完全落地,但是做一遍開發對能力的提升是極大的。teprunner
並不算優秀平台,和真正企業級項目比起來,就是個小玩具。如果能博君一樂,也算是體現價值了。
參考資料:
源碼前端 https://github.com/dongfanger/teprunner-frontend