首先介紹下在本文出現的幾個比較重要的概念:javascript
函數計算(Function Compute): 函數計算是一個事件驅動的服務,經過函數計算,用戶無需管理服務器等運行狀況,只需編寫代碼並上傳。函數計算準備計算資源,並以彈性伸縮的方式運行用戶代碼,而用戶只需根據實際代碼運行所消耗的資源進行付費。函數計算更多信息 參考。
Funcraft:Funcraft 是一個用於支持 Serverless 應用部署的工具,能幫助您便捷地管理函數計算、API 網關、日誌服務等資源。它經過一個資源配置文件(template.yml),協助您進行開發、構建、部署操做。Fun 的更多文檔 參考。
OSS: 對象存儲。海量、安全、低成本、高可靠的雲存儲服務,提供99.9999999999%的數據可靠性。使用RESTful API 能夠在互聯網任何位置存儲和訪問,容量和處理能力彈性擴展,多種存儲類型供選擇全面優化存儲成本。
ROS:資源編排(ROS)是一種簡單易用的雲計算資源管理和自動化運維服務。用戶經過模板描述多個雲計算資源的依賴關係、配置等,並自動完成全部資源的建立和配置,以達到自動化部署、運維等目的。編排模板同時也是一種標準化的資源和應用交付方式,而且能夠隨時編輯修改,使基礎設施即代碼(Infrastructure as Code)成爲可能。
CI/CD: CI/CD 是一種經過在應用開發階段引入自動化來頻繁向客戶交付應用的方法。CI/CD 的核心概念是持續集成、持續交付和持續部署。
本文打算以一個簡單的函數計算項目爲例,在此基礎上編寫測試用例,進行配置,讓其支持 CI/CD 工做流程。實現以下四個小目標:html
這裏以你們熟悉的 Github 倉庫爲例,並結合 Travis CI 。當用戶往示例項目 push 或者 PR(Pull Request)時,會自動觸發 Travis CI 的工做任務,進行單元測試、構建打包和部署發佈。java
示例項目地址爲:https://github.com/vangie/tz-... ,該項目是基於 FC Http trigger 實現的簡單 web 函數,訪問放函數是會返回指定時區的當前時間。項目目錄結構以下linux
tz-time ├── .funignore ├── .travis.yml ├── Makefile ├── bin │ ├── delRosStack.sh │ ├── deployE2EStack.sh │ └── waitForServer.sh ├── deploy.log ├── index.e2e-test.js ├── index.integration-test.js ├── index.js ├── index.test.js ├── jest.config.e2e.js ├── jest.config.integration.js ├── package-lock.json ├── package.json └── template.yml
部分文件做用介紹:git
.funignore
- Funcraft 部署時突然的文件清單.travis.yml
- Travis CI 配置文件index.js
- 函數入口文件測試一般很是以下三類:單元測試、集成測試和 E2E 測試。在函數計算場景下,這三類測試能夠經過以下方法實現。github
fun local invoke/start
模擬運行函數本例子只實現了單元測試,集成測試和 E2E 測試對於 travis 示例來講觸發方法相似,實現方法能夠參見上面的方法提示進行配置。web
FC 函數的單元測試和普通的函數並沒有二致。採用熟悉的單元測試框架便可,本例中使用了 jest 進行測試。下面看看一個測試用例的代碼片斷shell
jest.mock('moment-timezone'); const { tz } = require('moment-timezone'); const { handler } = require('./index'); const EXPECTED_DATE = '2018-10-01 00:00:00'; const TIMEZONE = 'America/New_York'; describe('when call handle', () => { it('Should return the expected date if the provied timezone exists', () => { const mockReq = { queries: { tz: TIMEZONE } } const mockResp = { setHeader: jest.fn(), send: jest.fn() } tz.names = () => [TIMEZONE]; tz.mockImplementation(() => { return { format: () => EXPECTED_DATE } }) handler(mockReq, mockResp, null); expect(mockResp.setHeader.mock.calls.length).toBe(1); expect(mockResp.setHeader.mock.calls[0][0]).toBe('content-type'); expect(mockResp.setHeader.mock.calls[0][1]).toBe('application/json'); expect(mockResp.send.mock.calls.length).toBe(1); expect(mockResp.send.mock.calls[0][0]).toBe(JSON.stringify({ statusCode: '200', message: `The time in ${TIMEZONE} is: ${EXPECTED_DATE}` }, null, ' ')); }); });
經過 jest.mock 對 moment-timezone 進行 mock,讓 tz 被調用的時候返回預先設定好的值,而不是一個動態變化的時間。npm
一般該類單元測試分爲三步:編程
若是依賴包不存在原生依賴(依賴 linux 下的可執行文件或者 so 庫文件)的使用 npm test 觸發測試便可,若是有原生依賴,那測試須要跑在 fun 提供的 sbox 模擬環境裏,使用以下命令觸發
fun install sbox -f tz-time --cmd 'npm install'
本例子中的集成測試會藉助 fun local start 命令把函數在本地啓動起來,因爲函數配置了 http trigger,因此能夠經過 http 請求調用函數。
集成測試咱們仍是纔是 jest 框架進行編寫,爲了區別於單元測試文件 *.test.js
,集成測試文件使用 .integration-test.js
文件後綴。爲了讓 jest 命令獨立的跑集成測試用例而不是和單元測試混和在一塊兒,須要編撰以下文件 jest.config.integration.js
module.exports = { testMatch: ["**/?(*.)integration-test.js"] };
而後在 package.json 中配置 scripts
"scripts": { "integration:test": "jest -c jest.config.integration.js" }
因而能夠經過執行 npm run integration:test 來執行集成測試。
而後在此基礎上在 Makefile 中添加 integration-test 目標:
funlocal.PID: fun local start & echo $$! > $@ integration-test: funlocal.PID bin/waitForServer.sh http://localhost:8000/2016-08-15/proxy/tz-time/tz-time/ npm run integration:test kill -2 `cat $<` && rm $<
integration-test 目標依賴 funlocal.PID 目標,後者負責啓動一個 fun local 進程,該進程會在本地啓動 8000 端口。解讀一下上面的 Makefile 代碼
fun local start & echo $$! > $@
啓動 fun local 進程,並將進程 PID 寫入到目標同名文件 funlocal.PIDbin/waitForServer.sh http://localhost:8000/2016-08-15/proxy/tz-time/tz-time/
經過一個 url 測試 fun local 進程是否啓動完成。kill -2 `cat $<` && rm $<
測試完成之後銷燬 fun local 進程。npm run integration:test
會啓動若干的測試用例,其中一個測試用例以下:
const request = require('request'); const url = 'http://localhost:8000/2016-08-15/proxy/tz-time/tz-time/'; describe('request url', () => { it('without tz', (done) => { request(url, (error, response, data) => { if (error) { fail(error); } else { const resData = JSON.parse(data); expect(resData.statusCode).toBe(200); expect(resData.message).toContain('Asia/Shanghai'); } done(); }); }); });
端對端測試和集成測試的測試用例很是的相似,區別在於測試的服務端,端對端測試部署一套真實的環境,集成測試經過 fun local 本地模擬。
本例中藉助 fun deploy --use-ros
部署一套環境,環境名稱爲 tz-e2e-
前綴帶上時間戳,這樣每次測試都會部署一套新的環境,不一樣環境之間相互不會影響。測試完成再經過 aliyun-cli 工具把 ROS 的 stack 刪除便可。
下面端對端測試的 Makefile 目標:
stack_name := tz-e2e-$(shell date +%s) e2e-test: # deploy e2e bin/deployE2EStack.sh $(stack_name) # run test npm run e2e:test # cleanup bin/delRosStack.sh $(stack_name)
bin/deployE2EStack.sh $(stack_name)
負責部署一個新的 ROS stack。部署以前須要使用 fun package 構建交付物,具體如何構建交付物能夠參考下一小節。npm run e2e:test
運行端對端測試bin/delRosStack.sh $(stack_name)
測試完成以後,清理部署的 ROS stack,會釋放掉響應的雲資源。fun package
命令可被用於構建交付物,fun package
須要指定一個 OSS 的 bucket。fun package 命令會完成以下步驟:
生成的 template.packaged.yml 文件就是最終交付物,能夠經過 fun deploy 命名進行部署。
當構建環節生成了交付物之後,就能夠經過 fun deploy 進行部署了。持續部署須要解決以下兩個問題:
fun deploy
藉助於 ROS,能夠輕鬆的解決上述問題。
fun deploy --use-ros --stack-name tz-staging --assume-yes
其中:
--use-ros
表示藉助於 ROS 進行部署,其工做機制是將 template.yml 推送到 ROS 服務,由 ROS 服務執行每一個服務的新建和更新操做。若是沒有該參數,fun 就會在本地解析 template.yml,調用 API 進行資源建立。ROS 有個額外的好處是能夠進行部署的回滾,失敗的時候能自動進行回滾。--stack-name
指定一個 stack 的名稱,stack 是 ROS 的概念,能夠理解爲一套環境。--assume-yes
用於無人值守模式,跳過確認提示。注意,此處若是不指定參數 --use-ros
,fun deploy 會採用直接調用雲資源 API 進行部署, 這是 fun deploy 的默認部署方式,雖然也基本實現了冪等部署,可是僅支持部署有限的雲資源(FC、OTS、API Gateway等),遠不及 ROS 豐富,並且也無法作到 ROS 已支持的回滾和一鍵刪除,因此此處不推薦。
上面全部步驟的腳本化配置能夠參考 Makefile 和 .travis.yml 文件。經過上述兩個文件能夠實現 Github 和 Travis CI 的聯動,實現基於代碼提交觸發的 CI/CD。
本文講述了 FC 函數
「 阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術圈。」