Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

当前团队的两大痛点『自动化测试』与『持续集成』及可能的解决方案 #191

Open
2 tasks
EthanLin-TWer opened this issue Dec 10, 2017 · 4 comments

Comments

@EthanLin-TWer
Copy link
Owner

EthanLin-TWer commented Dec 10, 2017

背景

最近团队在代码合并、回归测试的环节遇到了比较明显的瓶颈和困难。讨论这些问题的前提假设是:在当时的情景、资源下,团队已经拿出了当时觉得最好的解决方案。这也是我所相信的,经历这个项目的从头至尾,能理解它发展至此的背后动力。不过,要讲我们真正遇到的痛点之前,有必要先讲一下项目前面的一些故事。

这个项目之前是以一个 P 项目(瀑布项目:2周开发+2周 ST+2周 UAT+上线)的形式进行了大约2个多月,到最近过渡并转成一个 M 项目(敏捷项目:3周开发+1周 ST&UAT+上线)。我们工作的软件又包含两个部分:1.0 的 Angular 应用 + 2.0 的 React Native 应用,每次发布都需要同时构建、打包两边的代码。在项目起始之初,我们对发布的需求并没有那么强,因此团队相信采用特性分支(feature branch)的方式进行开发已经可以满足开发和部署需求。而事实上,特性分支确实没对开发过程带来过多障碍,但对发布流程却带来了真真切切的挑战。而这次敏捷项目转型,其实又在背后推了一把,把我们在部署上持续不能集成这个问题,更彻底地暴露出来。

在项目发布最复杂的时候(P 项目向 M 项目转型的过渡期),我们同时有3个版本在进行开发:领导紧急要求的版本、P 项目最后一次发布、M 项目的第一个迭代的代码。此时我们是采用 develop 作为主分支,release 作为主发布分支,对于这种突然插进来的领导版本,自然是单独拉一条分支出来发布,这就是我们的 hotfix/boss 分支。同时,develop 分支上在进行着当前 M 项目第一个迭代的代码开发,并且又有一部分人力在为上个瀑布项目的 release 分支上修复一些 ST/UAT 测出来的 bug。就这样,我们一个迭代在3个分支上同时工作着,有时修复一个 bug,可能同时需要将这个修复 cherry-pick/merge 到其他两个分支中,作为一个团队我们似乎从来都没能100%执行这套 pick 的操作。更要命的是,1.0 和 2.0 发布的版本需要一一对应,两个代码库的3条发布分支组合起来,就是3 * 3 = 9种可能,稍一弄错,花20分钟打出来的包就少个功能或多个功能,要遭到 QA 的嘲讽。

经历过这段过渡期(几次漏 cherry-pick 某个提交的折腾)后,hotfix/boss 分支成功上线,我们又回到了只有 develop 和 release 两个分支的时候。这时又有一个问题:完成了本迭代即将进入测试阶段的 develop 分支,应该与 release 分支如何联动?小伙伴们讨论了几种方案:

  • 每次进入测试阶段前,将 develop 分支 merge 到 release 分支,作为新一轮发布分支的起点。后续在 release 分支上修复 bug,并同时 cherry-pick / merge 回 develop 分支
  • 拉出多个 release 分支。这个好处是,以前的 release 分支你就不用管了,减少了 cherry-pick 的复杂性
  • 基于主干的开发(Trunk-based Development, TBD)。祁兮提到了这个方案,优点是持续集成

最后结果是,由于大家已经经历了多分支带来的复杂交互和复杂追踪的痛苦,对于这个增加分支的方案持比较消极的态度,先否决了再说,因此第二个方案扑街了;TBD 的方案,由于同时需要涉及 feature toggle 设施建设、团队习惯改造和能力建设等,因成本较高的因素亦被否决了。现在来看,这两个方案其实各有其优点,但考虑到当时团队所拥有的资源及所能承受的成本,第一种方案确是成本看起来较低的方式。因此,团队选择了第一种单发布分支的方案:在每个迭代开发结束、测试开始前将 develop 上代码合并回 release 分支,作为下一次 release 的 candidate。这个方案的选择无可厚非,但亦偏偏正是本文所要讲痛点的来源。

不论如何,经过了上面这么多讨论,无疑我们看到了分多个分支进行开发和发布的复杂度。那么,最好的方案是不是直接跳到 单分支开发(trunk-based development) 即可呢?回答可能是反直觉的:我觉得并不是。

痛点

解决方案及分析

真正的痛点

YY:解决方案与实施次序

大道至简

总结起来,一个理想的项目,应该是将 git 做持续集成的开发工具来用,而不是将其用作发布流程的管理器。其核心为持续集成工具,而非管理/流程工具。我认为在开发和部署的流程上,这样配置是最好的:

  • 开发:单分支开发。这样保证了最基本却又很难做到的持续集成,配合其他恰当的实践可以增强团队对变化的快速响应能力
  • 部署:多发布分支。这样可以避免维护发布分支与开发分支之间复杂的联系,降低流程复杂度,提高工作幸福感
          • -- - - - 我是可爱的分割线 - - - - -- - - - - - -- -

草稿

  • 梳理一下这些合并代码的痛苦、不能重构、不能持续集成的痛点总结一下。它其实是两个问题:
    • release-develop 模型下的合并流程比较复杂的问题
    • feature branch v.s. trunk based 多分支还是单分支的问题
  • 当前的合并流程复杂点在于:每月一次合并周期较长,可能冲突,只能人工修复,又需要上下文,决策依据和现场较难恢复,出错风险中等,且冲突根本来源无法消除。针对这个问题,大家提出来过的解决方案大概有三种:
    • 多条 release 分支。这样每次的 release 分支是从 develop 上拉出来的,不存在冲突问题;修复 bug 在 release 上修,然后合(merge/cherry-pick)回 develop,也基本不存在冲突。就解决这个问题的话,这个是目前而言成本最小的方案
    • 频繁的 merge,比如我一周 merge 一次。这个思路下,冲突来源还是在的,冲突也还是会发生的,只不过通过频繁做,降低了每次的冲突量级和修复风险。不失为一种思路,但感觉还不够彻底
    • 单分支方案,所有在 develop 上的代码随时都可以发布。单从解决「合并流程复杂、风险高」这个问题而言,它所需要的成本(feature toggle 的成本、团队原子提交的习惯和能力)可能不及它解决问题的收益,但它带来的一个更为深远的影响为:持续集成
  • 单分支还是多分支的问题。多分支有诸多缺点,目前对于团队的影响分别如下:
    • 推迟了集成频率、增加了修复冲突的风险和成本(目前影响不大,合并无大影响)
    • 不同分支间发生了语义层面的重构(如重命名)时,合并风险高(目前影响不大,因为团队少 & 不敢进行重构)
    • VCS 被用作管理发布的工具,而非持续将每个人每天的代码集成到主干上,无法持续集成,导致回归时 bug 变多,对团队有无法预估的回归 & 集成风险(影响最大,每次回归都是问题重重)
    • 要使用 trunk-based 版本管理模型,对团队有了更高的要求:
      • 保证每个提交的原子性,即保证提交的 lint 通过、单元测试通过、快照测试通过(可以折衷,仅要求 push 粒度的原子性)
      • 需要实现一套 feature toggle 设施,它是有成本的,并且正确性也很难完美保证,也可能带来成本
      • toggle 设施的引入对于性能、bundle size 会不会有影响?
  • 无法/不敢/倾向于少做重构,又引发出一个重要的问题:自动化测试不够。它导致两个后果:
    • 团队无法持续优化:既然重构有时会引发 bug 并且必须回归的时候才能发现,成本太高,久之大家会对「重构」这个事情有负面情绪,并倾向于少去做,QA 也会更保守,PO 会质疑其价值
    • 手动回归成本太高:这又反过来阻碍团队进行重构。此外,手动回归成本太高,一定程度上也影响了持续集成的效果。另外,回归一直出问题会占用团队成员的精力,让团队无暇思考并去解决这些存在着的问题
  • 所以总结起来,目前团队在 自动化测试持续集成 的实践还不够好,并且这两个事情会互相影响。见上条。解决方案是什么呢?我们来 YY 一下:
    • 代码合并的痛苦:可以通过多发布分支 或 单主干分支的方案来解决
    • 先做持续集成:需要单分支开发、原子提交、feature toggle 设施等的支撑,并且只能保证主干代码的持续集成,产品质量和重构氛围由于「自动化测试」的限制仍然无法期望有明显的改善
    • 先做自动化测试:需要多平台支持、可能需要代码库进行改善、只能自动化部分回归场景,但可能改善重构氛围,减少回归测试的成本,使 bug 收敛,进而对产品质量的提高形成保障,带来直接的士气提升
  • 所以再说一下解决方案和解决次序,应该是这样:
    • 使用 多发布分支 来快速解决代码合并痛苦的问题,为后面重点问题解决留出精力
    • 次做 自动化测试。因为第一它对产品质量会有显著提升和感受,能减少 bug 数量,减少回归人力投入,提升回归信心,对于提升团队追求技术卓越的士气有直接作用,可为后面的问题解决提供背书
    • 最后推 trunk-based-development 和持续集成。因为这个事情需要投入成本,并且没有太可视化的指标,没有前面的背书可能难以说服团队
@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Jul 10, 2018

上面是之前尝试回答如何在组里铺开敏捷实践的问题。技术面分析还行,但现在来看,对项目属性和人事结构还少点视角。话归主题,主干开发 vs 分支开发,总结一下,用哪个,要点在于:

  • 权限限制。有没有向仓库主分支的合并权?比如开源项目 fork + PR 模型,那肯定是要分支的
  • 发布频率。为啥不是集成频率而是发布频率呢?发布频率不高的话,主干开发就可以不是刚需
  • 重构频率。特性分支的一个主要问题是进行语意重构时,冲突合并的困难。但如果你的团队连重构这种持续改进的行为都不敢做,或者要专门停下等所有分支合并完再做,那主干开发就可以不是刚需
  • 团队能力。不会原子提交,不会单元测试,不会质量内建,不会重构,不想持续改进

用不会不想不能的模型来套呢,「团队能力」是不会的问题,最好解决;「权限限制」、「发布频率」是不能的问题,客观限制,但权限这个事情也是可以争取的,而发布频率这个事情往往与组织结构有关系;「重构频率」则又是另外一个问题。不频繁重构可能有多方面的问题,有不想的,也有不太能的,比如没有自动化测试设施配套,比如不会重构,比如做的每件事没卡的话没法由测试部门同事验收。不管如何,这一点都指向更广阔的其他问题。

有同学提过提交历史的问题。经实践证明,git 提交历史在遇到问题时用来 debug、提供信息的场景,比你没事回去看一个「完整」的提交更频繁。Git 及其历史的精髓在于持续集成版本管理(用于快速 revert 或 bisect),而不在追求提交的「完美」或「高质量」。持续集成、版本管理在敏捷持续快速迭代的上下文中是重要的,但很多时候我们往往因为环境本身不够敏捷而看不到这一点的价值。

反过来说,分支一定不好么,当然不是。调试性的、spike 性质的、基础设施类型的,完全可以用分支。完全可以定期同步主分支以持续集成自己的分支。分支上也完全可以小步提交。只不过对于日常大部分的业务开发,它可以更容易做到原子提交(即这个 commit 上去不会破坏构建和应用功能),那当然推荐直接向主干提以获得频繁集成的好处。

好自为之,不再多说。只希望下个 team 能用上主干开发。

@JimmyLv
Copy link

JimmyLv commented Jul 10, 2018

使用 Branch 隔离代码的时机

嗯呀,基于主干开发,又没说基于主干发布。发布的时候有分支我觉得没毛病,这才是需要隔离代码的正确时机。

哪怕 feature branch,讲的也只是开发时期的事情。既然谈到隔离代码的时机,潜台词就是branch有其适用的场景和不适用的场景,分开讨论。

Feature 大小与拉分支的成本

feature的需求范围,负责的人与团队,需要有一个尺寸大小的讨论:

  1. feature很小,merge回主干的周期就短 -> 拉分支成本>吼一声的成本,也就没有必要另开分支。
  2. feature很大,merge回主干的周期很长,甚至跨团队 -> 沟通成本>拉分支并merge修conflict的成本,那就果断拉分支。

短期视野 vs 长期视野

当然,拉完分支的时候很爽很干净很安全,随便我自己怎么玩(重构) -> 短期视野

  1. merge回主干的时候,第一次就尼玛这么多conflict,我的天我再也不拉分支了妈妈我要回家。
  2. 第二次还是好了伤疤忘了疼,但是为了merge的时候没有那么多conflict,卧槽我的天我再也不重构了!

所以吧,综上我觉得重构很重要,持续改进的意识不能少。 -> 长期视野

但往往就是因为长期视野,跟拉分支时的短期视野是相矛盾的,人往往会只图眼前利益只顾一时爽,也需要吃过亏才知道错在哪儿。

尽快跑起来,持续改进

所以现在看来,你们团队走过的路已经是最好的状态,爽也爽了,摔也摔了,而且还会反思回顾做持续改进。

要知道,在没摔之前,果断跑起来是最明智的选择,最快时间build,最快时间measure,最快时间learn。万一呢,小孩子有天分,运气好,第一次下地就跑起来了。😂😂

小孩子走路可能有点儿不恰当的比喻,换个例子。想起来我第一次骑自行车,从来没学过一拿把手居然就骑出去了,哈哈哈哈。

回顾一些互联网产品,可能QQ和Facebook最开始也只不过是运气好而已吧。

拉分支跟「build-measure-learn」周期的关系

跑题了跑题了,所以回到feature这个点上来,关键就在于如何缩短「build-measure-learn」的周期。

为什么会拉分支,为什么会merge的周期那么长,导致持续集成做不到,可能都是因为feature的尺寸太大了吧。

每个dev拿到一张feature卡,不拉分支他都没信心做下去,或者说拉分支让他感觉更安全,哪怕build-measure的周期长点也无妨。

缩短「build-measure-learn」周期的必要性

但是,「build-measure-learn」的周期长了之后,就很可能缺了learn啊!

时间一长人又没长记性,只顾着解决conflict去了,还在乎个毛线learn,祈求能保证在deadline之前修好就不错了。

所以,feature小一点,不用拉分支,或者拉分支merge回的周期短一点。

  • 做到每个feature都可以build-measure-learn闭环,
  • 做到每个commit都可以build-measure-learn就更好了。

精益思想与 TDD 的结合

如何做到每个commit都能build-measure-learn,我想你肯定知道答案。

TDD啊!整个TDD cycle都是build-measure-learn,每个测试用例都是define需求,快速build,measure坏味道,learn并重构!

@EthanLin-TWer
Copy link
Owner Author

谈事情之前,先统一一下如何衡量谈论主题本身——于是「Build - Measure - Learn」这个视角我觉得是很合适的。

今天翻文章的时候翻到有人说,feature branch is for isolating changes, not hiding changes。意思就说,好处是隔离开发,而用坏的同学往往以为是为了推迟集成。可以说,learn 这个事情,就算是用了 feature branch,也是可以在 code review 的时候获得反馈的。区别只是,大家只是知道这个事情,想基于你的分支代码来开发重构也是不可能,只能默默祈祷两三天后的合并一切顺利。so,分支最大的问题,还是推迟了集成。另一个问题就是你说的,重构冲突啊。冲突死你啊。

嗯,然后讲到集成呢,减少 feature 的尺寸确实是一种方案。只要周期短,分不分支无所谓;就算不拉分支,commit 个两三天不 push 又跟拉分支有什么区别呢😂提高 commit/push 频率也是一种解决方案。最终都是促进了小步快跑、持续集成、频繁迭代、持续改进的团队氛围。

重构是另个更大的问题。

主要还是「Build - Measure - Learn」这个理念和衡量视角要安利出去。过去遇到的挑战是,讲快速、讲重构,同学说我们要稳不要快,我们不重构😂

核心在快!快速撸码、快速学习、快速反馈~

@JimmyLv
Copy link

JimmyLv commented Jul 10, 2018

jonnyschneider_diagram_artboard203_125ff10e0a26d24871eab89d01cd0477

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants