通过一次场景模拟来深入理解和实践持续集成(CI)

栏目: 服务器 · 发布时间: 4年前

内容简介:最近参加 leader 所举办的 CI 相关的 CoP(Communication of Practice),过程相当精彩且收获甚多,本文是听完了第二场分享“CI in action”之后的一次总结,分享本身是基于 Java 和 Gradle 进行的,但是由于对前端开发者而言,可能以前端所了解的技术栈进行总结更为熟悉。因此,本着吸收分享内容且结合前端实际开发的初衷,写下本文本次课进行了一次场景的模拟。首先,我们拥有一个共享仓库,它托管了实现一个简易计算器

最近参加 leader 所举办的 CI 相关的 CoP(Communication of Practice),过程相当精彩且收获甚多,本文是听完了第二场分享“CI in action”之后的一次总结,分享本身是基于 Java 和 Gradle 进行的,但是由于对前端开发者而言,可能以前端所了解的技术栈进行总结更为熟悉。因此,本着吸收分享内容且结合前端实际开发的初衷,写下本文

一、场景模拟

本次课进行了一次场景的模拟。首先,我们拥有一个共享仓库,它托管了实现一个简易计算器 APP 的代码(相关的代码在 Github ),其目录结构为:

通过一次场景模拟来深入理解和实践持续集成(CI)

其中:

./src
./test
./gulpfile.js

在本次场景模拟中,开发人员被分为两组,每组有三个人,他们将进行 Peering Programming (结对编程) ,因此,我们可以暂时省略 Code Review 这个过程:

A组
B组

场景中,涉及的机器有三台:

  • A组的开发机
  • B组的开发机
  • 公共机(模拟的CI服务器)

二、开发功能与提交代码

1、A组开发

现在,A组开始实现乘法功能,一顿操作猛如虎:

git clone git@github.com:RuphiLau/ci-action-sample.git
git checkout -b feat/multiply
./src/OperationType.ts
export enum OperationType {
    PLUS = 0,
    MINUS = 1,
    MULTIPLY = 2 // +
}
  • 打开项目文件 ./src/Calculator.ts ,代码如下:
import { OperationType } from './OperationType'

export default class Calculator {
    public calculate(action: OperationType, a: number, b: number): number {
        switch (action) {
            case OperationType.PLUS:
                return this._plus(a, b)
            case OperationType.MINUS:
                return this._minus(a, b)
            case OperationType.MULTIPLY:        // +
                return this._multiply(a, b)     // +
        }
    }
    private _plus(a: number, b: number): number {
        return a + b
    }
    private _minus(a: number, b: number): number {
        return a - b
    }
    private _multiply(a: number, b: number): number {   // +
        return a * b                                    // +
    }                                                   // +
}
  • 补充下自动化测试脚本,打开 ./test/Calculator.spec.ts ,代码如下:
import { expect } from 'chai'
import Calculator from '../src/Calculator'
import { OperationType } from '../src/OperationType'

describe('Calculator', () => {
    describe('Plus', () => {
        it('should return the plus result of two numbers', () => {
            const calc = new Calculator()
            const result = calc.calculate(OperationType.PLUS, 1, 2)
            expect(result).to.equal(3)
        })
    })
    describe('Minus', () => {
        it('should return the minus result of two numbers', () => {
            const calc = new Calculator()
            const result = calc.calculate(OperationType.MINUS, 2, 1)
            expect(result).to.equal(1)
        })
    })
    describe('Multiply', () => {                                        // +
        it('should return the multiply result of two numbers', () => {  // +
            const calc = new Calculator()                               // +
            const result = calc.calculate(OperationType.MULTIPLY, 1, 2) // +
            expect(result).to.equal(2)                                  // +
        })                                                              // +
    })                                                                  // +
})

OK,接下来,A组需要先在本地执行测试和构建:

$ npm run build

输出:

> ci-action-sample@1.0.0 build /Users/ruphi.liu/Desktop/Lab/ci-action-sample
> gulp

[18:00:20] Using gulpfile ~/Desktop/Lab/ci-action-sample/gulpfile.js
[18:00:20] Starting 'default'...
[18:00:20] Starting 'test'...


  Calculator
    Plus
      ✓ should return the plus result of two numbers
    Minus
      ✓ should return the minus result of two numbers
    Multiply
      ✓ should return the multiply result of two numbers


  3 passing (29ms)

[18:00:22] Finished 'test' after 1.26 s
[18:00:22] Starting 'build'...
[18:00:22] Finished 'build' after 123 ms
[18:00:22] Finished 'default' after 1.39 s

Nice~~,好了,A组已经完成了”乘法功能“,因此接下来需要提交代码,提PR,继续一顿操作猛如虎:

  • git add .
  • git commit -m 'feat: Implement the multiply operation'
  • git checkout master
  • git pull
  • git checkout feat/multiply
  • git rebase master ,没有冲突,完美~
  • git push
  • Pull Request ,而后 Squash and merge

正常而言,在部署了CI自动化流程后,在PR通过且被 mergemaster 后,便会触发相应钩子,从而触发 CI服务器 中的自动化测试和构建过程。但是为了直观地了解CI做了什么,因此我们本次采用手工模拟 CI服务器 所做的事情:

  • CI服务器 创建一个隔离的环境
  • CI服务器 克隆代码: git clone git@github.com:RuphiLau/ci-action-sample.git
  • CI服务器 执行测试和构建过程,由于本过程已经都在 gulpfile.js 里写好了,拆解来看,做了如下事情:
    gulp.task('test')
    gulp.task('build')
    
  • CI服务器 完成构建过程,构建结果代码置于 ./dist 目录
  • CI服务器 执行 自动化部署 ,将构建后的代码部署到生产环境

2、B组开发

B组和A组同时开始开发,B组实现的是“除法功能”,在B组写完代码后,进行如下的流程:

  • npm run build ,得到:
> ci-action-sample@1.0.0 build /Users/ruphi.liu/Desktop/Lab/ci-action-sample
> gulp

[21:46:07] Using gulpfile ~/Desktop/Lab/ci-action-sample/gulpfile.js
[21:46:07] Starting 'default'...
[21:46:07] Starting 'test'...


  Calculator
    Plus
      ✓ should return the plus result of two numbers
    Minus
      ✓ should return the minus result of two numbers
    Divide
      ✓ should return the divide result of two numbers
      ✓ should throw error when the divisor is zero


  4 passing (33ms)

[21:46:08] Finished 'test' after 1.21 s
[21:46:08] Starting 'build'...
[21:46:08] Finished 'build' after 121 ms
[21:46:08] Finished 'default' after 1.33 s

这说明构建是OK的,因此我们可以继续提交

git add .
git commit -m 'feat: Implement the divide operation
git checkout master
git pull
git rebase master
First, rewinding head to replay your work on top of it...
Applying: feat: Implement the divide operation
Using index info to reconstruct a base tree...
M       src/Calculator.ts
M       src/OperationType.ts
M       test/Calculator.spec.ts
Falling back to patching base and 3-way merge...
Auto-merging test/Calculator.spec.ts
CONFLICT (content): Merge conflict in test/Calculator.spec.ts
Auto-merging src/OperationType.ts
CONFLICT (content): Merge conflict in src/OperationType.ts
Auto-merging src/Calculator.ts
CONFLICT (content): Merge conflict in src/Calculator.ts
error: Failed to merge in the changes.
Patch failed at 0001 feat: Implement the divide operation

因此我们首先需要解决冲突,在解决完冲突后,执行: npm run build ,OK,发现没问题,我们可以继续了:

git add .
git rebase --continue
git push --set-upstream origin feat/divide

接下来,我们可以再提一个 PR ,因为不需要 Code Review ,所以我们可以直接 Squash and merge 代码,之后 CI服务器 上仍然会执行自动化测试和构建工作,在最终构建完成后,我们就得到集成了 AB 两组开发的功能的代码,这也就是 CI 本质上所做的事情:

CI本质上就是合代码,持续地进行代码的合并。所谓持续,是经常性地提交代码并触发CI流程(业界通常认为这个频率是至少每天一次);而集成,就是将分支代码合并到主分支里。而我们所做的自动化测试等工作,是为了高质量地合并代码

三、冲突与构建失败问题

现在,我们假设一个情况:

B组开发完成后,提交的代码是有问题的,并且因为一些蜜汁操作,这些代码意外地上到 master 分支里了,那么会发生什么情况?

这将导致: 在merge后触发了CI流程,但由于代码有问题,跑CI的过程将出错 ,那么怎么办呢?

通常的做法是: 回滚 ,将代码回滚到 merge 之前的状态,然后由 B组 解决 CI构建 过程中出现的问题,修复完成后重新提PR和合并代码

四、总结

经过本次的场景分析,我们可以把整个流程图示为:

通过一次场景模拟来深入理解和实践持续集成(CI)

而对这个流程的分析,我们可以总结出 CI 的最佳实践:

  • 每次merge就触发CI 正因为CI是为了高质量地合代码,CI的本质也是合代码,所以每次 merge 都应该是执行 CI 的时机。我们可以通过相应的 hooks ,在监测到即将有代码要合到 master 时,可以执行 CI流程 ,比如在这个时机之前检查是否有进行 Code Review ,在此时机之后执行自动化测试和构建
  • 隔离与独立的测试构建 CI的每次构建都应该是在一个独立的上下文中,在这个独立的上下文中安装依赖、构建和跑自动化测试等。可以通过 Docker 来实现
  • CI流程开始后,开发人员保持关注,直到构建完成 在CI流程中,可能由于构建失败导致流程挂掉,如果开发人员未保持关注,则会在挂掉后很长一段时间内都无人知晓, 从而造成时间上的浪费 。比如可能开发人员通过过往经验得出这次构建需要一个小时,因此在CI跑起来后,开发人员就去做其他事情了,实际上CI在10分钟后就挂掉了,如果开发人员未保持关注,在一个小时后才来看CI的结果,那么中间这50分钟无疑是浪费的。假如开发人员保持关注,则能够在10分钟出现问题的时候及时回滚去修复问题,从而进入下一次的提交和 CI流程 。开发人员保持关注的手段有很多种,比如关注 CI看板 (看板上会展示相关的构建信息,如失败后飘红),也可以是设立 告警机制 (失败时开发者能够收到告警短信、邮件),更简单的则是开发者盯着 CI日志 看,发现问题后立马进行处理

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++沉思录

C++沉思录

Andrew Koenig、Barbara Moo / 黄晓春、孟岩(审校) / 人民邮电出版社 / 2002-11-01 / 50.00元

《C++ 沉思录》集中反映了C++的关键思想和编程技术,不仅告诉你如何编程,还告诉你为什么要这样编程。本书曾出现在众多的C++专家推荐书目中。 这将是C++程序员的必读之作。因为: 它包含了丰富的C++思想和技术,从详细的代码实例总结出程序设计的原则和方法。 不仅教你如何遵循规则,还教你如何思考C++编程。 既包括面向对象编程也包括泛型编程。 探究STL这一近年来C++最重要的新成果的内在思想。一起来看看 《C++沉思录》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试