1

函数(无副作用的函数)是这样一个基本的构建块,但我不知道在 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 的调用,或者某些子集可能的排列,以证明该函数是功能性的。

例如,上面(仅)两个可能的排列是:

  • myObj = new MyObject();
  • myObj.myFunction(someInput);
  • myObj.myFunction(someOtherInput);

和:

  • myObj = new MyObject();
  • myObj.myFunction(someOtherInput);
  • myObj.myFunction(someInput);

我应该只能提供输入/输出对(someInput,expectedOutput)和(someOtherInput,someOtherOutput),其余的由框架完成。

我没有使用过 QuickCheck,但它似乎不是一个解决方案。它被记录为生成器。我不是在寻找一种为我的函数生成输入的方法,而是一个让我以声明方式指定我的对象的哪一部分是无副作用的框架,并使用基于该声明的一些排列来调用我的输入/输出规范。

更新:我不想验证对象没有任何变化,记忆功能是这种测试的典型用例,记忆器实际上改变了它的内部状态。但是,给定一些输入的输出始终保持不变。

4

8 回答 8

7

如果您正在尝试测试这些函数是否没有副作用,那么使用随机参数调用并不会真正减少它。这同样适用于具有已知参数的随机调用序列。或伪随机,带有随机或固定种子。很有可能(有害的)副作用只会发生在随机发生器选择的任何调用序列中。

也有可能在您正在进行的任何调用的输出中实际上不会看到副作用......无论输入是什么。它们的副作用可能会出现在您不认为要检查的其他一些相关对象上。

如果你想测试这种东西,你真的需要实现一个“白盒”测试,你可以在其中查看代码并尝试找出可能导致(不需要的)副作用的原因,并根据这些知识创建测试用例. 但我认为更好的方法是仔细的手动代码检查,或者使用自动静态代码分析器......如果你能找到一个可以为你完成这项工作的工具。

OTOH,如果您已经知道这些功能没有副作用,那么实施随机测试“以防万一”有点浪费时间,IMO。

于 2010-08-09T07:16:23.647 回答
3

我不太确定我明白你在问什么,但似乎 Junit Theories ( http://junit.sourceforge.net/doc/ReleaseNotes4.4.html#theories ) 可能是一个答案。

于 2010-08-09T07:19:36.977 回答
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());
   }
}

复杂度较高的问题可以基于命令模式解决:您可以将测试方法包装在命令对象中,将命令对象添加到列表中,对列表进行打乱并根据该列表执行命令(= 嵌入式测试)。

于 2010-08-09T07:35:33.337 回答
0

听起来您正在尝试测试在类上调用特定方法不会修改其任何字段。这是一个有点奇怪的测试用例,但完全可以为它编写一个清晰的测试。对于其他“副作用”,例如调用其他外部方法,这有点困难。您可以用测试存根替换本地引用并验证它们没有被调用,但您仍然不会以这种方式捕获静态方法调用。尽管如此,通过检查来验证您在代码中没有做类似的事情是微不足道的,有时这必须足够好。

这是测试通话中没有副作用的一种方法:

public void test_MyFunction_hasNoSideEffects() {
   MyClass systemUnderTest = makeMyClass();
   MyClass copyOfOriginalState = systemUnderTest.clone();
   systemUnderTest.myFunction();
   assertEquals(systemUnderTest, copyOfOriginalState); //Test equals() method elsewhere
}

试图证明一种方法真正没有副作用是有点不寻常的。单元测试通常试图证明一个方法的行为正确并符合合同,但它们并不意味着取代检查代码。检查方法是否有任何可能的副作用通常是一个非常简单的练习。如果您的方法从不设置字段的值并且从不调用任何非功能性方法,那么它就是功能性的。

在运行时测试这个很棘手。可能更有用的是某种静态分析。也许您可以创建一个@Functional 注释,然后编写一个程序来检查您程序的类中的此类方法,并检查它们是否只调用其他@Functional 方法并且从不分配给字段。

随机搜索,我发现某人的硕士论文正是关于这个主题的。也许他有可用的工作代码。

尽管如此,我还是要重申,我的建议是您将注意力集中在其他地方。虽然您可以在很大程度上证明一种方法根本没有副作用,但在许多情况下,最好通过目视检查快速验证这一点,并将剩余的时间集中在其他更基本的测试上。

于 2010-08-09T07:14:44.273 回答
0

我认为您缺少的术语是“参数化测试”。然而,在 jUnit 中似乎比在 .Net 风格中更乏味。在 NUnit 中,以下测试使用所有组合执行 6 次。

[Test]
public void MyTest(
    [Values(1,2,3)] int x,
    [Values("A","B")] string s)
{
    ...
}

对于 Java,您的选择似乎是:

  • JUnit在版本 4 中支持这一点。但是它有很多代码(看起来,jUnit 坚持不接受参数的测试方法)。这是侵入性最小的。
  • DDSteps,一个 jUnit 插件。请参阅此视频,该视频从适当命名的 excel 电子表格中获取值。您还需要编写一个映射器/夹具类,将电子表格中的值映射到夹具类的成员中,然后用于调用 SUT。
  • 最后,你有 Fit/ Fitnesse。它和 DDSteps 一样好,只是输入数据是 HTML/Wiki 形式。您可以从 Excel 表格粘贴到 Fitnesse 中,只需按一下按钮,它就会正确格式化。您还需要在这里编写一个夹具类。
于 2010-08-09T10:19:18.963 回答
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[]>使用一些自定义注释注释的某个字段中获取数据并生成所有排列。

于 2010-08-09T09:46:05.077 回答
0

恐怕我找不到链接了,但是Junit 4有一些帮助功能来生成testdata。就像是:

public void testData() {
  data = {2, 3, 4};
  data = {3,4,5 };
...
 return data;
}

然后 Junit 将使用您的方法将这些数据。但正如我所说,我无法再找到链接(忘记关键字)以获得详细(和正确)的示例。

于 2010-08-09T07:54:57.163 回答
0

看看http://fitnesse.org/:它经常用于验收测试,但我发现它是一种针对大量数据运行相同测试的简单方法

于 2010-08-09T08:53:49.577 回答