钱包被偷

2008年06月23日 12:07

今天早晨坐公车上班,下车的时候下意识的摸了一下口袋,钱包没了。

共计损失现金若干,银行卡若干,可报销的发票若干,其他不记名代币卡若干,公司证件一张,饭卡一张,身份证一张,小虫照片一张。

现金损失也就算了,证件和卡后续的手续让我感觉很头大。

心情沮丧。

其实平时坐车已经很小心了,今天不知怎么就大意了。

唉。

再谈单元测试

2008年06月16日 14:52

最近一直比较忙,很久没有更新blog了。

今天收到一封信,问了我一个问题:

关于你提出的几点:1. 单元测试是一种测试,它不是代码的一部分;2. 单元测试是最低层级的测试,它只保证函数的可靠性,不保证其它;3. 单元测试应该能保证每一个函数的可靠性。

当今前端测试的问题在于仅仅对函数的输出进行验证并不能很好的确认其行为。因为js还需要对DOM进行操作,需要对CSS进行操作,IE,FF显示效果不一致等等。使得前端开发程序员不得不人肉进行测试,查看程序是否符合预期的显示效果。你们认为如何才能提高前端单元测试的有效性呢?

说实话这个问题是我刚刚接触单元测试的时候,也一直被困扰的一个问题,那就是,GUI界面如何单元测试?我记得在几年前,我还就这个问题特地咨询过gigix,当时他告诉我说“测试能测试的”。但是当时我对单元测试一知半解的时候,对于这个答案也是不甚了了。

今天我不敢说对这个问题已经理解得非常透彻了,但是我想把我的想法说出来,大家讨论一下。

在解释这个问题之前,我想重复一下我对单元测试的理解:单元测试是最底层的测试,它只保证函数的可靠性。

但是这里有一个重要的概念,我们需要进一步的说两句:什么叫“函数”?

从语法而言,函数就是语言概念上的一个语句块,这个语句块接收0个至多个输入,产生0个至多个输出。但是,所有的语言都没有规定函数需要有怎样的“语义”。于是,我们也不能在解释器或编译器层面阻止一个“坏函数”的诞生。例如,下面这两个函数,如何测试?

x = 1;

function a()
{
   global x = x+1;
   if (x < 10)
       setTimer(100, b);
}
function b()
{
    feed = time.now();
    diff = random(feed).getInteger(10);
    global x = x - diff;
}

函数a严重依赖于函数b,以及平台相关的定时器和一个状态未知的全局变量x。而函数b也依赖于平台相关的函数time和一个状态未知的全局变量x。这样两个函数要进行测试,难度是非常高的。简单的说,a几乎可以认为是无法测试的,因为它并不是一个我们所谓的“输入-处理-输出”函数,而是依赖于定时器这样的平台相关操作,定时器这种东西是很难模拟的,就算模拟出来意义也不大,因为真实的定时器几乎可以肯定不会跟模拟的定时器有相同的表现——而这个表现,正是我们编写这个函数的目的。函数b倒是可以测试,但你必须事先为它模拟好一个时间函数(类),一个随机数函数(类),和一个确定状态的全局变量x——这些工作在一些语言里可以做到但要付出很大的代价,在另一些语言里几乎可以说是不可能的任务。

那么,接下来的工作倒也变得很简单了:我们就要做一个价值上的衡量,我花很多时间精力去实现这些测试基础框架(对了,这些基础框架也需要测试),跟我整个系统的测试工作本身相比到底值不值?

其实在大部分时候,这个答案是“不值”。如果为了一个系统的单元测试要做如此多的工作,其难度不亚于开发一个新系统,那我们当然会选择不测试。

上面的例子说明了两点:1、函数并不是天然可测试的;2、不是所有的“函数”都是需要测试的。

等一下,我记得你说过“单元测试必须保证每一个函数的可靠性”这样的话?

对。所以我们必须明确一下单元测试里函数的定义,它跟语言上的函数稍有不同,实际上,是多了一段语义限制:函数,指的是接收0个至多个输入,进行逻辑处理,并产生0个至多个输出的代码块,它的输出受且仅受输入的影响。

符合这种定义的函数,是“可测试函数”,测试它们不需要花额外的精力。不符合这种定义的函数,则是“不可测试函数”。不可测试函数又分两种,一种是无法测试的,比如系统所需的回调函数(尤其是线程、定时器之类的回调)、随机函数等等,它的输出严重依赖于系统当时的状态,而这种状态难以复现,所以对他们的测试可以说是没有任何意义的。另一种是难以测试的,它也是输入-处理-输出这样的,但它的输出除了依赖于输入之外,还依赖于系统的状态,但这种状态是可以复现的。

对于可测试函数,我们没什么好说的,单元测试教材上这样的例子比比皆是。但我们必须说,现实远没有这样理想化,完全不依赖外部环境的函数,非常少见。难道说我们就没有办法做单元测试了不成?

当然也不是这样。但是正如我上面提到,函数并不是天然可测试的。随随便便的拿一个系统就要对它做单元测试,要么是不可能的,要么是难度很大的。为了让一个系统可测试,可单元测试,我们还是要做一些工作的。

最重要的一件工作,当然就是让函数尽可能变成可测试的,再不济也应该是难以测试的,而尽量减少无法测试的函数。这些无法测试的函数,留作功能测试去测(使用人工手段或者其他的测试手段)。

举个例子来说,按上面的定义,线程回调函数是无法测试的,因为它严重依赖调用时的时间点和当时系统状态。如果你把所有的业务逻辑都直接写在回调函数中,那么整个业务逻辑就变成了无法测试的。但是这些逻辑里肯定是有一些是可以剥离出来作为固定输入固定输出的逻辑,那么就把它剥离出来,这样,至少这一部分就变成可测试了,这个经过测试的部分就可以做到一定的保障,减少上一层功能测试的压力。

第二件工作,就是做好Mock Object。纯可测试的函数是很少见的,绝大部分函数都会像上面的那个函数b一样,多多少少要用到一些基础设施,比如时间、随机数、数据库、网络等,这些东西在真实环境中是不稳定的,我们需要构造一个“虚假的”环境,为测试提供一个稳定的基础(稳定的基础当然包括“稳定出错”的情况),这样才可以为测试提供一个稳定结果。这个Mock的工作通常也是很大的,有很多东西需要虚拟,如果是一个小的系统,我们不值得去构造这样一个虚拟环境,可以尽量把其中环境依赖的部分剥离出来,对环境独立的部分单独测试,对环境依赖的部分用人工测试。然而在一个大的系统中,环境依赖部分太多,人工测试变得不可能,这时我们还是有必要认真做一下Mock这个工作的。

当然还有其他的一些工作,包括对软件过程的制定,项目配置,都会随着单元测试的引入而需要做变动,但这不是我们这次讨论的重点,暂且忽略。

说了这么多,总结一下:像显示效果这样的测试,靠单元测试是做不到的,只能用人工或者其他测试手段。但是如果你的系统是按照易于测试的原则实现的话,可以测试那些能够测试的部分,这样会给你的人工测试减轻很多压力,因为很多东西通过单元测试之后,人工测试就可以认为他们一定是正确的了。这就是我前一篇文章所提到的“分层测试”原则。

另外,测试并不是万能良药,对任何系统都可以简单而方便的实施。为了做到易于测试,还是需要在前期(如需求阶段、编码阶段)做一番努力的。

Simpblog 测试中

2008年06月08日 00:30

Simpblog,一个用Pylons搭建的简易blog系统。初步完成了。

使用的框架是Pylons+SQLAlchemy+SQLite3。

前台使用Mako渲染,使用了Jonas John创作的CSS风格模版。

管理界面使用AuthKit做权限验证。

没有使用可视化的编辑器,使用了CreoleWiki语法,并使用MoinMoin的Creole Wiki分析脚本及HTML generator产生对应的HTML代码。

RSS使用PyRSS2Gen生成。

目前该系统还在内部测试阶段,如果发现bug,欢迎及时回报。

------------这是胡言乱语的分割线-------------

Pylons的开发还算是很快捷的,我从打算到开始动手到测试不过几天的时间。当然这跟SimpBlog的模型简单也有关系。

最初的设想是做一个庞大的框架,可是真正动手的时候,想法变了很多,我希望这个东西是能够尽快出来然后边测试边修改边增强功能,如果实现太复杂太庞大,可能在我激情消退之前都还出不来。于是就尽量快的写了一个。说它是原型也好,如果真的投入使用,也是可以不断调整的──事实上在开发过程中数据模型和想法已经做了很多改变──改变并不是坏事,我觉得。

这次在开发中尝试了AuthKit。发现AuthKit并没有想像中的复杂。如果使用得当,是可以轻易的实现很强大的授权和验证工作的。我喜欢。

Pylons的部署和开发,真是两个完全不同的概念。为了简化部署动作,我在服务器端也安装了完整的Pylons环境及其它支撑库。然后通过svn直接check代码到目标路径,用一个脚本自动运行。这样做的确是省了很多事,但令我意想不到的是,使用lighttpd的proxy之后,由于不是根目录,我的很多代码出现了问题。

首先需要解决的是一个共性问题:如何将带路径的url自动映射。幸好Pylons使用的部署工具PasteDeploy已经考虑到了这个问题。只要在development,ini的[app:main]小节增加

filter-with=proxy-prefix

并增加[filter:proxy-prefix]小节:

use = egg:PasteDeploy#prefix
prefix = /testblog

就可以了。

但是不久就发现还是有很多路径错误,原来Pylons里所有的路径都必须经由h.url_for封装,才能被prefix过滤器处理,直接写的URL是不会被处理的。于是又将模版中所有路径改写成h.url_for形式的。终于大功告成。

19周年

2008年06月04日 08:45

对于19年前的事,真的不想多说什么。是非对错后人评吧。

写出来只是提醒自己别忘记这件事的存在。

打算写一个新的blog

2008年06月02日 23:45

今天因为genshi的擅自修改代码的问题,以及搜索资料后发现的令人吃惊的渲染速度,我决定关注一下mako。

我想启动一个小项目:写一个简单的blog系统,来尝试一下mako模版。

根据我自己对现有blog的使用来看,很多功能其实我是不用的,比如上传文件/照片的功能,tag功能,等等。而有些功能我并不满意,比如我在群里不止一次抱怨过的编辑器。

所以我想借这次机会,实现一个我心目中的单人blog。

  • 它要很简单,只有写blog的功能,其它什么也没有──我不需要一个完整的CMS,只要一个能写文字的东东。其它的东西,我会将它们分布到我认为合适的服务上──他们更专业。
  • 它要有足够的扩展性,当我突然想需要一个功能的时候,我不需要到处折腾,只要修改或增加一点儿代码就能做到。
  • 它要足够快,起码不能比现在的慢──其实我也是想看看pylons+mako到底能快到什么地步。
  • 它要有一个对我来说好用的编辑器:不需要写一堆的<p></p>,但我想加HTML tag的时候,请给我这样的自由而不需要额外的工作。
  • 支持回复并有效阻止垃圾评论。
  • 纯文本,没有数据库。

当然,目前而言这肯定是一个试验性质的项目,我仅仅考虑自己的需求,而不会考虑任何产品化方面的易用性需求。但是,不排除如果真的还不错的话,我也许真的会用它替换掉现在这个,并且好好的产品化它。

苍老与新生

2008年06月02日 09:36

很久没有发摄影类的文了。原因很简单,拍得少了,拍得让自己满意可以见人的更少了。

不过昨晚在检查照片的时候看到这张,是去江苏参加叔叔葬礼的时候,奶奶和小虫的合影,突然觉得有点感动。

奶奶年纪大了,女儿们早已嫁入夫门,作为大儿子的父亲又从小离家,叔叔成了她唯一的依靠。这次叔叔的突然去世,给她带来的打击不是我们能想像的。办丧事的几天,奶奶常常伤心得不能自己。

奶奶很喜欢小虫,小虫来到她身边的时候,她会把小虫紧紧抱住,仿佛那是她生命中的一点籍慰。那情形让人看着心碎。只有小虫的灿烂笑容,才会给全家悲伤的气氛带来一点生机和活力。

拍的时候说实话我只是随手按下了快门。可是出来的结果,我感觉是恰如其分的表现了我所感知到的那种心情和氛围。我第一次觉得有一张照片就是我想要的。

从技术角度而言,这张照片完全没有做处理。即使有什么瑕疵,我也不想使用技术手段做什么处理,我觉得我有必要保持这张照片的原汁原味,因为我自己的感动,也是建立在这份原汁原味之上的。

六月一日

2008年06月01日 17:21

六月一日,说四件事。说给自己听,说给小虫听,也说给每个人听。

一、祝小虫儿童节快乐。

二、祝四川小朋友儿童节快乐。

三、纪念去年厦门的黄丝带。

四、传递今年汶川的黄丝带。

Design downloaded from free website templates.