“PhraseFixture”的优点——比用于 FIT 和 FitNesse 验收测试框架的 DoFixture() 更好





5.00/5 (2投票s)
FitLibrary 中强大的 DoFixture() 模拟了类英文的规范。PhraseFixture 提案将其提升到了一个新的水平。
“PhraseFixture” 提案
背景
本文假定您已了解 FIT (Framework for Integrated Test) 和 FitLibrary。
概述
对于那些使用过 FitLibrary
的 DoFixture
的读者来说,您可能熟悉 ChatStart
示例(FitLibraryUserGuide.DoFixture.WritingFixtures
)。如下所示:
- 第一个表格的 fixture 是一个
DoFixture
,因此创建的流程 fixture 对象会处理其余的表格。
ChatStart |
- 第二个表格包含一个动作,该动作被映射到(初始)流程 fixture 对象的
connectUser()
方法,如下所示:
connect user | sarah |
- 第三个表格包含两个动作,它们也应用于流程 fixture 对象。
用户 | sarah | creates | fit | Room |
用户 | sarah | enters | fit | Room |
DoFixture
试图做的是让表格数据更具可读性。例如,在上面的表格中,最后一行试图尽可能地接近人们在英语中会说的话:“User Sarah enters fit room”。这里有两个问题:
- 短语被拆分到表格的多个列中,这似乎很奇怪。当大多数人看到表格时,他们通常会想到一个网格,其中行是“记录”,每一列代表某种字段。对于 FIT 中的
TableFixture
来说,表格是一个很好的方法。但是DoFixture
将表格的隐喻推向了极限。当人们创建表格,其中每一行根据“参数”的数量具有不同的列数时,情况会变得更糟。 - 另一个问题是,即使忽略了短语在表格中的事实,“User Sarah enters fit room”听起来也很奇怪。难道不应该交换“fit”和“room”吗?好吧,这样听起来会更好,但对于
DoFixture
来说却行不通。这是因为DoFixture
依赖于命令和参数在列之间交替。在上面包含“sarah
”和“fit
”这几个词的五列中,它们构成了参数,而“user
”、“enters
”和“room
”这几个词被连接起来以对应 fixture 方法userEntersRoom()
;。请参阅下面的类:
public class ChatStart : DoFixture
{
private ChatRoom chat = new ChatRoom();
public ChatStart()
{
setSystemUnderTest(chat);
}
public bool ConnectUser(string userName)
{
return chat.connectUser(userName);
}
public bool UserCreatesRoom(string userName, string roomName)
{
return chat.userCreatesRoom(userName, roomName);
}
public bool UserEntersRoom(string userName, string roomName)
{
return chat.userEntersRoom(userName, roomName);
}
}
提案:PhraseFixture()
有可能让 fixture 变得足够智能,即使短语中的单词不遵循列交替顺序也能找到正确的方法。此外,如果使用空格作为分隔符,那么行看起来会好得多。例如:
Chat Start |
Connect user sarah |
User sarah creates room fit |
User sarah enters room fit |
这肯定比
ChatStart | ||||
connect user | sarah | |||
用户 | sarah | creates | fit | room |
用户 | sarah | enters | fit | room |
看起来更好,并且可能对业务分析师来说更容易输入到表格中。
实现这一点的方法很简单,就是尝试单词的组合,直到找到匹配的方法,而未使用的单词则成为参数。所以对于第四行,“User
”、“Enters
”和“Room
”这几个词被匹配到 UserCreatesRoom()
方法,未使用的单词“Sarah
”和“fit
”成为方法的参数。
缺点
每个解决方案都有其自身的问题。虽然这比 DoFixture
读起来更好,但由于使用空格作为单词分隔符,包含空格的 string
参数将需要以其他方式处理。提案要求将 string
放在引号中。因此,例如,如果上面的用户名是“sarah connor
”而不是“sarah
”,那么作者就必须输入:
User “sarah connor” enters room fit |
这还不错。
算法
该算法非常简单,也许可以改进。但它的工作原理是这样的。举一个简单的例子,假设短语中的单词是“Connect user sarah
”。它会查找以下方法名称,并且顺序确保首先查找最长可能的方法名称:
ConnectUserSarah()
UserSarah()
ConnectSarah()
Sarah()
ConnectUser()
User()
Connect()
(加粗的那个显然是会被找到的,因为该方法存在于 fixture 中。并且由于该特定匹配没有使用“Sarah
”这个词,所以这个词将成为 ConnectUser()
函数的参数。)
我相信可以想出更智能的算法,但这里包含的算法足以证明可行性。
支持代码
const int cBitsInInt = 32;
static uint ReverseBits(uint x)
{
uint h = 0;
uint i = 0;
for (h = i = 0; i < cBitsInInt; i++)
{
h = (h << 1) + (x & 1);
x >>= 1;
}
return (uint)h;
}
static uint ReverseAndShift(uint x, int length)
{
return ReverseBits(x) >> (cBitsInInt - length);
}
static bool FindMethod(MethodInfo[] methods, string[] columns,
out MethodInfo foundMethod, out List<string> parameters)
{
uint bits = (uint)(1 << columns.Length);
for (uint i = bits - 1; i >= 0 && i <= cBitsInInt; i--)
{
string s = "";
parameters = new List<string>();
uint x = ReverseAndShift(i, columns.Length);
for (int j = 0; j < columns.Length; j++)
{
if ((1 & x) > 0)
s += columns[j].ToLowerInvariant();
else
parameters.Add(columns[j]);
x >>= 1;
}
foreach (MethodInfo method in methods)
{
if (method.Name.ToLowerInvariant() == s)
{
foundMethod = method;
return true;
}
}
}
foundMethod = null;
parameters = null;
return false;
}
Kenneth Kasajian (ken@kasajian.com)