Bambook使用手记

自从去年7月末盛大的Bambook(中文名称:锦书)上市起,我就一直关注着这款产品。不过考虑到刚上市的产品价位较高,功能和应用有限,缺陷较多等因素,我也一直没有下单购买。期间我还差点买入Kindle,后据说Kindle对PDF及中文的支持不佳而暂时打消了念头。上周在京东看到Bambook价格降到了499,而且据网上评测Bambook在经过一年的固件升级后,增加了许多功能,其中就包括对原生pdf文件的支持。我的直觉告诉我:是时候下单了。

我选择了在盛大Bambook官网下单,你无需在盛大官网注册即可下单购买,系统会自动为你生成一个账户和密码。另外盛大也支持货到付款,这点还是蛮体贴的。我上周四下的订单,这周一,也就是昨天我的Bambook就到货了。

Bambook是通过宅急送运到的,之前就收到过盛大的短信,提示先验货后付款,因为电子书的屏幕很容易在运输过程中出现损坏。屏幕可是电子书的最关键部件,不能马虎。Bambook的包装很简约:除主机外,还有一根数据线,同时兼具充电功能、保修卡、简单的用户手册和一个盛大账户卡(默认与Bambook绑定的,享受盛大书城服务)。扳动开关键启动Bambook,屏幕显示一切正常,外观也没有瑕疵,验货完毕,付款。

盛大的Bambook有两种颜色可选:白色和粉色。我买的是白色的。Bambook给我的第一印象:外观精致,分量适中,手感不错。这里就不上图了,网上有许多清晰的图片可供欣赏。随意按了一通后,发现按键的感觉有些偏硬,似乎没有Kindle(几个月前曾体验过同事从美国买来的Kindle)的舒服。

Bambook的屏幕显示也是比较清晰的,至少我是看不出与Kindle有啥较大区别。Bambook内置了几本小说,不知道是啥格式的,阅读翻页都很顺畅,直觉上看其反应速度算是中等吧(我可没有拿秒表精确计算过,只是直观感觉罢了),至少我是可以接受的。Bambook支持全屏阅读和横屏阅读,对于txt或snb格式的书籍支持4档字体大小调节。

和Kindle一样,Bambook也支持听书,内置的发音引擎发出的声音还是比较清晰的,声音也很大(可调节)。当然了Bambook的发音引擎也无法避免一个通病,那就是音调听起来总是那么怪里怪气的。

Bambook支持通过WIFI和3G上网卡连接网络,不过遗憾的是其没有内置浏览器(这点要远逊于Kindle),通过Bambook只能到盛大的云中书城搜书、看书和下载书籍。对我来说,这个功能基本算是鸡肋。

Bambook通过云梯软件(当前版本是0.97,且只有Windows版)与PC进行数据交互,你通过Bambook自带的帐号在云梯内部登录后,即可下载云梯插件、下载电子书。云梯还提供书架管理,你可以将自制的电子书放入书架,传输到Bambook中。新版Bambook的一个重大改进就是支持原生PDF文件的阅读,不过经尝试发现,在云梯未连接Bambook之前,我们是无法将原生PDF文件加入书架中的,云梯会提示你选择转换为txt或html图文混排;但当连接Bambook后,就可以直接将原生PDF文件拖入书架,同时也上传到Bambook中。

Bambook的原生PDF文件的显示效果还是很不错了。无论是文字版,还是扫描版都能良好展现。不过因为Bambook屏幕只有6寸,字体尺寸被压缩,看起来感觉很小。这也是目前电子书产品的一个普遍问题了。对于原生PDF文件,Bambook也无法进行缩放浏览。我们只能选择横屏+全屏的阅读方式来缓解这一缺憾了,换到横屏+全屏后,个人感觉文件内容基本还是可读的。另外以后下载PDF格式书籍时,尽量挑选那些字体大的版本下载。目前我还没找到一款合适的软件可以支持修改PDF字体大小并保存的。

在还不到一天的时间里,我对Bambook的体验就是这些了。Bambook功能的扩展还要指望盛大的固件开发人员的努力了,比如增加浏览器,支持原生PDF字体缩放等。

下一步计划给Bambook配一个套,这样就可以对屏幕有一个很好的保护了,否则携带起来的确不甚方便。

CBehave – 一个C语言行为驱动开发框架

Behaviour-Driven Development,即行为驱动开发在业界早已不是什么新鲜玩意了。我之前也略有了解,不过一直没有"深入钻研"。直到今年年初InfoQ的几篇有关BDD的文章才让我对BDD有了更多的认识。与TDD一样,C语言在BDD领域依旧是一个"后进分子",在多数主流语言(Java,C#,Ruby等)都已经拥有比较成熟的BDD框架(如JBehaveSpecFlowCucumber)的今天,C语言却似乎仅有一款BDD框架-CSpec可用。于是年初的时候我就把设计和实现一个用于C语言的行为驱动开发框架加入到我今年的ToDoList中了。

在确定好目标的同时,我也给这款框架命名为CBehave(模仿JBehave),并在Google Code上建立了CBehave的托管项目。但人的时间和精力总是有限的,直到8月中旬我才开始着手进行这个框架的设计和实现。设计和实现一款给程序员使用的工具,这本身就是一件让人兴奋的事情。我先是通过DanNorth的博文"Introducing BDD"(其中译版在这里)了解了BDD的"诞生历程",然后又广泛地了解了一下其他语言的BDD框架。对于CSpec这一目前唯一的C语言BDD框架,我并不想给予过多评价,不过总体来说和其他语言的BDD框架相比,CSpec有些简陋,应该说还无法很好的支持BDD中一些核心思想的表达,并且目前它还不支持Mock。这些都坚定了我重新实现一个C语言BDD框架的决心,起码我不完全是"重新发明轮子"^_^。

作为后来者,CBehave的设计参考了诸多现有的主流BDD框架,其中直接灵感来源于Cucumber,不过由于C语言静态编译语言的本质,CBehave与Cucumber在大多地方也只是形似而已。作为一篇CBehave的介绍性文章,这里列举一些CBehave的主要特点:

首先,CBehave借鉴Cucumber的设计采用Feature + Scenario结合的方式来描述功能需求(DanNorth: 需求也是行为),并且在每个Scenario内部采用BDD的经典的GIVEN-WHEN-THEN结构描述行为的验收标准(acceptance criteria)。

这里给出一个总体的行为描述模板:

FEATURE #1
    SCENARIO #1
        GIVEN
            … …
        WHEN
            … …
        THEN
            … …

    SCENARIO #2
        GIVEN
            … …
        WHEN
            … …
        THEN
            … …

    … …

FEATURE #2
… … 

FEATURE #n

原则上FEATURE之间是相互隔离的;FEATURE内部的多个Scenario之间在代码定义和执行时也是相互隔离,互不干扰的。这是一个使用该框架的基本约束。不过这就好比建议性锁,全靠使用时的自觉,否则很容易造成框架运行出错。

下面是一个真实的使用CBehave对strstr函数进行测试的例子(代码片断,完整例子参见源码cbehave/src/example/string_test.c):

FEATURE(1, "strstr")
    SCENARIO("The strstr finds the first occurrence of the substring in the source string")

        GIVEN("A source string: [Lionel Messi is a great football player]")
            char *str = "Lionel Messi is a great football player";
        GIVEN_END

        WHEN("we use strstr to find the first occurrence of [football]")
            char *p = strstr(str, "football");
        WHEN_END

        THEN("We should get the string: [football player]")
            SHOULD_STR_EQUAL(p, "football player");
        THEN_END

    SCENARIO_END

    SCENARIO("If strstr could not find the first occurrence of the substring, it will return NULL")

        GIVEN("A source string: FC Barcelona is a great football club.")
            char *str = "FC Barcelona is a great football club";
        GIVEN_END

        WHEN("we use strstr to find the first occurrence of [AC Milan]")
            char *p = strstr(buf, "AC Milan");
        WHEN_END

        THEN("We should get no string but a NULL")
            SHOULD_STR_EQUAL(p, NULL);
        THEN_END
    SCENARIO_END
FEATURE_END

int main() {
    cbehave_feature strstr_features[] = {
        {feature_idx(1)},
    };

    return cbehave_runner("Strstr Features are as belows:", strstr_features);
}

编译运行这个测试,我们会得到如下结果(节选):

Strstr Features are as belows:

Feature: strstr
 Scenario: The strstr finds the first occurrence of the substring in the source string
  Given: A source string: Lionel Messi is a great football player
  When: we use strstr to find the first occurrence of [football]
  Then: We should get the string: [football player]
 Scenario: If strstr could not find the first occurrence of the substring, it will return NULL.
  Given: A source string: FC Barcelona is a great football club.
  When: we use strstr to find the first occurrence of [AC Milan]
  Then: We should get no string but a NULL

Summary:
 total features: [1]
 failed features: [0]
 total scenarios: [2]
 failed scenarios: [0]

CBehave将strstr的行为原汁原味地输出到最终的测试结果中,与xUnit等框架相比,这确是一个进步,我们在获知测试结果的同时,还依稀中看到了这个特性的需求描述,前提是你要给出一个很好很精确的描述,但这已经不是框架可以帮助你做的了^_^。另外即使你的测试失败了,你甚至可以不通过错误提示中的源码文件名和行号信息也可以快速定位到错误的位置所在。因为错误周围是有足够的上下文信息的。

其次,CBehave支持mock。CBehave中mock的实现完全参考了我之前设计的单元测试框架LCUT,下面是一个简单的例子(片断,完整代码参加cbehave/src/example/product_database_test.c):

FEATURE(1, "Get the total count of employees")
    SCENARIO("Get the total count of employees")
        GIVEN("The db connection is ready and there are 5 employees in total");
            CBEHAVE_RETV_RETURN(connect_to_database, 0×1234);
            CBEHAVE_ARG_RETURN(table_row_count, 5);
            CBEHAVE_RETV_RETURN(table_row_count, 0);
        GIVEN_END

        WHEN("We call function: get_total_count_of_employee");
            int count = get_total_count_of_employee();
        WHEN_END

        THEN("The total count of employees we read from db should be 5")
            SHOULD_INT_EQUAL(count, 5);
        THEN_END

    SCENARIO_END
FEATURE_END

最后,CBeahve还支持多种SHOULD_XX宏,并且可根据需要灵活添加。目前已支持整型、字符串以及布尔类型的判定。

关于CBehave的实现历程这里也简单说一下。

首先需要确定CBehave的"长相",也就是CBehave采用的行为描述模板是啥样子的。这块儿确是花了我不少的时间,查看各种资料以及研究其他框架的设计,最终选择了Feature+Scenario以及用Given-When-Then来描述行为的"文档模板"。

其次,有了"文档模板"后如何将其转换为可执行的代码实体?这块也费了我不少脑细胞。思前想后,最终设计是将Feature转换成一个可运行的实体-函数。另外我在函数中使用{}来物理划分Scenario,{}可以隔离变量的可见性和作用域,已达到多个Scenarios定义和执行互不干扰的目的。

上面的标准文档结果中的一个FEATURE宏展开后的样子大致是这样的:

static void cbehave_feature_n(void *_state) {
    cbehave_state _old_state;
    cbehave_feature_entry(…, &_old_state, _state);

    {
        /* Scenario #1 */
        int _scenario_state = 0; \
        cbehave_scenario_entry(x, _state);

        … …
        cbehave_scenario_exit(&_scenario_state, _state);
    }

    {
        /* Scenario #2 */

    }

    … …
    {
        /* Scenario #n */

    }

_feature_over: \
    cbehave_feature_exit(…);
}

关于feature函数的命名,我考虑了很长时间,由于从外界输入的信息无法约束,这里我引入了Feature序号,并同时将序号作为Feature函数名的一部分。例如:
FEATURE(10, "fopen features")
展开后的feature函数名就是cbehave_feature_10,这样做对用户也有一定约束,但约束较小,只需CBehave用户保证各个Feature的序号不同即可。

在使用CBehave时,很可能出现另外一个问题:那就是测试代码在GIVEN或WHEN区段中依赖的一些资源申请或其他代码的初始化可能失败或出现异常。遇到这种情况时,用户多选择return或exit。但一旦用户这样做,CBehave就无法统计和输出测试失败情况或统计的不够准确了。为了尽量保证CBehave统计的精确性,CBehave提供了一个宏FEATURE_RETURN供用户使用。FEATURE_RETURN将控制权转移到Feature函数的末尾,也就是上面宏展开末尾的哪个_feature_over跳转标识符处,这样Feature有机会把这一错误情况记录下来。另外还可以保证其他Feature测试的继续运行。这里举个简单例子(完整代码参见cbehave/src/example/text_editor_test.c):

FEATURE(1, "Text Editor – Open Exsited File")
    SCENARIO("Open an Exsited File and write something to it")

        GIVEN("A file named foo.txt")
            FILE *fp = NULL;
            char *buf = "Hello Cbehave!";
        GIVEN_END

        WHEN("we open the file and write something to it")
            fp = fopen("foo.txt", "r+");
            if (!fp)
                FEATURE_RETURN(errno);
        WHEN_END

        THEN("We should see [Hello Cbehave] has been written into foo.txt")
            if (fp)
                fclose(fp);
        THEN_END
    SCENARIO_END
FEATURE_END

例子中如果fopen打开foo.txt失败,代码中调用了FEATURE_RETURN来应对这一情况,而不是直接调用return或exit。

最后,与LCUT不同,Cbehave会尝试运行完所有Feature的测试,而不是遇到测试错误就停止运行。

因为之前有过LCUT单元测试框架的设计和实现经验,这次CBehave框架的设计和实现就相对容易了些。CBehave的设计用了一天时间,上周末两天"百忙"中抽空完成了编码和测试,目前已提供了cbehave-0.1.0-beta版供下载体验,欢迎大家提出你的宝贵意见和建议^_^,更多关于CBehave使用方面的细节请参考CBehave用户指南(http://code.google.com/p/cbehave/wiki/CBehave_User_Guide_cn)。

BTW,我并不是一个纯粹的TDDers。我个人认为完全采用TDD或是BDD还是有一定局限的。是否采用这些方式进行开发还要视产品(或项目)的时间、质量、人员能力等诸多制约因素而定。个人推断国内的C程序员多普遍缺乏采用框架进行单元测试的意识,BDD或TDD的推广还是任重道远的。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats