摘要:在验收测试框架Fitneese中,使用Scenario可以把最常用的测试步骤封装起来,从而达到模块化定义Fitnesse测试用例的能力。但Scenario仅限于封装Script测试步骤,Script实例要先创建,然后才能调用;Scenario也不能封装Table。本文后半部分展示修改Fitneese代码,扩展Scenario的封装范围。
首先普及一下概念,什么是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文本:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
!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);
else
message = 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中:?
1
2
3
4
5
6
|
! 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是怎么替换的:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Override public 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:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Override public 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中增加一行打印:?
1
2
3
4
5
6
7
|
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:?
1
2
3
|
D:\git\FitnesseKit\fitnesse>ant compile ... D:\git\FitnesseKit\fitnesse>ant run |
再次运行刚刚失败的测试,现在看命令行打印:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
[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:?
1
2
3
4
|
| 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 | |
命令行打印如下内容:?
1
|
[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函数:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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例子:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
!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,使测试步骤具有良好的可读性:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
!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具体查看。
相关推荐
本文介绍了一种基于FitNesse的自动化回归测试工具(Automated Regression Testing Tool, ARTT)的设计与实现。 #### 2. FitNesse的测试流程 FitNesse提供了一个集成化的测试环境,支持多种类型的测试,包括单元...
FitNesse 作为一种集成化的测试框架,不仅可以帮助开发团队更高效地进行协作,还能在开发过程中及时发现问题并进行修正。通过本文介绍的基本操作流程,用户可以快速上手 FitNesse 并开展测试工作。随着进一步学习和...
Praegus开源工具链FitNesse测试的测试输出侦听器。 | | 安装 该侦听器仅与Java 11+兼容,因此请确保您正在使用Java 11运行FitNesse测试。 将此项目添加为pom的依赖项: < groupId>io.orangebeard < artifactId>...
FitNesse-Selenium-FrameWork 是一个用于自动化验收测试的框架,它巧妙地结合了FitNesse测试套件和Selenium WebDriver的能力。这个框架的主要目标是简化Web应用程序的端到端测试过程,确保代码的质量和功能符合预期...
4. **运行测试**: 使用Maven命令,如`mvn fitnesse:run`,启动并运行Fitnesse服务器,执行所有测试用例。插件会自动处理服务器的启动、停止以及测试结果的报告。 **fitnesse-launcher-sample-master** "fitnesse-...
Fitnesse自动化框架是一款强大的开源测试工具,专为软件开发团队设计,以支持各种协议和编程语言。这个框架的核心理念是实现测试代码与业务逻辑的分离,使得非程序员也能参与到测试过程中,提升整个项目的协作效率。...
Fitnesse-Demos 适应性测试框架演示在这个项目中,我编写了一些具有某些基本功能(例如Math Utilities)的简单Java类。 为了演示Fitnessfit的功能,我编写了一些“ fixture代码”(例如称为“ MathFixture.java”的...
Fitnesse-jdbc-slim JDBC固定装置。 允许在多个数据库连接上运行SQL命令。 该项目是由许可的。安装此模块和spring依赖项必须在。 您可以从或通过下载jar(请参见下文)。 夹具将用于连接的jdbc驱动程序也必须位于类...
FitNesse是一款强大的开源自动化测试框架,主要用于进行回归测试,特别是在软件质量保证和系统健壮性提升方面扮演着至关重要的角色。随着软件测试自动化趋势的发展,各种自动化测试工具应运而生,但早期的测试工具...
开源测试软件-fitnesse,使用方法自己百度,教程很多
#### FitNesse:自动化验收测试的利器 FitNesse是一款开源的自动化框架,特别适用于验收测试。它提供了一个易于理解和维护的界面,允许非技术背景的业务分析师参与测试案例的编写,从而确保测试案例与业务需求的...
1. **软件测试**: FitNesse支持自动化测试,特别适合于验收测试驱动开发(Acceptance Test Driven Development, ATDD)。它允许测试人员编写易于理解的测试案例,这些案例可以自动执行并提供即时反馈,确保软件功能...
3. `fitlibrary.jar`:可能包含了Fitnesse的特定测试库或扩展,供测试脚本使用。 4. `license.txt`:包含了该开源软件的许可证信息,解释了软件的使用权限和限制。 5. `README.txt`:通常会提供关于如何安装、配置和...
- **start**:启动测试,需要提供用于自动化测试的fixture类名作为参数。 - **check**:执行一个方法并验证其返回值是否符合预期。 - **press**:执行一个void方法,但不对结果进行任何验证。 - **enter**:用于模拟...
fitnesse学习资料:关于fitnesse测试软件的一些整理文档
FitNesse fixtures 是一组可以在测试自动化工具 FitNesse (fitnesse.org) 中使用的类
欢迎使用FitNesse,它是完全集成的独立验收测试框架和Wiki。 要开始使用,请访问 !快速开始和错误追踪器有错误或功能要求吗? 。社区有不是功能要求或错误报告的问题吗? 边缘构建可在FitNesse的最新稳定版本。 ...
Fitnesse是一个强大的开源工具,主要用于软件测试的自动化和协作。它结合了Wiki的便捷性和Fit框架的测试能力,提供了一种简洁的方式来定义和执行测试用例。在本文中,我们将深入探讨Fitnesse的学习,特别是如何使用...
- **编写基本测试**:介绍如何使用FitNesse的ColumnFixture特性来编写和运行测试,以及如何用平易近人的语言描述测试案例。 - **实战环节**:鼓励读者亲自实践,通过编写具体的测试案例来加深对TDD和FitNesse的...
- **提高效率**:FitNesse支持自动化测试,减少了手动测试的工作量,提高了测试效率。 #### 四、如何使用FitNesse实施ATDD ##### 4.1 开发三剑客工作坊 在开发三剑客工作坊中,QA团队与开发人员、产品专家共同...