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

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

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

首先我们将移动应用的范围限定在智能移动操作系统(比如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的实战对比》仅仅针对两种自动化测试框架进行了讨论,却引发了大量的讨论,由此可见业界对于自动化测试框架存在很多不同的理解和争议。在我看来,没有任何一个自动化测试框架是银弹,并且适合所有类型的测试,所以“如何选择一款适合自己的测试框架”变成为了一个首要问题。

自动化测试一直是敏捷开发和敏捷测试的重要基石,也是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的核心代码库而继续使用单体代码库的管理方式,然后自己开发和定制化自有的代码管理工具,还是学习Linux,Android以及OpenStack等开源项目而转向分布式代码管理方式和免费的分布式代码管理工具,或者直接使用基于云端的代码管理系统等。

去年中旬两位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、我在工作中遇到很多客户都存在对于代码版本管理的各种问题,困惑和不同的需求。
所以我希望将我在这个方面的经验分享给更多人,希望能帮助更多的团队解决在代码版本控制方面的问题和疑惑。

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

  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

让我们再聊聊TDD 续 – 人人都在做TDD

在上一篇文章里面,通过对DHH的文章以及DHH和Kent Beck等讨论的分析,我阐述了对TDD的理解和分类,现在来继续聊聊TDD的实施和分层。
现在还有非常多的软件工程师在质疑TDD的可行性,比如太难不会、成本太高无法推动、意义不是很大等,但是他们却一直都在做着TDD,只不过没有意识到而已,这便是“不识庐山真面目,只缘身在此山中”。

上一篇文章里面,通过对DHH的文章以及DHH和Kent Beck等讨论的分析,我阐述了对TDD的理解和分类,现在来继续聊聊TDD的实施和分层。

现在还有非常多的软件工程师在质疑TDD的可行性,比如太难不会、成本太高无法推动、意义不是很大等,但是他们却一直都在做着TDD,只不过没有意识到而已,这便是“不识庐山真面目,只缘身在此山中”。

TDD的实施一般分为思维层面和技术层面。一般来说,思维层面上的实施成本较低、容易接受,但是缺点很多,比如难以传递、难以持续获得快速反馈等;而技术层面上的实施一般成本较高、不容易被人接受,但是优点更多,比如可以获得快速反馈、更容易传递和协作等。而现实世界中TDD的实施一般分为三个阶段,即无意识的TDD、被动通过技术实现的TDD、以及有意识和主动通过技术实现的TDD。

第一阶段:无意识的TDD

对于软件开发人员,当他们拿到一个新的软件需求时,首先会思考如何实现,其中包括当前软件架构、业务分解、实现设计、代码分层、代码实现等。然后通过思考和设计所得到的产出物来驱动代码实现,进而在代码实现中会思考如何通过一个或多个函数或者算法来实现业务逻辑。所以软件系统的实现要先通过意识层面的思考,再进行技术层面的工作。

当开发人员思考和设计这些函数或者方法的时候,一般都会思考它们有哪些参数,然后想象将这些参数换成真实的数据后传递进去,会得到怎样的返回值。好一点的开发人员会思考如何处理异常输入和异常返回值。

这类思考其实已经是意识思维上的TDD,它帮助开发人员先在大脑里面设计并验证代码实现,甚至帮助其重构代码。所以很多开发人员都在无意识的情况下做着TDD。

比如在一个银行系统里面,开发人员拿到一个需求,需要开发一个通过手机APP转账的功能。

  1. 首先开发人员会基于当前的软件架构思考:是开发一个全新的模块来处理这个业务?还是基于当前架构中的某个模块来添加代码进行处理?
  2. 当确定架构和设计之后,就开始思考具体的代码实现,比如类的设计、方法的设计或者函数的设计等。当开发“将钱从原帐号转出”这个功能前,开发人员会思考:这个功能需要支持当钱从原帐号中转出成功后,原帐号中的余额等于原始余额减去转出金额。进一步有些程序员还会设计一些用来验证功能的实例,比如帐号中的原始余额是999.99,转出111.11,那么剩余的金额就应该是888.88。
  3. 在这样思考之后,开发人员便开始根据自己大脑中的测试逻辑和用例来驱动和辅助开发过程。在代码开发完毕之后还会想一些办法来验证一下所实现的功能是否符合预期,比如人工使用之前的或者新的测试用例再测试一下。如果验证正确,就会认为自己开发的功能正确了,并交给测试人员进行测试。

其实开发人员在开发前思考测试逻辑和用例的过程就是在做TDD了。

很多做业务分析的BA和测试分析前移的QA也同样在无意识的做着TDD(注:在前一篇文章中已说明TDD包含ATDD),比如分析验收条件、写出验收文档等。只不过这些AC和验收文档可能写得不是很明确或者不是很好,比如不是实例化需求等,但是本质上已经是TDD了。

只不过是初级的无意识的TDD,可能有的人做得好,有的人做得不好,而且没有明确的产出来协助和规范这个测试驱动开发方式,也缺乏快速反馈、度量、传递和协作等。因此从无意识到有意识将是做好TDD的一个重要过渡。

第二阶段:被动通过技术实现TDD

当有一部分软件工程师意识到了TDD的意义和普遍存在性之后,就开始准备解决思维上的TDD的缺点。而解决这些问题的方法就是在技术层面上用代码来实现TDD,用明确的代码来协助和规范开发人员的测试驱动开发行为,来度量他对业务逻辑以及代码实现的理解度。通过将他的理解传递给以后的维护人员,让他的理解能重复被使用,以及和其他人协作开发。

但是现实中很多开发人员的认识不足以及技术能力不够,就算管理层支持并且主动推动TDD,最终由于开发人员设计和选取的测试用例合理性很差,导致驱动出来的代码有效性差,测试用例无法体现出SBE(Specification by example)导致易读性差,对于自动化测试框架和测试编写不熟悉导致开发速度很慢等,往往是被动的在技术层面上去实现TDD,所以出现了各种怨言,各种抵触,进而导致技术层面上的TDD很难以大规模实施。

由于意识层面上的难易程度和工作量都比技术层面上相对较小,所以前者实施起来相对容易一些,而后者则相对较难,所以如果通过了各种手段强行实施TDD,而没有主动去摆正做TDD的意识,甚至没有足够的技术能力,那么这样的TDD就是一个倒三角,非常容易倒塌。

TDD倒三角

所以,如果不希望技术层面上的TDD随时倒塌,就需要把这个倒三角补全,才能更好的、长久的实施TDD。

第三阶段:有意识和主动通过技术实现TDD

为了大规模以及有效的实施TDD,首先要突破思维意识的局限,认识到TDD的普遍存在性和适用性,不要害怕和排斥TDD这种思维和开发模式。

其次要主动学习,并刻意练习TDD的技术实现,提升自己的技术能力,从而在技术层面能更容易的实现TDD,摆脱被动TDD的困境。其中学习的方法包括阅读TDD相关的书籍和文章,书籍包括《测试驱动开发》、《重构》、《BDD In Action》以及《系统思考》等,从而充分理解TDD优点和局限。

对于刻意练习,一定要长时间坚持去做,让其成为一种习惯。如果在项目中没有合适的环境去练习,还可以通过一些第三方的TDD练习系统去做刻意练习,比如Cyber-dojo。只有大量的刻意练习才能让你在真实的代码编写过程中去思考和理解TDD,去运用你通过学习得到的知识,最终才能做到有意识和主动的通过技术去实现TDD,TDD的倒三角才能变成一个稳定的砖块,然后哪里需要往哪里搬。

TDD砖块

总结

综上,大部分开发人员都应该在做TDD,只不过他们是无意识的或者被动的去实现的,只有少部分是有意识和主动的去实现的。既然人人都在做TDD,那么我们为什么不能和黑客帝国里面的Neo一样选择红色药丸来认清楚现实,主动拥抱TDD,并通过大量的刻意练习去改变自己的工作习惯,让TDD成为自己工作习惯的一部分,这样才能更好的提升软件质量,大大降低软件维护成本。不管你信不信,反正我信了。


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

Share

让我们再聊聊TDD

技术人员拒绝TDD的主要原因在于难度大、工作量大、Mock的大量使用导致很难测试业务价值等。这些理解主要是建立在片面的理解和实践之上,而在我的认知中,TDD的核心是:先写测试,并使用它帮助开发人员来驱动软件开发。

最近几年“TDD已死”的声音不断出现,特别是David Heinemeier Hansson那篇文章——《TDD is dead. Long live testing. (DHH)》引发了大量的讨论。其中最引人注目的是Kent Beck、Martin Fowler、David三人就这个举行的系列对话(辩论)——Is TDD Dead?

当前国内对TDD的理解十分模糊,大部分人也没有明确和有意识的去实施TDD,因此许多人对此都有着不同的理解。

其中最经典的理解就是基于代码的某个单元,使用Mock等技术编写单元测试,然后用这个单元测试来驱动开发,抑或是帮助在重构、修改以后进行回归测试。而现在大部分反对TDD的声音就是基于这个理解,比如:

  • 工期紧,时间短,写TDD太浪费时间;
  • 业务需求变化太快,修改功能都来不及,根本没有时间来写TDD;
  • 写TDD对开发人员的素质要求非常高,普通的开发人员不会写;
  • TDD 推行的最大问题在于大多数程序员还不会「写测试用例」和「重构」;
  • 由于大量使用Mock和Stub技术,导致UT没有办法测试集成后的功能,对于测试业务价值作用不大
  • ……

总结一下,技术人员拒绝TDD的主要原因在于难度大、工作量大、Mock的大量使用导致很难测试业务价值等。

这些理解主要是建立在片面的理解和实践之上,而在我的认知中,TDD的核心是:先写测试,并使用它帮助开发人员t来驱动软件开发。

首先是先写测试,这里的测试并不只是单元测试,也不是说一定要使用mock和stub来做测试。这里的测试就是指软件测试本身,可以是基于代码单元的单元测试,可以是基于业务需求的功能测试,也可以是基于特定验收条件的验收测试。

其次是帮助开发人员,主要是帮助开发人员理解软件的功能需求和验收条件,帮助其思考和设计代码,从而达到驱动开发的目的,所以TDD是包含两部分:ATDD与UTDD

ATDD(Acceptance Test Driven Development):验收驱动测试开发,首先BA或者QA编写验收测试用例,然后Dev通过验收测试来理解需求和验收条件,并编写实现代码直到验收测试用例通过。

由于验收方法和类型也是多种多样的,所以根据验收方法和类型的不同,ATDD其实是包含BDD(Behavior Driven Development)、EDD(Example Driven Development),FDD(Feature Driven Development)、CDCD(Consumer Driven Contract Development)等各种的实践方法。

比如以软件的行为为验收标准,这个是BDD;如果以特定的实例数据为验收标准,这个是EDD;如果以Web Service API消费者提出API契约来驱动API提供者开发API,这个是CDCD等。所以ATDD的具体实现需要结合项目的实际情况来选用适合的验收测试方法与类型。

UTDD(Unit Test Driven Development):单元驱动测试开发,首先Dev编写单元测试用例,然后编写实现代码直到单元测试通过。这个就是现在很多人所谓的TDD、实践的TDD、喜欢的TDD、抱怨的TDD,但是它却只是真正意义上TDD的一部分而已。

TDD金字塔

再来看看David 的《TDD is dead. Long live testing》,他主要是认为TDD大量使用mock,导致无法测试软件连接了数据库之后的功能,进而无法测试其业务价值。

其次他提出应该使用”Long live testing”, 而他并没有说明这种测试应该是在编写代码之前还是之后写,以及会不会用来作为客户对于软件的验收标准。如果他没有这样做,那他只是使用”Long live testing”来做回归测试;如果他做了,那么他也是使用了ATDD,从而使用了TDD。

所以他对TDD的理解还是狭隘的,认为TDD只是UTDD,导致他写了这篇文章来批评TDD。有可能他在现实工作中已经使用了ATDD,也就是TDD。

最后来看看Kent Beck、Martin Fowler、David关于Is TDD Dead?的辩论,我觉得他们所说的都有道理,并且也是合理的。原因是他们的背景和行业不同,本来对于不同的行业和不同的背景就应该选择适合的测试驱动方法(有可能不一样)。

首先来看看Kent Beck,他在Facebook工作,出版过很多书,可以定位为一名在大型IT公司工作的软件思想家。其次是David,一个标准欧洲帅哥,ROR创造者之一,Basecamp公司的创始人和CTO,Basecamp是一个只有几十个人的小型软件公司,所以他可以定位是一名创业者、技术牛人。

Kent Beck所在的公司开发的是大型复杂业务软件(Facebook平台),代码量巨大,需要长时间(几年)大量人员(几十甚至几百)来开发和维护。DHH开发的中小型企业软件(比如CRM),代码量一般,需要快速(几个月)、少量人员(几个到十几个)开发和维护。

Kent Beck在金钱和人力资源相对充足、时间相对充裕的情况下追求的是代码质量,大量人员的良好协作与平台稳定。DHH却在金钱和人力资源相对较少情况下追求最大化客户业务价值,使得少量人员能快速开发出软件并卖给客户赚钱。

所以在Kent Beck所在的环境下,单元测试(UTDD)是非常有价值的;而在DHH所在的环境下,功能测试或者ATDD却更为适合。

国内很多人对于TDD的狭隘理解还源于很多网上的中文资料,百度百科对于TDD的解释就是其中一个:

“TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。”

而国外有不少站点上的资料是对于TDD是有正确理解的,比如下图是一个敏捷调查表。从其中的“We take a test-driven development(TDD) approach”和”We take a TDD approach at the requirements level”就能发现其对TDD的理解就是包含UTDD和ATDD。

TDD不是银弹,不要期望它能轻易解决你的问题,无论是UTDD、EDD还是BDD,根据自己项目的实际情况,比如资金、人力资源、时间、组织架构等,合理的选择。

今天我们又聊了聊TDD,也希望大家重新理解一下,重新思考和尝试一下,然后你会发现另外一片云彩。

TDD并没有死,死的是你的持续学习、思考、实践与总结。当前国内很多软件开发人员对于TDD的理解比较模糊,大部分人也没有明确和有意识的去实施TDD,因此很多人都有着不同的理解。


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

Share

从测试策略到测试架构

现在业界流行的测试金字塔和测试象限只是两种高度抽象和简化的测试策略模型,不具备实际可操作性,只具备高层次的指导性和参考性。直接根据这两个模型来工作是低效的,甚至可能带来负面效果。

文/刘冉

今年是我做软件测试的第7个年头了,当年我从软件开发转做软件测试的时候,没有想过我能在这个领域做这么久。

在这7年里面,我在软件测试领域摸爬滚打,从自动测试起步,逐步接触到软件测试的各个领域:各种测试方法(等价类,全配对等)、测试技术(单元测试,功能测试,性能测试,探索性测试等)、自动化测试工具(JUnit,Selenium,Gatling,ZAP等)、测试流程(传统测试流程,敏捷测试流程等)以及测试策略(测试象限和测试金字塔等)。

其中“测试策略”在测试业界是讨论的比较少的,因为大多数人的工作重点是设计测试用例,执行测试或者开发和维护自动化测试,而只有少部分人才会涉及到测试策略的工作,从而导致很多测试人员其实并没有系统的了解测试策略。

所以我准备将我这几年对于测试策略的经验、总结以及思考以系列文章的形式写出来,希望能稍微帮助一下大家去理解测试策略,从而做到更好的测试,减少缺陷,提高质量。

测试策略

首先来看一下Wikipedia上对于测试策略的定义:

A test strategy is an outline that describes the testing approach of the software development cycle. It is created to inform project managers, testers, and developers about some key issues of the testing process. This includes the testing objective, methods of testing new functions, total time and resources required for the project, and the testing environment.

Test strategies describe how the product risks of the stakeholders are mitigated at the test-level, which types of testing are to be performed, and which entry and exit criteria apply. They are created based on development design documents. System design documents are primarily used and occasionally, conceptual design documents may be referred to. Design documents describe the functionality of the software to be enabled in the upcoming release. For every stage of development design, a corresponding test strategy should be created to test the new feature sets.

更多内容详见:https://en.wikipedia.org/wiki/Test_strategy

所以测试策略(Test Strategy)的第一目标就是“减少缺陷的出现和发布”。其中“减少缺陷的出现”可以通过测试前移等方法来解决,在进行软件需求分析和架构设计的时候发现缺陷;而“减少缺陷发布”可以使用各种测试方法、技术来验证和测试编码完成的功能(这两点在今后的文章里面会通过不同的例子进行更详细的阐述)。

由此可见,“测试策略”并不是只由测试人员定制的,它是由一个团队的各个角色一起来制定和建立的,目的是保证软件的质量,减少缺陷。

而“测试计划”是用于实施测试策略的。只有充分理解测试策略目的和实施方式,才能充分理解测试策略,为什么要做测试策略,什么样的测试策略才更有意义、更好,怎样实施才能更有效等问题。

测试计划

测试计划在Wikipedia中是这样定义的:

A test plan documents the strategy that will be used to verify and ensure that a product or system meets its design specifications and other requirements. A test plan is usually prepared by or with significant input from test engineers.

更多内容详见:https://en.wikipedia.org/wiki/Test_plan

制定测试计划是保证测试策略能被有效执行的一种方式。它告诉了团队在什么阶段,什么样的角色应该执行测试策略中什么样测试技术和测试方法。它主要由测试人员编写,但是应该由整个团队进行评审,因为开发人员、产品经理、业务分析人员甚至用户都可能参与到测试计划的执行中。

测试计划是可以根据项目的实际进展情况进行调整的,所以它并不是一成不变的。

测试架构

在上个世纪六七十年代软件系统还处于小规模的时候,软件开发并没有谈什么架构,软件测试也不存在什么策略可言。但是随着软件规模的极速增大,复杂性也成指数级增加,专业的软件架构应运而生。

为了有效的在规定时间内完成复杂软件系统的测试,必须有一个指导性的策略来帮助团队理解、选择和组织大量的测试,因此软件测试策略就出现了。而测试策略往往是高层次的指导,对于一些中小型项目也许已经足够了,但是却不足以应付现代越来越复杂的软件系统。

因为随着微服务、移动互联网、物联网、大数据分析系统、AI系统等的出现,要测试一个包含各种技术,外部依赖,或者独立子系统的复杂系统,并不是简单的根据测试策略在不同层面上做不同的测试就可以了,而是要理清各种测试之间的相互联系和制约,然后思考怎么有效的将各个维度上的测试联系起来,以软件系统架构的思维去思考整个测试体系。

请注意这里不是说要去设计一套全自动化的测试系统来完成整个系统的所有测试,而是通个各种有效的方式(无论手动还是自动)把各种测试合理且有效的联系起来,形成一个拥有完整架构的测试体系,这样才能使整个系统的各种测试更加可视化和更易于理解,使整个系统的各种测试更加有效,避免重复测试,节约成本。

举例来说,一个前后端分离的Web业务系统不仅有前端UI和大量的JavaScirpt代码,还有后端的API和第三方依赖系统以及数据库系统,如何将各层测试有效的联系起来就是测试架构需要解决的问题。

首先,前端、后端API、第三方依赖系统和数据库系统有各自的单元测试、集成测试等,然后可以使用契约测试来测试统一前端和后端API,再使用Stub加入对于第三方依赖系统的契约测试或者监控测试,还需要使用测试数据生成系统参数,将各种测试数据存入数据库系统用于支持契约测试等。

对于不同软件系统,其架构一般都是根据业务需求、技术能力等各种条件来设计的。与软件架构一样,测试策略和测试架构在不同的项目里面,需要根据其软件系统的架构、技术栈、业务需求、人员的技能等因素来定制和设计。

再谈测试策略

现在业界流行的测试金字塔和测试象限只是两种高度抽象和简化的测试策略模型,不具备实际可操作性,只具备高层次的指导性和参考性。直接根据这两个模型来工作是低效的,甚至可能带来负面效果。所以对于测试金字塔和测试象限不能盲目的使用,而是需要根据项目的实际情况来生成适合自己项目的测试策略和测试架构,并在此基础上执行真实的测试工作。

扩展阅读:

http://www.infoq.com/cn/articles/an-effective-test-strategy

http://www.testingexcellence.com/test-strategy-and-test-plan/

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

Share