请注意 pre/post 不能算是典型意义上的设计模式,Pre/post 是契约式编程思想的概念。但是在从 chromium 代码中确实能看到不少他们的身影。
契约式编程(英语:Design by Contract,缩写为 DBC)在 Wiki 上的解释:契约式编程是一种设计计算机软件的方法。这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。这种方法的名字里用到的 “契约” 或者说 “契约” 是一种比喻,因为它和商业契约的情况有点类似。
在《程序员修炼之道:从小工到专家》中专门有一条讲的就是契约式编程(按合约设计)。
DesignbyContract 的核心是断言(assertion)。所谓 “断言”,是指永远为真的布尔型语句,如果不为真,则程序必然存在错误。通常情况下,检查断言的时机,应该局限于调试(debug)阶段,而不是代码的实际执行阶段。实际上,完成的程序永远不应期望断言会被检查。
DesignbyContract 使用了三类断言:后继条件(post-conditions),前提条件(pre-conditions),以及不变量(invariants)。其中前驱条件与后继条件都是针对操作(operation)而言的。
前提条件 preconditions:acondition that must hold up on invocation of a function 在方法被调用之前就必须满足的条件。
后继条件 post-conditions:acondition that must hold up on exit from a function 法被调用之后所要保持的条件
不变量 invariants:acondition that must always hold for objects of the class,except while a public member function is executing.在方法的执行过程中,不变量可能为假,但是,在其他任何对象能够与被调用方进行交互的时刻,不变量断言必须恢复为真。
在之前 MBT 的探索中,我们曾经尝试使用了 PRE/POST 模型,可参考文章http://tmq.qq.com/2016/11/pre_post_explore/。
虽然 c++11 不支持 contract 的语法,但是从 chromium 的代码上也可以看到代码也采用了 pre/post 的方式来设计。
我们可以从 chromium 的启动代码中看下 pre/post 思想是怎么使用的。
下图是 Chromium 中浏览器启动时候的代码顺序:
具体函数调用可以参考网上文章(http://blog.gclxry.com/chromium-framework-start/)。
启动的主要逻辑都是在 Browser Main RunnerImpl,可以看到下面这段函数也是典型的 pre/post 的设计:
上面函数在 main_loop_->Main Message Loop Start 之前, 先调用 main_loop_->Pre Main Message Loop Start 准备相应的环境, 在执行完 main_loop_->Main Message Loop Start 之后又调用 main_loop_->Post Main Message Loop Start 来做后置条件的相关操作。
在每个 layer 里面都有对应的 pre/post 代码:
Browser MainLoop 里面的 Pre Main Message LoopStart 又会调用对应的平台的 Chrome 应用 (_parts 对应的就是 Chrome Browser Main Parts,不同的平台的应用不一样,windows 上面就是 Chrome Browser Main Parts Win) 的 Pre Main Message Loop Start。
在 Chrome Browser Main Parts 里面 Pre Main Message Loop Start 又会调用 chrome_extra_parts_(多个扩展应用 Chrome Browser Main Extra Parts:其中 Chrome Browser Main Parts 是对应的平台,
Chrome Browser Main Extra Parts 是对应不同的 Chrome toolkits (e.g., GTK, VIEWS, ASH, AURA, etc.)) 的 Pre Main Message Loop Start。
在编程语言不支持了 DBC 的情况下,在代码层面采用 PRE/POST 的设计可以极大地提高代码的易读性和可维护性。且建立这种契约明确了我们什么时候什么阶段该干什么事。
维基百科的解释:委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟 mixin。
Chromium 是一个复杂的开源项目,其中应用了丰富的设计模式来组织代码,应用最广泛的应该算是 Delegate Pattern(委托模式)。
在 chromium 中,每个模块具体功能的实现基本上都是通过 Delegate 类来实现的,如果开发者继承该 Delegate 类,并加以实现就能很方便的完成定制,倘若开发者不需要某个模块的功能,也就不用实现相关的 Delegate 类,那么该模块就不会发挥效用。
Delegate 的使用使得自动化测试也非常容易,这些测试需要能直接检测 Chromium 中的某个特性或功能能不能正常工作,检查新添加的代码对原有的代码有没有影响,但是由于有些功能需要手动干预才能正常工作,比如下载模块中弹出的对话框需要手动选择保存文件的地址和文件名;这些会给自动测试代码带来麻烦,但是有了 delegate 的设计,我们在测试代码中可以直接实现对应的 Test 的 delegate,继承正常工作的 delegate,该类做一些简单的修改事先填好一些数据,从而绕开需要手动输入代码块的执行。这就一方面完成了对已有代码的测试,也同时兼具了自动化。
在 Chromium 项目中有个 Download Manager 类 (content 里面),它负责完成任务的下载功能,当在浏览器中点击某个不能被渲染的链接时,浏览器就认为该链接的文件需要下载,就通过 Download Manager 来完成下载流程。
当在浏览器中点击某个不能被渲染的链接时,浏览器就认为该链接的文件需要下载,就通过 Download Manager 来完成下载流程。但是下载文件的实际工作都是在 Download Manager Delegate 中完成的,比如选择文件的路径,检查文件路径名是否合法,下载时候完成之类等。开发者只需要自己设计一个新的 Delegate 类来继承 Download Manager Delegate,并覆盖相应的方法即可完成下载功能,另外需要通过 Set Delegate 方法,在程序开始时把自定义类的对象注册到 Download Manager 中。目前 Chrome,Content Shell,CEF3 和 Crosswalk 都有自己的实现。
Chrome 基于 Download Manager Delegate 的 UML 类图:
下面看看自动化测试中应该怎么使用设置 test 的 delegate:
首先,基于 Chrome Download Manager Delegate 实现自己的测试的 delegate:Delaying Download Manager Delegate。
上面代码就是基于 Chrome Download Manager Delegate 定义了测试所需要的 Delaying Download Manager Delegate 类,并重写了方法 Should Complete Download 这样就可以绕开真实的 delegate 里面复杂的 Should Complete Download 逻辑判断,并简单的返回了 false 来进行测试。
在对应的测试用例代码里面调用 set Delegate 替换为测试的 delegate。
定义完了测试的 delegate 类,调用 SetDelegate 来使得 Delaying Download Manager Delegate 生效。
** 未完待续......
版权所属,禁止转载
扫描下方二维码,关注微信公众号:腾讯移动品质中心 TMQ,获取更多测试干货!