2019独角兽企业重金招聘Python工程师标准>>>

首先普及一下概念,什么是Fitnesse,听一听.NET版Cucumber的创始人Aslak Hellesøy谈Fitnesse与Cucumber对比:

FIT/Fitnesse和Cucumber都执行高级语言编写的验收测试。FIT仅识别HTML,Fitnesse则通过提供Wiki语法来简化编写测试的过程。在FIT/Fitnesse当中,所有的测试都以表格的形式呈现。
FitNesse比Cucumber的优势在于Wiki支持。

原文链接:http://www.infoq.com/cn/news/2009/11/interview-cucumber-for-dotnet

1.Scenario是什么

Fitneese的SliM UserGuide中介绍了 Scenario

原文是这么介绍Scenario的:

A Scenario table is a table that can be called from other tables; namely Script Table and Decision Table.

The format of a Scenario table is the same as the format of a Script Table, but with a few differences. You can see a Scenario table in action here.

Scenario是一种Table,可以被Script Table 和 Decision Table调用。

由此很多人都对Scenario报了很大的期望,希望能用Scenario模块化封装测试步骤。

2.Scenario能力展示

下面是我结合Script示例和Scenario示例写的一个Scenario演示用例:

wiki文本:

!define TEST_SYSTEM {slim}
!path classes|import|
|fitnesse.slim.test|!4 定义scenario checkLogin: 登录并检查结果
| scenario | checkLogin | u || p || ensure || logged |
| @{ensure} | login with username | @{u} | and password | @{p} |
| check @{logged} | login message | @{u} logged in. |
| show | number of login attempts |!4 创建script实例,后面调用scenario都是针对这个实例
| script | login dialog driver | Bob | xyzzy |!4 Invoking a scenario from a !-DecisionTable-!
| checkLogin |
| u | p | ensure | logged |
| Bob | xyzzy | ensure |  |
| Bob | zzyxx | reject | not |
| Cat | xyzzy | reject | not |!4 Invoking a scenario from a !-ScriptTable-!
| script |
| checkLogin | Bob || zzyxx || reject || not |
| checkLogin | Bob || xyzzy || ensure ||  |!4 script原示例
| script | login dialog driver | Bob | xyzzy |
| login with username | Bob | and password | xyzzy |
| check | login message | Bob logged in. |
| reject | login with username | Bob | and password | bad password |
| check | login message | Bob not logged in. |
| check not | login message | Bob logged in. |
| ensure | login with username | Bob | and password | xyzzy |
| note | this is a comment |
| show | number of login attempts |
| $symbol= | login message |The fixture for this table is:{{{public class LoginDialogDriver {private String userName;private String password;private String message;private int loginAttempts;public LoginDialogDriver(String userName, String password) {this.userName = userName;this.password = password;}public boolean loginWithUsernameAndPassword(String userName, String password) {loginAttempts++;boolean result = this.userName.equals(userName) && this.password.equals(password);if (result)message = String.format("%s logged in.", this.userName);elsemessage = String.format("%s not logged in.", this.userName);return result;}public String loginMessage() {return message;}public int numberOfLoginAttempts() {return loginAttempts;}
} }}}

测试用例页面:

点击Test执行后:

展开DecisionTable调用Scenario的测试结果:

展开ScriptTable调用Scenario的测试结果:

至此,我们看到Scenario可以把Script步骤封装起来,取个模块名,然后使用DecisionTable或ScriptTable调用。

3.Scenario的局限

请注意调用Scenario前的这一行:

目的是在调用Scenario前先创建好Script实例。

如果去掉这一句,再执行,是这样的结果:

再尝试一下,把创建Script实例的语句塞到Scenario中:

!4 定义scenario checkLogin: 登录并检查结果
| scenario | checkLogin | u || p || ensure || logged |
| script | login dialog driver | Bob | xyzzy |   <--这是新加的创建Script实例的语句
| @{ensure} | login with username | @{u} | and password | @{p} |
| check @{logged} | login message | @{u} logged in. |
| show | number of login attempts |

保存后执行测试:

4.不满意怎么办?

我还想使用Scenario封装TableTable,比如RestFixture定义的TableTable, 国外最著名的软件开发问答网站stackoverflow.com也在问: Can I make a scenario of RestFixture table in fitnesse?, or is there another way to make reusable components?

我准备修改Fitneese代码,使得Scenario能直接封装ScriptTable和TableTable,请往下看……

5.修改ScenarioTable.java,使Scenario能直接封装ScriptTable

Scenario的源代码在目录D:\git\FitnesseKit\fitnesse\src\fitnesse\testsystems\slim\tables下:

打开ScenarioTable.java后,关键代码是Scenario的参数@xxx是怎么替换的:

      @Overridepublic String substitute(String content) throws SyntaxError {for (Map.Entry<String, String> scenarioArgument : scenarioArguments.entrySet()) {String arg = scenarioArgument.getKey();if (getInputs().contains(arg)) {String argument = scenarioArguments.get(arg);content = StringUtil.replaceAll(content, "@" + arg, argument);content = StringUtil.replaceAll(content, "@{" + arg + "}", argument);} else {throw new SyntaxError(String.format("The argument %s is not an input to the scenario.", arg));}}return content;}});

增加两行打印System.out.println:

      @Overridepublic String substitute(String content) throws SyntaxError {
+      System.out.println("ScenarioTable.call.substitute <<<<<<<<<< content:" + content);for (Map.Entry<String, String> scenarioArgument : scenarioArguments.entrySet()) {String arg = scenarioArgument.getKey();if (getInputs().contains(arg)) {String argument = scenarioArguments.get(arg);content = StringUtil.replaceAll(content, "@" + arg, argument);content = StringUtil.replaceAll(content, "@{" + arg + "}", argument);} else {throw new SyntaxError(String.format("The argument %s is not an input to the scenario.", arg));}}
+      System.out.println("ScenarioTable.call.substitute >>>>>>>>>> content:" + content);return content;}

在D:\git\FitnesseKit\fitnesse\src\fitnesse\testsystems\slim\tables\SlimTable.java的构造函数SlimTable中增加一行打印:

      public SlimTable(Table table, String id, SlimTestContext testContext) {
+      System.out.println("SlimTable.SlimTable table:"+table);this.id = id;this.table = table;this.testContext = testContext;tableName = getTableType() + "_" + id;}

目的是查看每次启动的测试Table,比如一次import,一次ScriptTable,一次DecisionTable,一次TableTable,等等。

使用命令ant compile重新编译Fitnesse,并输入ant run重新启动Fitneese: D:\git\FitnesseKit\fitnesse>ant compile ... D:\git\FitnesseKit\fitnesse>ant run

再次运行刚刚失败的测试,现在看命令行打印:

 [java] ScenarioTable.call.substitute <<<<<<<<<< content:<table>[java]     <tr>[java]             <td>scenario</td>[java]             <td>checkLogin</td>[java]             <td>u</td>[java]             <td></td>[java]             <td>p</td>[java]             <td></td>[java]             <td>ensure</td>[java]             <td></td>[java]             <td>logged</td>[java]     </tr>[java]     <tr>[java]             <td>Script</td>[java]             <td>login dialog driver</td>[java]             <td>Bob</td>[java]             <td colspan="6">xyzzy</td>[java]     </tr>[java]     <tr>[java]             <td>@{ensure}</td>[java]             <td>login with username</td>[java]             <td>@{u}</td>[java]             <td>and password</td>[java]             <td colspan="5">@{p}</td>[java]     </tr>[java]     <tr>[java]             <td>check @{logged}</td>[java]             <td>login message</td>[java]             <td colspan="7">@{u} logged in.</td>[java]     </tr>[java]     <tr>[java]             <td>show</td>[java]             <td colspan="8">number of login attempts</td>[java]     </tr>[java] </table>[java] ScenarioTable.call.substitute >>>>>>>>>> content:<table>[java]     <tr>[java]             <td>scenario</td>[java]             <td>checkLogin</td>[java]             <td>u</td>[java]             <td></td>[java]             <td>p</td>[java]             <td></td>[java]             <td>ensure</td>[java]             <td></td>[java]             <td>logged</td>[java]     </tr>[java]     <tr>[java]             <td>Script</td>[java]             <td>login dialog driver</td>[java]             <td>Bob</td>[java]             <td colspan="6">xyzzy</td>[java]     </tr>[java]     <tr>[java]             <td>ensure</td>[java]             <td>login with username</td>[java]             <td>Bob</td>[java]             <td>and password</td>[java]             <td colspan="5">xyzzy</td>[java]     </tr>[java]     <tr>[java]             <td>check </td>[java]             <td>login message</td>[java]             <td colspan="7">Bob logged in.</td>[java]     </tr>[java]     <tr>[java]             <td>show</td>[java]             <td colspan="8">number of login attempts</td>[java]     </tr>[java] </table>[java] SlimTable.SlimTable table:[[scenario,checkLogin,u,,p,,ensure,,logged],[Script, login dialog driver, Bob, xyzzy], [ensure, login with username, Bob, and password, xyzzy], [check , login message, Bob logged in.], [show, number of login attempts]]

再去运行一个没有被Scenario的封装的Script:

| Script | login dialog driver | Bob | xyzzy |
| ensure | login with username | Bob | and password | xyzzy |
| check | login message | Bob logged in. |
| show | number of login attempts |

命令行打印如下内容:

 [java] SlimTable.SlimTable table:[[Script, login dialog driver, Bob, xyzzy], [ensure, login with username, Bob, and password, xyzzy], [check , login message, Bob logged in.], [show, number of login attempts]]

对比一下两种运行的打印:

[java] SlimTable.SlimTable table:[[scenario,checkLogin,u,,p,,ensure,,logged],[Script, login dialog driver, Bob, xyzzy], [ensure, login with username, Bob, and password, xyzzy], [check , login message, Bob logged in.], [show, number of login attempts]]


[java] SlimTable.SlimTable table:[[Script, login dialog driver, Bob, xyzzy], [ensure, login with username, Bob, and password, xyzzy], [check , login message, Bob logged in.], [show, number of login attempts]]

只要想办法在运行封装时,去掉**[scenario,checkLogin,u,,p,,ensure,,logged],**,说不定就可以了。

接下去,修改substitute函数:

      public String substitute(String content) throws SyntaxError {System.out.println("ScenarioTable.call.substitute <<<<<<<<<< content:" + content);
+       int trLeftFirstIndex = content.indexOf("<tr>");
+       int trRightFirstIndex = content.indexOf("</tr>");
+       int trLeftSecondIndex = content.indexOf("<tr>", trLeftFirstIndex + 1);
+       int trRightSecondIndex = content.indexOf("</tr>", trRightFirstIndex + 1);
+       int scriptIndex = content.toLowerCase().indexOf("<td>script</td>");
+       if(scriptIndex > trLeftSecondIndex && scriptIndex < trRightSecondIndex) {
+         StringBuffer removeFirstTr = new StringBuffer();
+         removeFirstTr.append(content.substring(0, trLeftFirstIndex));
+         removeFirstTr.append(content.substring(trRightFirstIndex + "</tr>".length()));
+         content = removeFirstTr.toString();
+       }for (Map.Entry<String, String> scenarioArgument : scenarioArguments.entrySet()) {String arg = scenarioArgument.getKey();if (getInputs().contains(arg)) {String argument = scenarioArguments.get(arg);content = StringUtil.replaceAll(content, "@" + arg, argument);content = StringUtil.replaceAll(content, "@{" + arg + "}", argument);} else {throw new SyntaxError(String.format("The argument %s is not an input to the scenario.", arg));}}System.out.println("ScenarioTable.call.substitute >>>>>>>>>> content:" + content);return content;}

再次编译,运行Fitneese:

耶,一击中的!

具体的代码在 git.oschina.net

6.尝试用Scenario封装TableTable

因为RestFixture是用TableTable实现的,所以我还想用Scenario封装TableTable,以便在使用RestFixture时,可以模块化组织测试步骤。

首先看一个TableTable例子:

!define TEST_SYSTEM {slim}!path D:\git\FitnesseKit\RestFixture\target\dependencies\*
!path D:\git\FitnesseKit\RestFixture\target\classes
!path D:\git\FitnesseKit\RestFixture\extra\slf4j-simple-1.6.6.jar| import |
| smartrics.rest.fitnesse.fixture |获取开始时间
| Table:Rest Fixture | http://localhost:${FITNESSE_PORT} |
| let | begin | js | (new Date()).getTime() | |调用某个服务,这里用 sleep 5秒 模拟
| Table:Rest Fixture | http://localhost:${FITNESSE_PORT} |
| let | sleepMiliSeconds | js | {{{
var start = (new Date()).getTime();
var now;
do {now = (new Date()).getTime();
} while(now - start < 5000);
now - start }}} | |获取结束时间
| Table:Rest Fixture | http://localhost:${FITNESSE_PORT} |
| let | end | js | (new Date()).getTime() | |打印调用服务所花时间
| Table:Rest Fixture | http://localhost:${FITNESSE_PORT} |
| let | spendSeconds | js | (%end% - %begin%) / 1000 | |

测试结果是这样的:

本测试用例的主要目的是检查调用某个服务所花的时间,本例子是5秒。

接下去我想把上面的获取当前时间,调用服务,计算所花时间都写成Scenario,然后用Script调用Scenario,使测试步骤具有良好的可读性:

!define TEST_SYSTEM {slim}!path D:\git\FitnesseKit\RestFixture\target\dependencies\*
!path D:\git\FitnesseKit\RestFixture\target\classes
!path D:\git\FitnesseKit\RestFixture\extra\slf4j-simple-1.6.6.jar| import |
| smartrics.rest.fitnesse.fixture |获取当前时间
| scenario |  getTime | _t |
| Table:Rest Fixture | http://localhost:${FITNESSE_PORT} |
| let | @{_t} | js | (new Date()).getTime() | |计算所花时间
| scenario |  spendSeconds | _s || beginTime || endTime |
| Table:Rest Fixture | http://localhost:${FITNESSE_PORT} |
| let | @{_s} | js | (@{endTime} - @{beginTime}) / 1000 | |调用某个服务,用sleep模拟
| scenario |  sleep | s |
| Table:Rest Fixture | http://localhost:${FITNESSE_PORT} |
| let | sleepMiliSeconds | js | {{{
var start = (new Date()).getTime();
do {var now = (new Date()).getTime();
} while(now - start < @{s} * 1000);
now - start }}} | |打印调用某个服务所花时间
| script |
| getTime | begin |
| sleep | 5 |
| getTime | end |
| spendSeconds | spend || %begin% || %end% |

测试结果是这样的:

保存内容是The instance scriptTableActor. does not exist,意思为从已定义的script中找不到。

修改ScenarioTable.java后,测试结果:

ScenarioTable.java的主要修改内容:

请到git.oschina.net具体查看。

转载于:https://my.oschina.net/fitnessefan/blog/299488

介绍并扩展Fitnesse的测试模块化机制:Scenario相关推荐

  1. 现代富文本编辑器Quill的模块化机制

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师. 官方网站:devui.design Ng组件库:ng-devui(欢 ...

  2. 【华为云技术分享】现代富文本编辑器Quill的模块化机制

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师. 官方网站:devui.design Ng组件库:ng-devui(欢 ...

  3. linux模块化机制,Linux模块化机制和module_init

    > 引子:模块化机制优点 模块化机制(module)是Linux系统的一大创新,是Linux驱动开发和运行的基础(当然,module并不仅仅是支撑驱动).其优点在于: 1.在系统运行动态加载模块 ...

  4. epoll监听文件_介绍一下 Android Handler 中的 epoll 机制?

    介绍一下 Android Handler 中的 epoll 机制? 目录: IO 多路复用 select.poll.epoll 对比 epoll API epoll 使用示例 Handler 中的 e ...

  5. html5 扩展属性,HTML5属性的介绍和扩展.doc

    HTML5属性的介绍和扩展 HTML5 视频 HTML5 规定了一种通过 video 元素来包含视频的标准方法. 如需在 HTML5 中显示视频,您所有需要的是: 音频 Canvas 一 简单图形整套 ...

  6. 一文介绍备机重建各种方法的实现机制

    摘要:本文将介绍备机重建各种方法的实现机制,并结合应用场景分析,以及对新增参数的使用建议,以期获得最佳应用效果. 本文分享自华为云社区<先码再看,一文介绍备机重建各种方法的实现机制>,原文 ...

  7. ORAN专题系列-29:运营商O-RAN扩展皮站测试的硬件架构

    作者主页:文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客 本文网址:原创 ORAN专题系列-29:运营商O-RAN扩展皮站测试的硬件架构_文火冰糖(王文兵)的博客-CSDN博客 目录 第1 ...

  8. es6模块化机制及CommonJS与ES6模块化规范区别

    模块化机制 CommonJS模块化规范 导入 1. let { firstName, lastName } = require('./1-module.js') 导出 1. module.export ...

  9. 介绍两个ios手机测试的辅助工具

    介绍两个ios手机测试的辅助工具,分别是同步助手和itools,都能很方便的安装.卸载.备份ipa,越狱,截图,查看实时日志,崩溃日志,这样能在app出错或崩溃时进行分析定位. 同步助手还有实时桌面, ...

最新文章

  1. 使用nc检测udp端口是否可以正常通讯
  2. windows下备份Mysql数据的脚本
  3. shell 函数定义和调用
  4. primefaces_通过OmniFaces缓存组件以编程方式缓存PrimeFaces图表
  5. java随机数排序算法_理解快速排序算法
  6. CentOS7搭建NTP服务器
  7. 在Spring Boot + Mybatis 中,使用@Repository失效
  8. “命令终端”的实现2-字符读取及按键控制
  9. 《从0到1学习Flink》—— Flink 项目如何运行?
  10. BZOJ3653: 谈笑风生
  11. qa 芯片测试_关于半导体设备测试,看这一篇就够了
  12. 强化学习之Q-Learning(附代码)
  13. 软件工程毕设(二)·任务书
  14. 冒泡排序(java代码实现)
  15. python怎么读单词_利用Python制作查单词小程序(一):抓取来自百度翻译的单词释义和音标...
  16. Android开关按键(左右复选)
  17. 第三方登入时昵称出现emoji表情致mysql插入失败
  18. 服务器两广豪杰维护,逆水寒11月22日更新到几点 世界boss即将掉落逆水之寒
  19. zabbix 5.0所有依赖包_开源的Zabbix报表系统ZbxTable正式发布!
  20. ESP8266利用Bliker、小爱同学和本地按钮控制4路开关

热门文章

  1. basler相机参数简要中文说明_附下载| OpenCV最新中文版官方教程
  2. 华为云NP考试题库_华为认证网络工程师怎么考
  3. python dataframe 中位数_python下的Pandas中DataFrame基本操作(一),基本函数整理
  4. 测试打桩_DNF:CEO实测旭旭宝宝红眼,打桩高达2494E,伤害超越狂人剑魂
  5. calipso是什么意思_眰恦是什么意思?
  6. python答辩结束语_Beta答辩总结
  7. 【蓝桥java】递归基础之39级台阶
  8. Redis数据库设置密码
  9. 取消对 null 指针“l”的引用。_C++中的引用
  10. 仅需6步,教你轻易撕掉app开发框架的神秘面纱(3):构造具有个人特色的MVP模式