首先介绍下在本文出现的几个比较重要的概念: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 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,作最懂云原生开发者的技术圈。”