函数(无副作用的函数)是一个基本的构建块,但我不知道用Java测试它们的令人满意的方法。
我正在寻找能够更轻松地测试它们的技巧。这是我想要的一个例子:
public void setUp() {
myObj = new MyObject(...);
}
// This is sooo 2009 and not what I want to write:
public void testThatSomeInputGivesExpectedOutput () {
assertEquals(expectedOutput, myObj.myFunction(someInput);
assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput);
// I don't want to repeat/write the following checks to see
// that myFunction is behaving functionally.
assertEquals(expectedOutput, myObj.myFunction(someInput);
assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput);
}
// The following two tests are more in spirit of what I'd like
// to write, but they don't test that myFunction is functional:
public void testThatSomeInputGivesExpectedOutput () {
assertEquals(expectedOutput, myObj.myFunction(someInput);
}
public void testThatSomeOtherInputGivesExpectedOutput () {
assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput);
}
我正在寻找一些可以放在测试,MyObject或myFunction上的注释,以使测试框架自动重复对myFunction的调用,对于我给出的给定输入/输出组合的所有可能的排列,或者可能的排列的一些子集,以证明该函数是有效的。
例如,在(仅)两个可能的排列之上是:
和
我应该只能提供输入/输出对(someInput,expectedOutput)和(someOtherInput,someOtherOutput),框架应该完成剩下的工作。
我没有使用过QuickCheck,但它看起来像是一个非解决方案。它被记录为发电机。我不是在寻找一种方法来为我的函数生成输入,而是一个框架,它允许我声明性地指定我的对象的哪个部分是无副作用的,并使用基于该声明的一些排列来调用我的输入/输出规范。 / p>
更新:我不打算验证对象没有任何变化,memoizing函数是这种测试的典型用例,而memoizer实际上改变了它的内部状态。但是,给定输入的输出始终保持不变。
答案 0 :(得分:7)
如果你试图测试函数 没有副作用,那么用随机参数调用并不会真正削减它。这同样适用于具有已知参数的随机调用序列。或伪随机,随机或固定种子。很有可能只有在随机发生器选择的任何调用序列中才会发生(有害的)副作用。
无论输入是什么,也有可能在您正在进行的任何呼叫的输出中实际上看不到副作用。它们的副作用可能出现在你认为没有考虑过的其他相关对象上。
如果你想测试这种东西,你真的需要实现一个“白盒”测试,在那里你看一下代码并试着弄清楚可能导致(不需要的)副作用的原因并创建基于测试用例的测试用例在那些知识上。但我认为更好的方法是仔细的手动代码检查,或使用自动静态代码分析器...如果你能找到一个能为你完成工作的那个。
OTOH,如果你已经知道这些函数是无副作用的,那么实施随机测试“以防万一”有点浪费时间,IMO。
答案 1 :(得分:3)
我不太清楚我明白你在问什么,但似乎Junit Theories(http://junit.sourceforge.net/doc/ReleaseNotes4.4.html#theories)可能是一个答案。
答案 2 :(得分:1)
在此示例中,您可以创建键/值对的映射(输入/输出),并使用从地图中选取的值多次调用测试中的方法。这不能证明该方法是有效的,但会增加概率 - 这可能就足够了。
以下是这种额外可能功能测试的快速示例:
@Test public probablyFunctionalTestForMethodX() {
Map<Object, Object> inputOutputMap = initMap(); // this loads the input/output values
for (int i = 0; i < maxIterations; i++) {
Map.Entry test = pickAtRandom(inputOutputMap); // this picks a map enty randomly
assertEquals(test.getValue(), myObj.myFunction(test.getKey());
}
}
基于命令模式可以解决更高复杂性的问题:您可以将测试方法包装在命令对象中,将命令对象添加到列表中,随机播放列表并根据命令执行命令(=嵌入式测试)那份清单。
答案 3 :(得分:0)
听起来您正在尝试测试在类上调用特定方法不会修改其任何字段。这是一个有点奇怪的测试用例,但完全有可能为它编写一个明确的测试。对于其他“副作用”,比如调用其他外部方法,它有点难度。您可以使用测试存根替换本地引用并验证它们是否未被调用,但您仍然不会以这种方式捕获静态方法调用。尽管如此,通过检查验证你在代码中没有做过类似的事情是有意义的,有时候必须足够好。
这是一种测试呼叫中没有副作用的方法:
public void test_MyFunction_hasNoSideEffects() {
MyClass systemUnderTest = makeMyClass();
MyClass copyOfOriginalState = systemUnderTest.clone();
systemUnderTest.myFunction();
assertEquals(systemUnderTest, copyOfOriginalState); //Test equals() method elsewhere
}
尝试证明一种方法是真正无副作用的,这有点不寻常。单元测试通常试图证明方法行为正确且符合合同,但它们并不意味着要替换检查代码。检查方法是否有任何可能的副作用通常是一项非常简单的练习。如果你的方法从不设置字段的值并且从不调用任何非功能性方法,那么它就是有用的。
在运行时测试这个很棘手。可能更有用的是某种静态分析。也许您可以创建一个@Functional注释,然后编写一个程序来检查程序的类以查找这些方法,并检查它们是否只调用其他@Functional方法而从不分配给字段。
随机搜索,我在这个主题上找到了某人的master's thesis。也许他有可用的工作代码。
不过,我会重申,我的建议是将注意力集中在其他地方。虽然你可以大多证明一种方法根本没有副作用,但在许多情况下通过视觉检查快速验证这一点可能会更好,并将剩余的时间集中在其他更基本的测试上。
答案 4 :(得分:0)
我担心我不再找到链接了,但是Junit 4有一些帮助功能来生成testdata。就像:
public void testData() {
data = {2, 3, 4};
data = {3,4,5 };
...
return data;
}
然后,Junit会将您的方法用于此数据。但正如我所说,我不能再找到链接(忘记关键字)的详细(和正确)示例。
答案 5 :(得分:0)
查看http://fitnesse.org/:它经常被用于验收测试,但我发现这是一种对大量数据运行相同测试的简单方法
答案 6 :(得分:0)
在junit中,您可以编写自己的测试运行器。此代码未经过测试(我不确定获取参数的方法是否会被识别为测试方法,是否需要更多的转轮设置?):
public class MyRunner extends BlockJUnit4ClassRunner {
@Override
protected Statement methodInvoker(final FrameworkMethod method, final Object test) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
Iterable<Object[]> permutations = getPermutations();
for (Object[] permutation : permutations) {
method.invokeExplosively(test, permutation[0], permutation[1]);
}
}
};
}
}
应该只提供getPermutations()实现。例如,它可以从一些带有一些自定义注释的List<Object[]>
字段中获取数据并生成所有排列。
答案 7 :(得分:0)
我认为您缺少的术语是“参数化测试”。然而,在.Net风格的jUnit中似乎更乏味。在NUnit中,以下测试对所有组合执行6次。
[Test]
public void MyTest(
[Values(1,2,3)] int x,
[Values("A","B")] string s)
{
...
}
对于Java,您的选项似乎是: