concordion简要
- concordion的实现可以理解为活文档,自然语言,易读,包含很多examples。文档是html,所以构建出可以navigable structure的文档。 频繁的运行可以使文档和系统功能同步。如果系统行为发生了变化,相应的文档就会fail
- concordion文档不包含实现细节,把需求和实现分开,包含列子,实现方式可变,方便重构
- concordion是Junit的扩展
- 易学,基于specifications by example、
- 不需要特定的语言格式,使用自然语言,关注易读性
- 丰富的测试报告。 HTML格式的报告可以加入超链接和图片等多种
- 文档可以当成unit test来运行。具备和Junit测试一样的深度
- BA QA DEV可以一起合作写出文档
- 文档可以适用于不同级别 unit, component, subsystem, system
- 活文档可以用来定义story的验收条件,帮助PO来控制项目范围
- 提高测试覆盖率。不管实现如何,需求一定会被满足
concordion实现
一个concordion规格文档由两部分组成
描述功能的html文件
测试代码 (基于Junit)
两个文件应该在同一个文件夹下
为了两个文档的融合使用,html里面应该包含concordion的相关命令。这个命令以结点的属性形式出现
Hello World!
测试代码基于Junit,方法名需要和html中的名字一样,使用ConcordionRunner和Junit的RunWith来让concordion把相关的命令和类联系起来
package example;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@RunWith(ConcordionRunner.class)
public class HelloWorldFixture {
public String getGreeting() {
return "Hello World!";
}
}
测试报告输出默认目录是 system property: java.io.tmpdir.
concordion使用方法
concordion:assertEquals:验证执行方法的返回值和所期待的结果一致
concordion:set:定义一个变量
The greeting for user Bob
will be:
Hello Bob!
concordion:execute:可以执行一个测试代码里面的方法
一般来说,执行一个没有返回值的方法,要用set或是setUp开头。
If the time is
09:00AM
then the greeting will say:
Good Morning World!
利用#TEXT这个特殊变量可以简写上面的spec。可以不使用concordion:set。
在把#TEXT作为参数传给方法,后面接着#TEXT的值
If the time is
09:00AM
then the greeting will say:
Good Morning World!
执行一个有返回值的方法,可以使用方法接受返回值
The full name
John Smith
will be broken into first name
John
and last name
Smith.
这里的#result是一个类,split()方法返回一个类对象. firstName and lastName 是类属性
处理不寻常的语句结构
当我们面对规格文档描述不利于concordion命令的实现的时候,我们可以把execute命令放到更上一层结点结构当中
The greeting "Hello Bob!"
should be given to user Bob
when he logs in.
execute命令的执行是有一定的顺序的
- 首先是处理所有child的set命令
- 然后是自己的命令
- 然后是自己child的execute命令
- 最后是所有child的assertEquals方法
concoirdion:execute在table里面的使用
concordion:execute on a
Full Name | First Name | Last Name |
---|---|---|
John Smith | John | Smith |
David Peterson | David | Peterson |
但是这样看起来也是比较重复的
所以concordion支持把concordion命令放在table heading级别,每一个td就会重复th的命令
下面的列子中,execute在table级别,set, assertEquals在table heading级别
Full Name
First Name
Last Name
John Smith
John
Smith
David Peterson
David
Peterson
concordion:execute在list上的使用
concordion:execute on a 可以给方法传递有层级的测试数据
当我们使用concordion在列表上的时候,比如ol 和 ul 元素, 该execute命令会执行在每一个列表元素上。
这个特性能帮我们设置有层级结构的测试数据
- Europe
- Austria
- Vienna
- UK
- England
- Scotland
- France
- Australia
当执行到 Searching for "arr" will return:
Username john.lennon ringo.starr george.harrison paul.mccartney
Matching Usernames george.harrison ringo.starr
verifyRows的语法是: var : iteration_object
#loopVar : expression
expression是一个可迭代对象并且对象里面的元素对象顺序是已知的
loopVar能够访问返回迭代对象的每一个元素,并且支持使用assertEquals去进行验证
从expression返回的对象元素的顺序必须与行里设置的测试数据是一致的
注解
当我们把未完成的测试代码加入到我们的build当中,为了避免使build失效,我们可以加注解。
- @ExpectedToPass 期待pass
- @ExpectedToFail 期待fail
- @Unimplemented 未完成
For example:
import org.concordion.api.ExpectedToFail;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@ExpectedToFail
@RunWith(ConcordionRunner.class)
public class GreetingTest {
public String greetingFor(String firstName) {
return "TODO";
}
}
Fail-Fast
当出现异常的时候,concordion会继续执行剩下的测试来展示所有的问题。使用Fail-Fast可以使concordion遇到第一个异常时就停止继续执行,跳出测试。
方法是使用注解
Fail-Fast有一个参数onExceptionType,这个参数是一个列表,列表里面的内容是各种异常类型,只有在执行当中遇到列表里面的异常类型,Fail-Fast才会生效。
import org.concordion.api.FailFast;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@FailFast(onExceptionType={DatabaseUnavailableException.class, IOException.class})
public class MyDataTest {
public void connectToDatabase() {
....
}
}
concordion:run 可以使该文档link到其他文档,并且run
concordion:assertTrue 验证执行方法返回值是不是True
concordion:assertFalse 验证执行方法返回值是不是False
When user Bob
logs in, the greeting will be:
Hello Bob!
The first name starts
with B.
The first name starts
with C.
支持Junit4.5以上, 测试代码使用ConcordionRunner
package example;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@RunWith(ConcordionRunner.class)
public class HelloWorldFixture {
public String getGreeting() {
return "Hello World!";
}
}
返回值的类型 也可以是map
public Map split(String fullName) {
String[] words = fullName.split(" ");
Map results = new HashMap();
results.put("firstName", words[0]);
results.put("lastName", words[1]);
return results;
}
可以使用MultiValueResult来返回多个结果
MultiValueResult 是concordion api的一个类
package example;
import org.concordion.api.MultiValueResult;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@RunWith(ConcordionRunner.class)
public class SplittingNamesTest {
public MultiValueResult split(String fullName) {
String[] words = fullName.split(" ");
return multiValueResult()
.with("firstName", words[0])
.with("lastName", words[1]);
}
}
使用这个方法,我们可以在测试代码里面设置比如属性firstName的值,然后在specification里面直接调用
John
特殊变量 #HREF
当我们想使用外部文件作为测试数据时,可以使用#HREF指向一个link,这个link就是数据源文件。
比如我们把测试数据放在一个csv文件里面,我们就可以在html中写一个link,然后在这个中使用concordion命令和#HREF
test file
blah.csv
Write specifications, not scripts
只在Specification中描述feature和功能,不要有太详细的步骤,具体的实现细节都应该放在测试代码里面。
Specification可以说是high level的测试脚本
concordion:echo
is able to print the variable to test report
Example
When user World
logs in, the greeting will be:
Hello, World!
Username: # this is the variable created in specification file
Example
When user World
logs in, the greeting will be:
Hello, World!
Username: # this is the class variable from test fixture code
@RunWith(ConcordionRunner.class)
public class HelloWorldFixture {
public String username = "username";
public String greetingFor(String firstName) {
return new Greeter().greetingFor(firstName);
}
}