性能测试问题与思考

性能测试对于大部分测试人员都是一个神秘地带,因为在很多公司,性能测试都是由一个性能测试团队来做,所以普通测试人员没有机会接触到真实的性能测试,因而很难学习到很多新的测试实践知识。

市面上现在有非常多关于性能测试的书籍,其中不少书籍都能够系统地介绍性能测试。今天我想通过另一种方式来介绍性能测试,那就是通过提出一些关于性能测试的问题,然后针对问题进行思考。希望通过不一样的方式让读者以另外一种视角来思考和学习性能测试实践。

1、如何在敏捷开发中做性能测试?

敏捷开发,由于其持续集成、快速反馈等特点,需要性能测试工具支持轻量化、集成CI服务器、全代码化等特性,所以传统的性能测试工具比如JMeter和LoadRunner等已经很难适用于敏捷开发。在敏捷开发中,性能测试应该需要具有以下特点:

  • 性能测试是持续集成和持续发布的一部分,尽可能早的发现性能问题,从而降低修复成本。这样可以使得很多性能问题在开发过程中被持续的尽快发现。建议将性能测试写成故事放到每个迭代里面去,见下图:
  • 性能测试脚本易读,易维护。比如代码化的脚本GatlingLocust等。下面是Gatling的DSL示例代码:
  • 可视化,报表易读,每个人都能及时了解状况 。下图为Jenkins集成了一个Gatling插件后所展现的Gatling持续测试报表。

通过在敏捷开发中做持续的性能测试,使得性能测试也可以:小步快跑->快速反馈->持续改进->持续交付。

2、如何通过数据分析更有效的做性能测试?

大部分情况下大型项目的性能测试需要的时间和人力都非常高,因为:

  • 用户并发要求高,测试硬件成本高
  • 测试功能点多,测试量大
  • 耐久性测试,测试时间长

其次是测试的有效性差,因为测试人员在测试环境中很难模拟真实用户的操作,比如访问的顺序分布,访问的强度分布以及用户端的各种访问参数。

为了解决这两个问题,应该通过用户数据分析来获得真实的用户行为数据,并用其来构造性能测试用例,其中可以用下面这个漏斗模型来进行思考:

通过这个漏斗模型可以知道,为了快速得到真实有效的性能测试数据模型,需要通过筛选并整合真实的用户数据,而并不是靠测试人员在实验室中想象出来的数据。

那如何进行用户行为分析呢?下面尝试用三个例子来进行说明。

第一个例子是一个用户注册流程,通过用户行为的数据分析可以得到每个功能点上用户的访问量。见下图:

图中展示了用户注册流程的6个步骤,分别是:

  1. Getting Started
  2. Account Information
  3. Personal Information
  4. Financial Information
  5. Review Information
  6. Thank You Page

其中第一个步骤有18606次访问,然后有71%的访问用户选择继续,但是只有13131(70.6%)到达了第二个步骤。最终只有925(5%)的用户完成了注册。由此可以知道不同步骤的真实访问比例,从而得到性能测试的数据模型和策略。

第二个例子是用户使用的浏览器的数据统计,如果性能测试需要模拟不同的浏览器,那么这些数据分析结果也可以用以确定浏览器在性能测试中的权重。

通过这个统计表可以知道使用IE浏览器的用户最多,所以在生成性能测试数据的时候应该多生成一些基于IE浏览器的数据,比如User-Agent等。

第三个例子是统计的用户访问地区,对于有些大型的互联网应用是需要进行多地区模拟来测试不同地区互相访问时的性能。这个数据统计结果可以帮助其设计更有效的这类性能测试用例。

通过这个统计表可以知道英国的用户访问量最多,而美国的访问量第二。如果应用服务器部署在美国,那么就应该尽可能的在英国架设测试服务器,通过在英国的测试服务器来测试美国的应用服务器,从而测试跨国网络的性能,并且还需要在产品环境检测英国到美国的网络性能,从而及时发现性能问题。

通过对这些真实用户数据的分析,可以设计更有效的性能测试模板,下面是一个性能测试模板的样例,其中每个功能点上圆圈中的数字代表这个功能点在真实环境中的用户访问权重。权重越大的功能点在整个性能测试模板中应该测试量更大,所花费的成本更高。

常见的Web系统用户数据跟踪与分析工具有:

  • Adobe SiteCatalyst:功能强大,使用繁琐,收费(贵)
  • Google Analytics: 功能较强,使用方便,免费和收费
  • 百度统计/腾讯分析:功能一般,免费

使用数据分析来生成数据模型的流程如下:收集数据->分析数据->生成测试数据模板->使用数据模板。

3、对大规模集群系统做性能测试应该注意什么?

通常中小型公司的IT系统都不使用大规模的集群,而只有大型公司才会大规模使用集群,导致很多测试人员没有机会实践和了解基于集群的性能测试。如果想学习基于集群的性能测试,除了常规的测试集群系统性能以外,还可以从以下几个方面进行思考,从而学习基于集群系统的性能测试:

  • 测试环境的真实性,由于大规模集群包含很多节点和服务,所以搭建和产品环境一样的测试环境的成本很高,导致测试环境的规模一般都比产品环境小很多。那测试这样的测试环境还有什么意义?
  • 一般集群都会使用负载均衡,但是由于存在多种负载均衡算法和配置,那么怎么才能保证负载均衡功能是按照设计和配置的进行工作?
  • 除了对集群系统进行整体的性能测试外,还需要单独对不同的服务和节点进行性能测试吗?

所以针对集群系统做性能测试不仅仅能测试系统的性能,还可以解决以上三个问题:性能规划,配置测试,隔离测试。

性能规划

对于一个大型的服务器应用系统,一般情况下都是由规模化的集群组建而成的。所以测试这类基于集群的服务器系统的时候,也需要将测试环境配置成和产品环境类似的集群系统。不过因为成本的原因,测试环境中的集群规模大都要小很多。可以通过测试小规模的集群,然后使用其测试结果,并通过数学建模推算产品环境的性能或者对产品环境进行性能规划。由于每个集群系统拥有各自不同的架构,配置和服务,所以其数学模型也是不同的。

配置测试

通过更改集群系统的各种配置,并在不同的配置下对集群系统进行性能测试,从而获得最优配置。比如辅助开发人员完成集群功能的开发与验证,比如负载均衡算法,热备等;以及辅助运维团队配置和调试产品环境的集群配置等。

隔离测试

对于集群系统的服务或者节点,开发这些服务的团队应该在隔离(stub)第三方依赖的环境下,各自对自己团队开发的服务进行独立的性能测试。从而尽早发现性能问题,尽早修复,避免在集群环境下发现同样的问题,增加调试和时间成本。

大规模集群系统基本都是复杂架构,环境也都是较为复杂的组织结构,而只有深入理解整个业务流程,系统架构以及环境结构才能有效地设计测试方案。

4、性能测试中的测试数据有几种类型?

测试数据一直是软件测试中的一个头疼的问题,特别是在性能测试中测试数据尤为重要,因为越真实的数据越能获得更好的结果。对于测试数据的类型可以分为以下四种:单一型,随机型,模板型,真实型。

单一型

它是通过录制或者观察,使用一个或者一类单一的测试数据来进行性能测试。这种数据的构建简单,但是数据过于单一,无法模拟真实用户。由于其数据构建简单,所以可以用于敏捷开发中的早期性能测试。

随机型

它是通过一些简单的数据规则,并结合随机算法生成的测试数据。这种数据和单一型比较,虽然增加了随机性,但是仍然缺乏真实性,并且其构建成本和性能问题的分析成本也相对较高。它可以用于上线前的大规模的多样化的综合性能测试。

模板型

它主要是通过数据分析并生成模板来构建测试数据。虽然它较随机型在一定程度上增加了用户真实性,但是准备数据的成本很高。在项目成本和资源允许范围内,可以结合模板型和随机型的方法,从而更为有效的进行性能测试。

真实型

它是通过直接导出或者重定向产品数据来做性能测试数据。它完全是真实的用户数据,构建成本较低,但是存在数据安全性的问题,比如数据泄露。在数据安全性可以得到有效保护的情况下是可以使用真实型数据来进行性能测试。

测试数据生成和管理对于一个大型项目的性能测试是十分重要的,所以如何高效的生成有效的测试数据成为了首要的任务。通过这四种测试数据类型,可以快速的判断在项目当前阶段适合使用那种类型的数据,从而避免一些弯路。

5. 其他问题

除了以上问题和思考,我还准备了一些其他的基本问题给读者自己去思考,从而通过思考问题来学习性能测试。

  • 性能测试主要包含哪些类型以及分别的作用是什么?
  • 前台页面的性能测试应该注意哪些问题?
  • 对于并发用户很少但是稳定性要求很高的系统需不需要做性能测试?为什么?
  • 对于后台有大量异步批处理需求的系统应该怎样进行性能测试?
  • Profiling是不是性能测试?什么时候应该做它?
  • 常见的性能测试工具有哪些?怎么选择性能测试工具?
  • 如果测试环境和产品环境的硬件配置不同,如何通过测试环境的测试结果评估产品环境的性能?
  • 在设计性能测试用例时需不需要考虑Think Time?
  • 中小型项目的性能测试都需要注意些什么?
  • 性能需求的来源有哪些?
  • 如何使用云服务进行超大规模性能测试?

更多精彩洞见,请关注微信公众号:思特沃克

Share

移动应用的测试策略与测试架构

今天我们来谈谈移动测试的测试策略与测试架构。

首先我们将移动应用的范围限定在智能移动操作系统(比如Android、iOS、WinPhone等)上,包括手机应用,智能设备应用等。

智能手机和智能设备的普及需要大量的应用来支撑。随着应用数量的增多,业务复杂度的提高,移动应用也越来越需要各种测试来保证应用以及设备本身的正确和稳定运行。因此移动应用测试的需求也越来越大,大量关于移动应用测试的书籍应运而生,比如《Android移动性能实战》《腾讯iOS测试实践》《移动APP性能评测与优化》《深入理解Android自动化测试》《精通移动App测试实战:技术、工具和案例》等。

这些书都介绍了大量的移动应用测试实践,但是无论看多少本书,学习多少种测试方法、测试技术或者测试工具和框架,首先还是需要学习并使用测试策略与测试架构。如果没有在一开始制定好的测试策略和测试架构,而是盲目进行各种测试,很有可能事倍功半。

对于移动应用,首先它本质上也是软件系统,所以通用的软件测试方法技术都可以使用。其次它又拥有嵌入式的特征,比如开发需要交叉编译、需要远程调试、硬件资源相对不足等。所以移动应用的测试也有其特殊之处,比如也需要交叉编译、远程测试以及各种硬件相关测试等。对应的移动应用的测试策略和测试架构也有其特殊性之处。

制定测试策略

我将移动测试分为三种类型,分别是基础测试、进阶测试和产品测试,其中基础测试是产品能正确并快速交付的基本保障,扩展测试主要是为了增强软件系统的健壮性,而产品测试主要是通过产品角度以及用户角度去思考而进行的测试。下面分别列举了常见的三种类型测试。

基础测试

  • 功能测试 (Function Test)[1] 。
  • 集成测试(Integration Test )
  • 单元测试(Unit Test)
  • 契约测试(Contract Test)[2]

进阶测试

  • 兼容测试(Compatibility Test)
  • UI视觉测试(UI Visual Test)
  • 性能轮廓(Profiling)
  • 安全测试(Security Test)
  • 异常测试(Exception Test)[3]
  • 猴子测试(Monkey Test)
  • 安装、升级和卸载测试(Install、Upgrade and Uninstall Test)
  • 耐久测试(Endurance Test)
  • 耗电测试(Power Consumption Test)
  • 流量测试(Network Traffic Test)
  • 其他硬件功能专项测试[4]

产品测试

  • 易用性测试(Usability Test)
  • A/B测试(A/B Test)
  • 产品在线测试(Product Verification Test or Product Online Test)
  • 用户测试(Customer Test)[5]

对于一个中小型项目来讲,很多时候资源都是十分有限的,很难做到全面类型的测试,大型项目更是如此,更难有足够多的资源做所有类型的测试。而且可能还由于团队人员的技术能力不足,或者所拥有的测试相关的技术栈的局限,以及开发测试环境和软件系统架构的限制,有些类型的测试是无法进行的。

所以,制定测试策略的关键点在于根据质量需求的优先级,并参考团队的各种限制来指定。

首先通过和PO、PM等进行讨论得到产品质量需求的优先级,然后根据优先级指定相应类型的测试。再根据团队的资源、项目周期、技术能力以及各种限制来制定相应的测试方法和测试技术,其中包括使用自动化测试还是手动测试、使用什么测试工具和测试框架、测试的范围和程度等。

下表是一个典型手机应用的测试策略表的样例(这个只是一个模拟项目的样表,真实项目中的各类信息应该更多,并且可以根据具体情况添加新列。并且注意,这些测试并不一定由测试人员或者QA来做,应该由整个团队一起协作完成):

表中的质量需求优先级的获取是一个比较繁琐的过程,需要和各个利益相关者一起讨论并且协商获得。

根据这个测试优先级表,就知道应该把资源优先投入到高优先级的测试中。等高优先级的测试做到团队可以接受的程度后,再按照优先级做下一个类型的测试。这个表中的优先级在开发过程中不是绝对不变的。如果PO、PM等利益相关者对于产品质量需求的优先级发生了改变,在得到团队同意后,还需要改变这个表中的测试优先级。所以需要经常与团队更新测试进度,并及时获得团队各个角色对于测试和产品质量需求的反馈与更新。

其次可以根据测试金字塔等模型来思考不同类型测试之间的关系和工作量,但是很多情况下也可以不用参考这些测试模型,因为移动应用的复杂度一般不会特别高,并且当前大多数情况下,移动应用中复杂的业务逻辑都会尽量在服务器端进行处理,所以移动应用很多时候只是一个用户交互系统,所以应该尽可能的完成会影响用户使用的E2E流程测试,然后再继续做其他类型的测试。

但是对于在移动应用中实现复杂业务的项目,测试策略还是应该尽量思考测试类型之间测试用例重复的问题,尽量避免重复的用例,降低测试成本。

制定测试架构

通过测试优先级表,我们获得了简易版的测试策略,然后就应该制定测试架构了。由于嵌入式软件的特殊性,其测试架构也与常规的桌面系统和服务器系统有一定的区别。下图为针对上面样列测试策略相对应的功能测试架构:

图中只针对功能测试进行了进一步的详细架构设计,并没有对其他测试比如集成测试、兼容性测试和稳定测试等进行详细架构设计,感兴趣的读者可以根据自己项目的实际情况自己尝试一下。

通过这个架构图,可以比较系统以及直观的了解各种类型测试的分布、关系和测试系统的架构等。

然后配合测试优先级表就可以较好的指导团队进行有效的测试,比如制定更好的测试计划,制定更适合的自动化测试系统等。并且还可以更有效的评估产品质量,比如什么类型的测试没有做,那么那些特定方面就存在较高的风险。

不过任何软件系统都是存在缺陷和风险的,关键是看这些缺陷对于开发商和用户产生的影响有多大,风险是不是在可控范围内的。永远不要尝试去找到所有缺陷并消除,而是要从风险大小、影响程度等各方面综合考虑,增加团队对于产品质量的信心,并且不要对客户产生严重的大范围的影响。

注:

[1]. 后台常住应用测试也属于功能测试。

[2]. 单机应用可以不用考虑做契约测试。

[3]. 异常测试包括弱网测试,比如低速网络信号、网络时断时续,网络切换以及无网络等,突然断电等。

[4]. 其他硬件功能专项测试包括硬件功能关闭,硬件功能异常等。

[5]. 用户测试包括收集用户使用信息,并生成用户真实使用的测试用例来对系统进行测试。


更多精彩洞见,请关注微信公众号:思特沃克

Share

自动化测试框架分类与思考

自动化测试一直是敏捷开发和敏捷测试的重要基石,也是DevOps和CI/CD必不可少的组成部分。由于不同项目的测试需求不同,以及各种不同的限制,导致需要的自动化测试框架和工具也不同。比如很多金融和能源类的企业就倾向于选择收费的企业级自动化测试框架或者工具,而新型互联网企业则倾向于开源免费的自动化测试框架或者工具,或者基于它们进行二次定制开发,或者重新开发适合自己的自动化测试框架、工具或者平台。

我之前写过一篇文章——《自动化测试框架Cucumber和RobotFramework的实战对比》仅仅针对两种自动化测试框架进行了讨论,却引发了大量的讨论,由此可见业界对于自动化测试框架存在很多不同的理解和争议。在我看来,没有任何一个自动化测试框架是银弹,并且适合所有类型的测试,所以“如何选择一款适合自己的测试框架”变成为了一个首要问题。我将自动化测试进行了简单的分层,见下图。

自动化测试架构分层图

其中测试库和被测系统紧密相关,所以可以选择的范围不是很大,也很难进行统一分类。而测试框架与被测系统关系并不紧密,而是和技术栈,开发流程与组织管理等关系紧密相关,并且种类繁多,可选择范围很多,所以选择也相对比较困难。

因此对测试框架进行统一分类可以更好的帮助团队选择适合自己的测试框架,从而更好进行自动化测试开发。

本文根据自动化测试用例的呈现方式和管理方式将其分为四种类型:函数型,单领域语言型,多领域语言型以及富文档型。

四个类型:

函数型

函数型自动化测试框架是第一代自动化测试框架,也是最轻量的测试框架。它只是通过函数的方式来定义测试用例,并且通过管理这些函数的调用来管理测试用例,从而快速的实现自动化测试,比如xUnit等。

例子JUnit:

public class DemoTest {
  @Test
  public void testAddWithTwoNumbers() {
    //测试实现代码
  }
}

函数型自动化测试框架由来已久,开发快速,运行稳定。虽然它相对简单与轻量,但是也存在缺点:很难通过函数名来描述测试用例的内容和细节,并且不方便对测试用例进行单独管理,因为测试用例的描述函数名和测试实现通常都在一起。

单领域语言型

由于函数型的自动化测试框架很难通过函数名去描述一个测试用例的内容。为了更清晰和容易的描述测试用例,就出现了单DSL型的自动化测试框架,比如RSpec,Jasmine,Mocha等。

例子Jasmine:

describe("The add function of the calculator can add two numbers", function() {
  it("should get the sum after add two numbers", function() {
    //测试实现代码
  });
});

单领域语言型可以通过自然语言或者关键字形式的领域语言来描述测试用例,从而以一种更加易读和理解的方式来描述测试用例。但是每个测试用例只用一句DSL语言,并不能很好的描述测试用例和被测场景,不易形成一套好的活文档。由于它的测试用例与测试实现通常也是在一起的,所以也不方便对测试用例进行单独管理。

多领域语言型

由于单DSL型框架中对于每个测试用例只能使用一句DSL来描述,并不能很好的体现测试用例场景,比如测试的前提,行为和结果等。为了能在测试用例层更为清晰的描述测试用例的行为和测试数据等型信息,出现了多领域语言型的自动化测试框架,比如Cucumber,JBehave,SpecFlow,RF等。

例子Cucumber:

测试用例代码

Feature: The add function of the calculator can add two numbers
  Scenario: add two numbers
    Given there are two numbers <Number 1> and <Number 2>
    When add these two numbers 
    Then should get <Sum> of two numbers
  Examples:
    | Number 1 | Number 2 | Sum |
    | 1        | 2        | 3   |
    | -1       | 2        | 1   |

测试实现代码

Given(/^there are two numbers$/) do
  //测试实现代码
end
When(/^add two numbers together$/) do
  //测试实现代码
end
Then(/^should get sum of two numbers$/) do
  //测试实现代码
end

多领域语言型的框架可以通过多句或者多个关键字的领域语言来描述一个特定的场景,使得测试用例更容易阅读和理解,并且比较容易做成一套活文档系统。由于测试用例和测试实现是分离的,还可以对测试用例进行独立管理。

但是缺点也是比较明显的,开发、管理和维护成本较高,并且如果没有业务分析或者产品人员等非技术人员参与协作开发,那么它的投入产出比就很低,大家往往会认为它是事倍功半。

富文档型

对于一些场景十分复杂,需要通过富文档的方式来描述软件测试场景,甚至需要一些业务流程图或者系统用户界面等,比如Concordion,Fitnesse,Guage等。

例子 Condordion:

测试用例代码,其中包含部分测试代码,比如断言等,其中concordion.css使用的是官方样例代码

<html xmlns:concordion="http://www.concordion.org/2007/concordion">
<link href="concordion.css" rel="stylesheet" type="text/css" />
  <body>
    <h1>Test Demo</h1>
    <p> Test the add function of  the calculator can add two numbers </p>
    <p> The Caculator:</p>
    <div> <img src="./Calculator.png"/> </div>
    <div concordion:example="add">
      <h3>Examples</h3>
      <table>
        <tr>
          <th>Number 1</th>
          <th>Number 2</th>
          <th>Sum</th>
        </tr>
        <tr concordion:execute="#sum = addWithTwoNumbers(#number1,number2)">
          <td concordion:set="#number1">1</td>
          <td concordion:set="#number2">2</td>
          <td concordion:assert-equals="#sum">3</td>
        </tr>
        <tr concordion:execute="#sum = addWithTwoNumbers(#number1,number2)">
          <td concordion:set="#number1">-1</td>
          <td concordion:set="#number2">2</td>
          <td concordion:assert-equals="#sum">1</td>
        </tr>
      </table>
    </div>
  </body>
</html>

测试用例呈现文档:

测试用例中的函数实现代码:

@RunWith(ConcordionRunner.class)
public class CaculatorFixture {
  public String addWithTwoNumbers(String number1, String number2) {
    //测试实现代码
  }
}

(注:虽然说最新版的Concordion已经支持MarkDown了,从而降低了一些开发成本,但是其对MarkDown的特性支持有待增加。所以如果需要更为丰富的文档形式,仍然需要使用HTML来开发测试用例。)

富文档型的框架比多领域语言型拥有更为丰富的文档,更容易阅读和理解,从而能做成说明书式的活文档,使得所有角色的人都能审阅。并且其测试用例和测试实现也是分离的。但是当前业界存在的富文档型测试框架的易用性和协作性都还不是很好,导致其开发,管理和维护成本相比前三种是最高的。并且当没有其它各个角色来协同开发,管理和维护时,其投入产出比也是最低的,所以它在行业中的使用率也是很低的。这类测试框架在易用性和协作性方面还有很大的发展空间,并且也是自动化测试框架和活文档系统的一个重要的发展方向。

思考与选择

自动化测试的代码实现层一般是与编程语言强相关的,而主流的编程语言比较少,所以选择比较容易:一般建议选择团队大部分成员都熟悉的编程语言(这样可以促使整个团队来对自动化测试进行开发和维护)或者是有特定测试库的编程语言(比如需要使用Scapy时就只能选择基于Python的自动化测试框架)。当确认自动化测试开发语言后,真正的问题是如何在如此众多的自动化测试框架里面选择合适自己的自动化测试框架。选择方法可以根据以上四种类型来进行选择,从而缩小选择范围。

  • 如果团队只是需要快速实现自动化测试,没有知识的传递问题,也不需要与业务分析和产品经历等非技术人员进行协作开发时,可以选择函数型自动化测试框架。
  • 如果为了解决知识传递问题,让测试用例更可读和易懂,并且没有非技术人员参与协作开发,这时可以选择单领域语言型。
  • 如果为了进一步解决和非技术人员协作开发的问题,并且想有一套简版的活文档,可以选择多领域语言型自动化测试框架。
  • 如果为了让测试用例拥有更为丰富的表现力,比如包含一个流程图来说明被测场景的流程,或者使用不同的格式或者表格来描述用例的细节,以及拥有一套丰富的活文档,这时就可以使用富文档型。不过由于当前的富文档型测试框架在编写用例时需要一定的技能,所以非技术人员很难直接参与协作编写。并且其编写以及维护成本更高,可能使得自动化测试开发人员使用的意愿也不是很高。参考自动化测试工具选项金字塔:

当确认了测试框架类型之后,比如只有一个可选项(Java->函数型->JUnit),那么就直接使用了,但是如果存在多选项(JavaScript-> 单领域语言型->Jasmine vs Mocha),就还需要对其进行深入比较,从而最终选择自己适合的自动化测试框架。

扩展阅读:


更多精彩洞见,请关注微信公众号:思特沃克

Share

单体中心代码库 vs. 分布式代码库

去年中旬两位Google工程师在《美国计算机学会通讯》发表了一篇论文“Why Google Stores Billions of Lines of Code in a Single Repository”,它介绍了谷歌为什么采用一个定制的大型单体中心代码库,并且在多个大会上分享了这个话题。InfoQ中文网站也发表了一篇较为客观的文章”Google为什么要把数十亿行代码放到一个库中?”来评论Google这种代码管理方法 ,其中总结了Google宣称的这种唯一中心库代码管理方式的优势,包括:

  • 统一版本控制
  • 广泛地代码共享和重用
  • 简化依赖管理,避免菱形依赖
  • 原子修改
  • 大规模重构
  • 跨团队协作
  • 灵活的团队边界和代码所有权
  • 代码可见性以及清晰的树形结构提供了隐含的团队命名空间

并且也总结了Google这种唯一中心库代码管理方式的一些问题,包括:

  • 工具投入(Google开发了自己专用的Eclipse ID插件)
  • 代码库复杂性(需要有依赖重构和代码清理辅助工具)
  • 代码健康(专用工具可以自动检测和删除无用代码、分派代码评审任务等)

更多的问题讨论参见这里

对于Google这样的大型团队或者公司,他们的代码管理看起来是简单的单体代码库管理方式,其实真正管理起来并不简单,甚至需要大量的额外投入来辅助管理,因为它是在各种前提和限制条件下的历史产物,其中最为重要的两点是:

  • 由于当前大部分的商业和开源代码管理工具或者系统在管理一个超过10亿个文件,20亿行代码的中心库时效率都十分低下,而且随时都有大量的代码同步(包括代码获取和提交)请求。所以为了在不影响程序员日常工作效率的前提下对海量代码进行高效管理,一般情况下这样的团队或者公司都会开发或者定制自己专用的代码管理工具和系统,比如Google开发的Piper,Facebook定制化的Mercurial和Microsoft定制化的Git系统GVFS等。
  • 大型公司一般是经过长时间的积累才有如此巨量的代码,并且都有自己特定的经历和原因,比如开发了大量定制化的外围辅助工具和系统,形成了特有的一套代码管理模型和流程。所以更换这种大型代码库的管理工具成本非常高,而且现实中很难找到一个代码管理系统能满足已有的管理和流程需求,所以一般情况下都不会更换。比如Google最开始使用Peforce来管理其单体中心代码库,后来发现它无法支持其巨大的代码量,所以开发了Piper用以管理中心库管理,并且其在代码健康上投入了大量的成本,比如开发了专用的工具来自动检测和删除无用代码、分派代码评审任务等。虽然Google也尝试过向Git进行迁移,最终由于文化和工作流程的巨大变更而放弃了,但是仍然对于一些新的实验性的或者一些开源的项目会尝试使用一些新的代码管理工具。

虽然说Google的大部分核心代码都是使用Piper在一个中心代码库进行管理和维护的,但是它仍然有不少开源项目,其中包括Android Open Source Project(2008)和Chromium(2014转向Git)这样的大型项目,或者创新的初始项目依然可以选择使用Git这样的开源代码管理工具进行代码管理,所以应该给予项目组足够的权利去选择适合自己项目的代码管理工具,从而让团队感受到足够的尊重和动力。

而世界范围内像Google和Microsoft等用有财力和物力去开发或者定制一款适合自己的专用代码管理及其周边辅助工具的公司是很少的,而绝大多数公司只适合通过购买商用,使用开源免费或者使用基于云的代码管理系统来管理自己的代码。

由于选择单体代码库还是分布式代码库直接影响了团队对于代码管理工具的选择和使用,所以一些正在快速增长或者需要转型的中小型公司就对代码管理方式和代码管理工具的选择产生了疑惑:是应该学习Google的核心代码库而继续使用单体代码库的管理方式,然后自己开发和定制化自有的代码管理工具,还是学习Linux,Android以及OpenStack等开源项目而转向分布式代码管理方式和免费的分布式代码管理工具,或者直接使用基于云端的代码管理系统等。

为此我总结了一个代码管理工具,选择四象限图用以帮助中小型公司选择代码管理方式和代码管理工具:

其中资源主要是指钱和人力资源,而技术是指项目组或者公司里面的大部分工程师的技术能力。

通过这个四象限图,中小型公司就可以通过另外一个角度去思考和判断自己应该选用什么样的代码管理方式和代码管理工具。而对于大型软件公司,比如类似于Google,Facebook,Microsoft等这样规模的公司就不适合用这个四象限模型,而是需要根据自身具体的情况而自己开发或者定制的代码管理工具,可以是中心服务器式,也可以是分布式,无论什么形式,只要适合自己的实际情况就可以了。


更多精彩洞见,请关注微信公众号:思特沃克

Share

大型分布式团队的代码版本管理

介绍这个话题,有两个原因:

  1. 从开始工作到现在,我经历过没有代码版本管理、代码集中式管理,以及现在的分布式管理,我深刻体会到它在软件开发过程中的重要性;
  2. 我在工作中遇到的很多客户都存在对于代码版本管理的各种问题、困惑和不同的需求。

所以我希望将我在这个方面的经验分享给更多人,希望能帮助更多的团队解决在代码版本控制方面的问题和疑惑。

(图片来自:http://t.cn/RSPnA5t)

一、代码版本管理系统的历史

代码版本管理系统大致可以分为三个时代:

第一代:本地式

这代主要的特点提供本地代码版本控制,比如SCCS(1972)、 PVCS(1985)等。

这代主要实现了基本的代码版本管理,但缺点是无法让多人同时对一个版本库进行修改。这个也和当时软件规模不够大有关,也没有这样的需求。

第二代:客户端-服务器式

这代主要的特点是提供集中式服务器端代码版本控制,比如 CVS(1986), ClearCase(1992), Visual SourceSafe(1994), Perforce(1995), Subversion(2000) 等。

这代主要是实现了中心服务器端的代码版本管理,特点是可以让多人同时对一个代码版本库进行同步和修改,但缺点也相当明显:

  1. 在无法连接服务器的情况下,无法查看日志以及提交和比较代码版本(慢速网络和远程异地工作的程序员的痛),以及当服务或者网络出现问题的时候很多人员就会无法工作。
  2. 不支持local branch,导致branch创建管理复杂,并且一旦创建就很难修改(快速迭代开发中的程序员的痛)
  3. 由于只有一个中心端服务器,一旦发生灾难性问题,那么所有日志都会丢失,所以需要经常做备份(备份需要不小的成本)
  4. 如果软件代码量过于庞大,一般会出现速度缓慢的情况,因为每次的日志查询、不同版本之间的代码比较和代码提交等操作都需要和服务器通信,造成服务器端的负载过大。

第三代:分布式

这代主要的特点是提供分布式代码版本控制,比如Git(2005), Mercurial(2005)等。

这代结合了第一代和第二代的优点并实现了分布式的代码版本管理。

这代的优点:分布式管理,在没有和服务器有连接的情况下仍然可以查看日志,提交代码,创建分支;支持local branch,可以快速方便的实现各种分支管理;支持分布式,从而可以实现分块管理,以及负载分流管理。

缺点是有一定的学习曲线,比如分布方式下的代码同步,local branch的理解与运用,分布式代码管理的理解与运用等。详细的比较可以参考:这里

二、大型分布式团队

曾经有这样一个分布式团队,他们在多个城市都有小分队,并且正在开发一个大型项目,见下图

他们使用的代码版本管理工具是第二代代码管理工具SVN,管理方案如下:

但是他们在使用的过程中却遇到了下面这些问题与痛点。

由于是分布式团队,所以:

  • 基于团队的代码模块分离困难

当服务器不可用时:

  • 不能查看提交记录
  • 不能比较文件
  • 不能提交代码

创建代码分支时:

  • 分支创建速度慢
  • 多分支管理困难

在提交代码时:

  • 希望有Code Review
  • 希望有CI Review

因为代码庞大:

  • 查看日志慢

备份代码库的时候:

  • 需要停机备份
  • 备份成本高

针对以上问题,可以使用新一代的分布式的代码版本管理系统来解决,见下图:

其中每一个团队都有自己独立的代码库,有一个中心库用于同步这些独立的代码库,并且每个库都由团队自己管理和维护。而且代码版本管理系统需要支持轻量分支,代码评审,离线提交,离线查看日志等功能。

但是由于当前没有一个单一的代码版本管理工具能同时满足以上所有需求,所以很多公司都基于它们开发集成管理系统,比如Gerrit,GitLab,GitHub,BitBucket等。其中的Gerrit由于其开源,免费,以及由Google开发和维护,并管理着Android,OpenStack等大型项目源代码的特点,成为了大型分布式团队优先选择的系统。

三、Gerrit

Gerrit是由Google开发的,用于管理Google Android项目源代码的一个系统。它是基于Java和Prolog等开发的,支持Git,权限管理,代码评审等综合的一个管理系统。它与GitLab和GitHub最大的不同是它隐藏了代码分库管理的细节,使得开发人员不需要进行fork这样的手工分库和同步操作就可以进行代码开发和提交,节省了开发人员的时间,见下图。

由于Android本身是一个开源项目,所以贡献者非常多,开发团队也遍布多个地方(存在时差),导致“如何保证代码质量”成为一个很大的问题。为此Google在Gerrit中加入了功能强大并且十分严格的代码评审系统。

首先当代码提交以后并不会直接merge到中心库里面,它会暂时存在一个临时库里面,同时生成一个代码评审记录,并向特定的评审人员发送请求评审的邮件。当评审者在评审代码之后,如果通过就需要在Gerrit系统里面对代码进行打分,如果通过了就可以将代码merge到中心库里面去,如果没有通过,那么这个代码提交就需要被返还给开发者进行修改。

与此同时它还可以自动触发一次包含本次代码提交的CI构建(前提需要手工预先配置),如果CI自动构建和测试通过,也可以自动在Gerrit系统里面进行打分,可以给最终进行merge的人员进行参考。示意流程见下图。

由于Android源代码由上百个独立的代码库组成,并且编译一个Android系统需要大部分代码库里面的代码,所以如何管理如此多的代码库也是一个难题,比如如何一次性同步需要编译一个需要支持特定设备的代码库组合。为此Google基于Python语言开发一个工具叫Repo ,这个工具可以自定义你需要的代码库的组合,并且一次性对这些代码库进行同步,比如pull和push,见下图。

四、SVN到Git的迁移

对于想从集中式代码管理系统迁移到分布式代码管理系统的团队来讲,如果团队规模小,那么问题一般都不大,但是对于大型分布式团队却是困难重重。最主要的两个困难:

  1. 代码量太大,很难一次性将所有的代码和日志等在短时间内迁移成功。
  2. 由于下属团队太多,很难同一时间让所有团队都切换至新的代码管理工具。

为了解决这些难题,一般都会首先选用1个团队来使用新的代码版本管理工具。如果这个团队转换成功,再将其作为标杆向其他团队推广,从而逐步的将所有团队切换到新的工具上去。

SVN到Git的迁移方案一般主要会使用两种工具:

  1. 开源免费的git-svn;
  2. 商业收费的Subgit。

其中使用Subgit的迁移方案如下图:

如果团队组资源充足,还可以使用Gerrit搭建一个独立的Git服务器,从而以分布式的方式进行代码迁移,如下图:

五、多产品线的管理

使用同一个中心代码库管理多产品线一直是大型项目的一个困难点,特别是使用SVN这样的工具更是难以管理,因为SVN这种工具的Branch本质上是一个目录拷贝,并且速度慢,而且代码回迁也需要手动进行。但是如果使用Git的特性来管理多产品线,比起SVN是事半功倍。具体方案见下图:

总结:

分布式代码版本管理系统并不一定适合所有团队,比如中小团队可能更关心的只是成本更低,简单易用,那么SVN等这类集中式版本管理工具还是更为适合。但是不管团队最终选用什么代码版本管理工具,只要适合自己的团队的开发流程和工作方式,并且代码管理顺畅就可以了。


更多精彩洞见,请关注微信公众号:思特沃克

Share