Lightweight Architecture Decision Records | 雷达哔哔哔

写在前面

ThoughtWorks每年都会出品两期技术雷达,这是一份关于科技行业的技术趋势报告,在四个象限:技术、平台、工具以及语言和框架对每一个条目(Blip)做采用、试验、评估、暂缓的建议。(参考阅读:解读技术雷达的正确姿势

一直以来,我们都未对每一个Blip做进一步的解读,而这次决定尝试一个新的专栏——《雷达哔哔哔》,由作者根据自己实践与理解,对雷达中部分条目作出解析,致力于用一篇篇短小精悍的文字,帮助读者加深对雷达的理解。

今天是《雷达哔哔哔》的第一篇,Blip是Lightweight Architecture Decision Records

位置

2018年5月第18期技术雷达,技术象限,建议试验

目标受众:

  • 系统架构师,技术管理者,开发设计人员

关注问题:

  • 传统的重文档编写维护量大,随着业务发展,很难保持同步
  • 在一些敏捷项目中,随着关键文档的缺失、项目Knowledge及决策丢失导致长生命周期的项目知识传递问题

解决方案:

  • 使用“ Lightweight Architecture Decision Records”(轻量级架构决策记录)来记录项目的重要决策,并将其与代码等其他项目资产一样,纳入到版本控制系统之中。

解读:

“项目要不要写文档”一直是一个很有争议的问题。在过去,项目一般都要写众多的文档,类似于需求说明书、概要设计、详细设计、数据库设计、等balabala设计……而这些文档的作用往往只是为了【过评审】或是【招投标】,写文档的形式也更多是简单的复制粘贴。

项目拿下了,过审了,一旦开动起来,文档反而就被束之高阁,再也无人过问了。

很多人没日没夜地写着千篇一律的文档、文档、文档……终于有一天,盼来了敏捷,并看到了敏捷宣言中硕大的一句:

(敏捷宣言)

唉呀妈呀,终于见到了亲人,从此高举着敏捷的大旗,与文档势不两立。

再有人敢提起写文档,就把早已准备好的“敏捷大棒”从身后掏出来,劈头就是一棒槌……

不得不说,敏捷又一次背了口黑锅。敏捷宣言所推崇的并不是简单的不写文档,而是认为之前那种写文档的方式根本没有体现出其应有的价值。还 不如代码写的漂亮些,测试写的完备些,让代码和测试成为真正有价值的活文档。

而这,相对于简单的复制粘贴攒文档,对于团队的要求反而更高了。

世间万物,物极必反。

随着时间的推移,再好的敏捷团队也会出现知识流失的问题,尽管有着完备的测试和易读的代码,但这些毕竟过于细节,无法快速还原当时设计或重构时的所有上下文。

所以技术雷达推荐使用“ Lightweight Architecture Decision Records”来记录项目的重要决策,相比于传统文档,它最大的特点就是轻量(Lightweight),关注于创造价值而不是遵循流程。 让我们看个ADR的模板:

(ADR Template)

同时技术雷达也建议我们不要将ADR束之高阁、放到Wiki或是文档库中。而是随着代码放到Git或其他版本控制工具里,这样既可以保持最大程度与代码同步,又能跟踪Decision的变更历史。

推荐的Adr-Tools工具,可以帮助我们更容易的做到这些。

相关Blip及延展阅读:

工具:

GitHub – npryce/adr-tools: Command-line tools for working with Architecture Decision Records

Share

写了这么多年代码,你真的了解设计模式么?

昨天和同事聊到最近他要做的一个培训,大概的课程是这样的:

第一天:

  • 上午:面向对象原则(OO+SOLID )
  • 下午:设计模式(Design Pattern)

第二天:

  • 上午:简单设计(SimpleDesign)
  • 下午:重构到模式(Refactor to DesignPattern)

面向对象原则,设计模式,简单设计,重构到模式……看起来都是常规操作,但你有想过他们的关系么?

忍不住要山寨一段《写了这么多年代码,你真的了解SOLID吗?》中的发言:

上面提到的每一项大家都耳熟能详,但我发现大部分开发者并没有真正理解。要获得最大收益,就必须理解它们之间的关系,并综合应用所有这些原则。只有把它们作为一个整体,才可能构建出坚实(Solid)的软件。遗憾的是,我们看到的书籍和文章都在罗列每个原则,没有把它们作为一个整体来看,甚至提出这几项的知名大叔们也没能讲透彻。因此我尝试介绍一下我的理解。

面向对象原则(OO+SOLID )

偷懒一下,请大家移步,直接参考文章《写了这么多年代码,你真的了解SOLID吗?》,记得回来呦~

简单理解,面向对象原则给我们提供了一系列面向对象上下文下的最佳实践,指导原则和终极目标,符合这些原则可以帮助我们最大化OO的威力。

如果把面向对象类比成软件开发领域的一个武林门派,面向对象原则就是这个门派的最高心法和目标,打个比方,有点像:心与意合,意与气合,气与力合,肩与胯合,肘与膝合,手与足合……这种。

心法这种东西就是神神秘秘的,真正看起来也简单,字面上也不难理解,很容易用它来挑战别人:“你看你看,你没有做到手与足合!”

但是回到自己,要想做到就难了,知易行难!

归其原因它虽然提供了目标和评价标准,使我们很容易拿他来评价别人,但并没有告诉我们自己如何才能达到这样的目标和标准。

设计模式(Design Pattern)

那设计模式是什么?

很多人,包括我在内,都曾迷陷于23种设计模式之中,初识设计模式,赞叹于其精妙,就像个萌新的江湖小生,偶然间掉到个山洞,一下就集齐了威震江湖的“7种武器”。

然后……就开始了用长生剑切菜,用碧玉刀削瓜的“幸福生活”,不但把简单的事情搞得巨复杂,最不能忍的是还暴殄天物!

回到设计模式,无非是在面向对象原则这些虚无缥缈的“心法”指导原则下,那些前辈大神们留下的“招式”或是“套路”而已。但招式和套路并不能致胜,它只是为我们这等小白提供了一个接近大神,理解心法的途径而已,通过长年累月的模仿去反思去领悟去体会“心法”的本质和精妙,此时脑中不禁浮现《少林寺》中李连杰夏练三九冬练三伏的画面……

这就能解释为什么使用同样的招式,大神们总能一击致命,而我们却总是被按倒在地摩擦的原因了。

简单设计(SimpleDesign)

设计模式是套路和招式,那简单设计是什么?

首先要区分一下简单设计和容易设计:

简单的反义词是复杂,容易的反义词是困难。简单不等于容易,追求简单的过程往往很困难,如果只是追求容易的往往导致系统过于复杂。

这是好多年前一位同事提到过的一句话,很在点儿上,我们经常混淆了简单和容易,对于这点,另一位同事之前也写过一篇文章来阐述做到“简单”的“困难”。

做为结果的简单设计是这么一种设计,它能被几乎所有人理解, 但只有极少数人能做出. 或者反过来说也可以. 简单设计是一种只有极少数人能做出的设计,但设计一旦做出后,能被所有人理解.

可见简单设计是一种只有极少数人能做出的设计,那我们怎么才能成为那“极少数人“呢?

对此,Kent Beck给出了清晰的答案:

  1. 通过所有测试(Passes its tests)
  2. 尽可能消除重复 (Minimizes duplication)
  3. 尽可能清晰表达 (Maximizes clarity)
  4. 更少代码元素 (Has fewer elements)
  5. 以上四个原则的重要程度依次降低。

这组定义被称做简单设计原则。

我们不具体探讨这几个原则,回到最初的问题,如果说面向对象原则是”心法“是”目标“,设计模式是前辈们沉淀下来的”套路”和“招式“的话,那简单设计是什么呢?

我觉得就是实战指导原则,他可以让我们不局限于哪些经典的过往的招式,跳出套路,无招胜有招。

如果说设计模式是一种自上而下,通过不断模仿前辈大神套路达到目标的一条道路的话(有招胜无招);那简单设计原则则是另一条自下而上,忘掉招式和套路,遵循简单的基本原则,随机应变,不断演进,不断浮现,逐步逼近目标的另一条道路(无招胜有招)。

但,记住,殊途同归。

好,这位客官问了,那两条道路都可以帮我们掌握武功最高的心法的目标,我走哪条呢?如果我已经会了一条,还需要学习另一条路径么?

请移步我的《筷子定理》

重构到模式(Refactor to DesignPattern)

说到这里,就不难理解“重构到模式”到底在说什么了吧。

它无非是在解释如何通过自下而上应用简单设计原则,运用重构的技术和手法,浮现出设计模式。我管这个过程叫“重走长征路”,重新体验一下那些大神前辈们创建发现设计模式的过程。

这个过程也再次证明了这两条通往同一个目标的不同的道路也是可以相互转换的,它们只不过是面向对象原则下的两种不同形式的表现而已。

如果说“设计模式”是“有招胜无招”,“简单设计”是“无招胜有招”的话,那“重构到模式”就是“无招生有招”的过程。

总结

总的来说,我们可以把这几个概念作为一个整体框架来思考。

“面向对象原则”是OO领域的终极目标,是面向对象这门武功的心法,很容易用来评判别人,但是自己却很难达到。

为了能达到心法的境界,之前的各任掌门大神们总结并留下了一些套路和招式,什么降龙十八掌之类的,让我等晚辈可以通过日复一日的刻意练习,去不断参透心法的真谛,这些套路和招式就是“设计模式”。

但只有这十八招显然是不够的,于是我们又学到了一些实战指导原则,让我们以另一种方式,自下而上,跳出招式和套路的限制,甚至能不断浮现出新的招式,且通向的是同一个目标,这些实战指导原则就是“简单设计原则”。

最后,不断操练“重构到模式”,通过实际运用实战指导原则和手法,推演招式和套路,帮助我们将这两种方法融会贯通,相互结合,最终领悟那隐藏在一切表象背后的真理,达到无招胜有招,草木竹石皆可为剑,心随意走,人随心动的境界。


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

Share

你的微服务敢独立交付么?

最近经常在项目或是社区里听到大家谈论微服务架构,但谈论的焦点更多集中在微服务拆分,分布式架构,微服务门槛,DevOps配套设施等话题上。

但是在我眼里,真正能称之为微服务架构的少之又少。原因也很简单,我所见到的很多所谓的微服务架构项目,大多都没有做到微服务架构的一个基本要求:服务的独立部署(交付)。

这里的独立部署和自动化部署还不是一个概念,服务的自动化部署相对简单,已有大量的工具可以帮助我们做到。但是这里所谈的独立部署,我认为关键和难点并不在于“部署”,而在于“独立”。

如果失去了服务独立部署(交付)的能力,一个微服务架构的威力将大打折扣,我们的系统虽然在物理上被拆分成了多个小的服务,但是如果从最终交付的角度来看,仍然是以一个整体存在的,就像单体应用一样,存在诸多的问题。

为什么服务的独立交付并不简单?

那为什么不能让每一个服务都独立部署到产品环境呢?问题的答案是:不是不能,而是不敢

为了表达清楚,让我们来看个例子吧。

像下图一样,我现在就是那个程序员帅哥(本色出演),突然有一天心血来潮,动手开发了一个网上商城。代码Push到Github并通过CI构建持续交付流水线,最终自动化部署到云端产品环境,供用户访问使用。

随着用户和访问量的增加,需求和功能也越来越多,系统也变得越发复杂。

从网上了解到最近有个叫微服务的架构非常火爆,我也赶了回时髦,当然也觉得这种架构确实可以帮助我解决现在的一些问题。

经过对系统的分析,我将商城的后台部分拆分出了3个服务,为了简单我们就称之为ABC三个服务。

我们假设一个比较极端的情况,三个服务相互调用(先不考虑这样是否合理),每个服务通过自己的持续交付流水线独立部署到产品环境。当前产品环境的各个服务的版本是:A:1.0、B:2.0、C:3.0

一切都非常完美是不是?看!我们已经做到了服务的独立部署!So easy~

当然,事情肯定不会那么简单。

问题出现在当我对A服务做了一次新的提交之后,A服务的最新版本升级到了1.1。不幸的是,这个新的版本意外的破坏了A与B之间的契约,错误的调用了B的接口,导致出现了错误。

虽然我的A服务和B服务都有比较完备的UT(单元测试),但因为UT无法发现服务之间的集成是否被破坏,所以只有UT作为质量保障的A服务持续交付流水线也自然没有能力发现AB服务集成被破坏的这个问题。最终导致存在问题的A1.1版本被部署到了产品环境,产品环境出现了严重的Bug。

请问在座的同学,碰到这样的情况,你会如何处理?

“加集成测试啊!”

这位同学说的极是,我这么聪明自然也想到了这一点,不就是要测集成吗?UT干不了就加集成测试不就成了。

为了统一语言,毕竟对于各种测试的叫法太容易引起混淆,参考Martin Fowler在《微服务测试策略》中的定义,我们在本文中将这种测试多服务集成的测试统一称作端到端测试(End-to-End tests,简称E2E测试)。

添加了E2E测试之后,我的交付流水线就变成了下面这个样子。

因为有了E2E测试的存在,问题迎刃而解,当A服务的新版本破坏了与B服务的集成时,E2E测试就会及时诊断出来,并阻止A服务的最新版本向产品环境流动,保证产品环境不被破坏。

这样看似没有什么问题,通过添加E2E测试,解决了服务间集成的验证问题,但在不知不觉中,我们也失去了微服务架构的那个重要的特性:“服务的独立交付”。

怎么讲?别急,我们再往下看。

假设A服务的修复过程中,B和C服务也提交了新的代码,我们假设这两个提交是没有问题的,但因为A服务的1.1版本导致E2E测试挂掉的问题还没有被修复,所以B和C的新版本也被E2E测试拦了下来,此时的E2E测试就像是一个亮起红灯的路口,阻塞了所有服务通往产品环境的通道。

所以说,随着集中E2E测试的添加,质量被保障的同时,我们的“微服务架构”也已悄然失去了服务独立交付的能力,杀敌一千自损八百,损失惨重!

这并不是我假想的场景,在我自己经历的几个真实项目中,这个问题都在一直困扰着我们。带来了各种各样的衍生问题,例如E2E测试长时间失败,无人修复,修复难度大,服务交付堵塞,为了保持交付通路畅通还不得不引入同样存在很大副作用的CodeFrezze机制和提交Token机制等。

可以看到,虽然我们能够在代码库,在部署结构上,甚至在组织上进行服务化拆分,但就因为这最后一个交付的十里路口,最后这一个红绿灯,让所有的服务又纠缠在了一起,所有的服务化拆分形同虚设,最终我们得到的也只是一个看起来像微服务架构的单体应用而已

拆除红绿灯,各行其道,收复失地!

那,如何才能将这个“红绿灯”拆除,让服务可以在有质量保障的前提下还可以做到独立交付呢?这就是本文要解决的问题,让我们继续往下看。

我的解决方法其实也很简单:Inline E2E tests

即并不添加新的集中的Pipeline做E2E测试,而是为每一个服务的Pipeline都添加一个相同的E2E测试的Stage,就相当于将E2E测试Inline到每个服务各自的部署流水线中,如下图所示。

其实Inline E2E测试还不是最关键的,最关键的变化点就是假设A服务有了新的提交,运行到A服务自己Pipeline的E2E测试的时候,此时的E2E测试并不是像之前一样获取B和C服务的最新代码库版本做集成验证,而获取当前产品环境上的B和C服务的已部署当前版本做集成验证

例如,如图所示A服务的版本从1.0升级到了1.1,当前产品环境的B和C的版本是2.0和3.0。在执行A服务Pipeline上的E2E测试时,验证出A1.1和B2.0集成存在问题,测试变红,Pipeline挂掉,从而阻断了A服务的1.1版本部署到产品环境,保证了产品环境不会被A的1.1版本破坏。

同样,假设A还没有被修复之前,B也有了新的提交,产生了一个新的版本B2.1,这时在B服务Pipeline上的E2E测试并不获取当前A服务的代码库最新版本1.1做集成测试,而是获取产品环境上的当前版本A1.0版本做集成测试。我们假设B2.1和A1.0之间的集成没有问题,测试通过,所以B的2.1版本就被成功的交付到了产品环境,而此时产品环境的A服务的版本仍是1.0。

看!服务之间的阻塞被神奇的解决了,服务再也不会被堵在一个统一的十字路口,而是各行其道,A的车道出了事故,是A的问题,应该由A来承担后果和解决问题,不应该影响到其他服务,其他服务依然可以持续的交付到产品环境。

向前看是持续集成,向后看是持续交付!

看到这里可能有些小伙伴会感到有些失望。咋呼半天,不就是将E2E测试整到每个服务的Pipeline里,再把获取版本从最新代码改成产品环境么?有啥厉害的。

但是,在我看来,这个看似简单的变化,意义却是重大的:它揭示了“持续集成”和“持续交付”的一个主要区别。

“持续集成”和”持续交付”,这两个概念相信大家一定都不陌生,在软件领域也被提了不少年头了,不算什么新概念新技术。但对于这两个概念,我们经常一起提及,也经常混淆,搞不清楚两者的区别到底是什么,可能认为持续交付只不过是持续集成的演进版,新瓶装旧酒而已。

但其实它们却有着本质的区别。

“持续集成”关注的是各个集成单元之前最新版本的集成问题,即是不是某个集成单元的最新版本破坏了系统整体的集成,我管这种视角叫:向“前”看。

而“持续交付”关注的应该不是集成单元最新版本之间的集成问题,而是某个集成单元的最新版本是否可以(能和敢)部署到产品环境。换句话说就是维持产品环境的其他服务不变,只将当前集成单元的最新版本部署到产品环境,产品是否依然可用,不被破坏。所以在“持续交付”的视角下,应该关注的是当前集成单元与产品环境上的其他服务的版本是否兼容,我管这种视角叫:向“后”看。

向前看是持续集成,向后看才是持续交付,如果前后都不看那就是在裸奔。

但是肯定早有同学在心里疑惑,将E2E测试下放到每一个服务自己的Pipeline中,靠谱么?是不是太重了?根据测试金字塔,E2E测试应该是属于靠近金字塔顶端的测试种类,无论从数量和覆盖范围应该也都不会太多,怎么能靠它来保障服务之间的所有集成点和契约呢?

主角登场-契约测试

细心的同学肯定已经发现上面最后一张图中,我已经悄悄的把E2E测试变为了CT,即Contract Test,契约测试。

契约测试也是这两年伴随微服务架构的兴起,经常被提及的一种比较新的测试类型。在测试金字塔中,他的位置介于E2E和Component Tests(可以理解成单个服务的API测试)之间。

简单的理解,契约测试就是一种可以用类似于单元测试的技术验证两两服务之间集成的测试技术。它相比于更低层次的单元测试的优势是可以测集成(两两服务之间),相比于更高层次的E2E测试的优势是实现方式上又类似于单元测试,更轻量,跑的更快,覆盖的范围也自然可以更广更细。

使用契约测试替换掉E2E测试之后,整个架构也会变得更复杂一些,目前契约测试的框架也有很多,如大家常常提到的Pact或是SpringContracts等等。这里我先以Pact为例予以说明,其他框架实现上可能有些差别,但是思路是一致的。

A服务调用B服务的一个API,我们就称为A和B之间存在了一个契约,即B应该按照这个契约提供一个满足契约要求的API,而A也应该按照这个契约约定的方式来调用B的这个API。在这个过程中A作为调用方,我们称之为Consumer端。B作为被调用方,我们称之为Provider端。

如果A和B都履行契约,按照契约定义的约定调用和被调用,我们就可以认为集成不会有问题。但无论是B擅自修改了API破坏了契约,还是A擅自修改了调用API的方式破坏了契约,都会导致契约被破坏,反应到测试上就是契约测试会失败,反应到产品上就是功能被破坏,出现Bug。

每个契约,例如A->B,都会有Consumer端和Provider端生成的两个产出物:分别是a-b.consumer.json.1.1(由Consumer端生成的契约文件,所以版本也是Consumer端A的版本号)和a-b.provider.jar.2.0(由Provider端生成的契约验证测试包,他由Provider端生成,所以版本是B的版本)。这个jar包其实就是一组测试,他的输入是a-b.consumer.json,产出则是测试的结果,也就是契约的验证结果:成功或是失败。

可以把A服务产出的契约文件a-b.consumer.json.1.1想象成一把钥匙,把B服务产出的Provider端的测试a-b.provider.jar.2.0想象成一把锁。那契约测试的执行过程就像是用这把钥匙试着去打开这把锁:如果可以打开,我们认为这A1.1->B2.0的契约是满足的,反之契约就是被破坏了。

值得注意的一点就是,契约测试不像E2E测试,它是有方向的,所以我们看到a-b和b-a是两个不同的契约。

所以,只有当A1.1->B2.0和B2.0->A1.1双向的契约都被验证通过后,我们才能认为A1.1版本和B2.0版本的集成是没有问题的。

用契约测试替换E2E测试

回到前面的例子上,假设我们已经构建了ABC三个服务两两之间的契约测试。此时,A服务有了新的提交升级到了1.1版本,那我们如何才能通过契约测试来验证A1.1版本能否交付到产品环境呢?

答案就是只要通过A的1.1版本的最新代码,生成所有A作为Consumer端的契约文件(a-b.consumer.json.1.1和a-c.consumer.json.1.1),用这两把“钥匙”去试着开(作为输入执行Provider端测试)产品环境对应的两把“锁”(a-b.provider.jar.2.0和a-c.provider.jar.3.0)。

如果都可以打开(测试通过)的话,就证明A的新版本1.1作为Consumer端与产品环境的B和C服务是兼容的。

等等,别着急,还没完……

因为我们还需要考虑A作为Provider的情况,做法还是通过A的1.1版本的最新代码生成A版本作为Provider端的契约测试(b-a.provider.jar.1.1和c-a.provider.jar.1.1),拿着这两把“新锁”,然后试着用产品环境上的两把“钥匙”(b-a.consumer.json.2.0和c-a.consumer.json3.0)去开。

如果也都可以打开(测试通过)的话,就证明A的新版本1.1作为Provider端与产品环境的B和C服务也是兼容的。

至此,当验证了A的新版本1.1无论是作为调用端还是被调用端都与产品环境上的其他服务契约满足后,我们就认为A1.1与B2.0和C3.0集成是没有问题的,也就代表A1.1可以被放心地部署到产品环境中,替代现在的1.0版本。

这块稍微有些复杂,用文字也很难讲的特别清楚,如果大家对我上边讲的内容感兴趣,但又没有完全理解。请大家移步去看一下我2017年9月份在北京CDConf(持续交付大会)上针对这个主题做的一次分享,讲的应该比写的更清楚一些。在那个分享的最后,也详细介绍了一些我们在这个方案实施过程中碰到的一些问题:例如对于契约变更,并发提交,多环境支持的解决方案,感兴趣的也可以拖到最后看一下。

《契约测试-微服务持续交付的金钥匙》(CDConf 北京 2017)

最后,敲黑板划重点

  • 微服务架构下的独立部署(交付)很重要,但往往容易被忽视,没有被引起足够重视。
  • 为了实现微服务的独立持续交付,我们要向“后”看,不要向“前”看,即关注当前变更服务与部署环境中其他服务的兼容性而不是关注当前变更服务与其他服务最新版本的兼容性。
  • 用契约测试来替代E2E测试,降低测试成本,提高测试覆盖,尽早测试。并通过不断地完善契约管理,保障微服务架构质量和避免微服务架构腐化僵化。

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

Share

测试矩阵

迷阵

“单元测试,集成测试,端到端测试,安全测试,性能测试,压力测试,契约测试,冒烟测试,验收测试,API测试,UI测试,兼容性测试……”

不知道你是不是像我一样,曾被这些各种各样的“测试”搞得晕头转向。作为一个有追求的开发人员,保证所写的程序、所构建的系统具备良好的质量自然是分内之事。但是面对这些千奇百怪的测试难免会望而却步,只能劝自己一句“专业的事情还是交给专业的人去做吧”,然后把测试的工作一把推给QA,闷头写自己的代码去了。

不光是测试种类众多,每个人对于某一个测试的理解也都不一样。就拿大家最熟悉的“单元测试(unit testing)”来举例,问题的关键就被聚焦到了“到底如何才算是一个单元(unit)?”有人说是一个方法,有的人说是一个类,有的人说都不对,应该是一个最小的业务单元(至少是API级别的)。还有人提出了Integration Unit Test的概念,即集成级别的单元测试。

不光是我等软件小辈,就连很多IT界的神级人物也常常为此争论不休。

古话说的好,一千个人心中有一千种单元测试,看来说的是有道理的。

列表法

(列表法)

这是昨天陪闺女写作业的时候,看到她使用了一种被称作“列表法”的方法去解一个小学2年级的逻辑题。闺女说,这种方法很神奇,原本看起来弯弯绕的问题,画个表勾勾叉叉就解决了。

随后我也查了一下:“列表法是小学数学学科中经常使用的一种方法,使用列表法可以解决许多复杂而有趣的问题。运用列出表格来分析思考、寻找思路、求解问题,经常用来解决类似于鸡兔同笼的经典问题……”

虽然我一直没有搞清楚为啥要把鸡和兔子放到一个笼子里,但回到测试迷阵的问题,好像这种小学3年级就教授的方法也能适用。

测试矩阵

(测试矩阵)

测试的种类繁多,难于理解,难于沟通。我觉得主要是在于我们将两个测试分类的维度混杂在了一起。

其中第一个维度是测试实现的层次或粒度,说白了就是在哪个层次上的测试,也可以理解成测试到底测的是哪儿。是方法?是类?是API?是单个Service?是两两Service?还是应用?还是系统?还是平台?

我们常说的单元测试,API测试,端到端测试,UI测试都是侧重于按照这种维度去分类不同的测试种类的。

但是我们在谈论这些测试的时候,其实隐含了一个概念就是他们测的是什么?也就是测试的目标。例如当我们提到上面的单元测试、API测试、端到端测试的时候其实隐含的想表达的是单元级别的功能测试,API级别的功能测试和端到端级别的功能测试。

这时候你肯定会想,这不废话么,不测功能我测什么?

这就是我想说的第二个测试分类的维度:我们测试的标的物,或是说测试的目标。如果说第一种测试维度是根据“测哪儿”区分的,那第二个维度就是根据“测什么”区分的。

例如,我们常常提到的:功能测试、集成测试、性能测试、安全测试、压力测试、兼容性测试,契约测试都是这种按照这个维度去区分不同的测试种类的,他们都不是关注于我们要测哪儿,而是更侧重于我们到底要测什么:业务功能是否正确?是否能按预期集成?契约是否被保证?安全能否达到要求?性能是否满足预期和要求?

只不过我们日常工作中,大多数情况下测试都是在验证功能是否正确,所以我们常常忽略了第二个维度,只关注于测哪儿。只有当我们去测试像性能和安全这种非功能需求的时候才会想到第二个维度,但有趣的是往往我们这时候又会忽略第一个维度,例如当我们听到有人提及性能测试的时候,并没有明确的表达测的是方法的性能、API的性能,还是UI的性能,进而导致了理解的不一致和混乱。

换个叫法

可见,之前之所以被测试迷阵困扰,其本质原因就是并没有明确区分开这两个维度,甚至将之混为一谈,从而使我们对于“XX测试”的定位和理解包括沟通都变得模糊而不准确。

如果我们不再提“单元测试”、“性能测试”这种含糊不清的概念,而是通过测试矩阵上的二维定位法,改称“方法级别的功能测试”和“API级别的性能测试”,我想我们对于测试的沟通讨论甚至学习实现将明确的多,也简单的多。


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

Share

从汽车贴膜看专业团队

前几天去给新车贴膜,体验了一把什么叫“专业团队的专业服务”。

听老板说这家店刚开张两个月,但是团队并不是新组建的,而是已经在一起配合了很久。这从后来的整个过程,也看得出来。整个过程我几乎一直站在旁边,虽然被冻得够呛,也被老板怀疑我是在监工,说了好几次让我放心,绝对做到令我满意。但我其实是在观察,或者说是在学习,因为我觉得他们同样作为一只专业服务团队,比我们更敏捷,也更精益。

在制品限制(WIP Limited)

汽车美容这种工作,由于存在场地限制,天然就满足精益中的WIP Limited。像我这次来的这家汽车美容店,只有三个工作台,也就形成了最自然的WIP Limited。就算是有再多的活,再多的车需要贴膜装饰,也只能排在外边,整个团队最多也只能工作在三台车上。

这种天然的WIP Limited存在,也限制了大家并行工作的最大车数。那为了获取更大的利润,也就是为更多的车服务。大家的关注点自然而然的就落在如何以最快的速度完成每一台车的贴膜装饰过程,也就是我们常说的单件流和Lead Time。

自组织全功能团队

(自组织全功能团队)

为了尽量缩短每一辆车从开始装饰到完成交车的整个过程,也就是缩短单个车的Lead Time,我观察到整个团队是在以一种几乎完美的方式协同工作。

首先,所有的工作被高度并行化。例如我的车最多的时候有四个人在同时施工,一个人在缝真皮方向盘套,一个负责贴车左侧窗户的膜,一个负责贴车右侧的膜,一个负责贴前后挡风的膜。

其次,大家并没有清晰的角色划分,缝方向盘套的人在完成了手头的工作后,立刻自觉的加入到贴膜的工作之中;而两侧的膜贴完后,两名工人立刻开始帮车打蜡和做内饰清洁;整个过程自然而连贯,完全自组织,不需要人安排和督促。

所有人都掌握了缝方向盘套、贴膜、打蜡、内饰清洗的工作技能,并没有严格的角色分工,很难说清楚谁是贴膜师,谁是打蜡师,他们每个人都像一个专业的全栈工程师。你也很难说清楚整个过程的流程,是先做贴膜,还是先做内饰清洁,整个过程已经被高度优化过,环环相扣,环环相融,无论是时间还是材料的浪费都被降到了最低。

Leader VS Manager

不用担心,这不是发生了意外,而是在做“新车去异味”项目。而这个一头扎进充满烟雾车厢的人就是这家店的老板。是的,他还是我上面提到的四名“工人”之一,分别完成了缝方向盘套,新车除味和右侧的贴膜工作。

在我的眼里,他就是一个称职的Leader。凡事冲在前面,以身作则,勇于承担一些困难甚至危险的工作。而不是坐在舒服温暖的办公室里指点江山。有了这样的老板,这样的Leader,员工们自然也干的格外起劲。而对于作为客户的我,自然也对这样的团队平添了一份信任和钦佩。

质量内建

关注Lead Time并不代表做的越快越好,更不意味着忽略质量,毕竟残次品也是一种常见的浪费。这不,在我的车几乎贴膜完成的时候,工人在做复检过程中发现左后窗户的贴膜有了一个小气泡。

老板在亲自检查、确认无法修复的前提下,二话不说直接将已经贴好的膜撕掉,重新亲自上阵贴了一个新的给我。整个过程迅速而敏捷,还保持了较高的质量和水准。

总结

一个小时之后,我的车焕然一新。

不得不佩服这样一只专业的团队和那个令人钦佩的老板。他们的技术是那样的全面而专业,整个团队的协作是那样的高效而自制。

而回顾整个过程,让我对于自己的团队有了很多反思,对于精益软件开发中的很多概念也有了更深刻的理解和认同。


更多精彩内容,请关注微信公众号:软件乌托邦

Share