繁体   English   中英

如何简化Java中无副作用方法的测试?

[英]How can I simplify testing of side-effect free methods in Java?

函数(无副作用的函数)是这样的基本构建块,但是我不知道用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,但这似乎是一个非解决方案。 它被记录为生成器。 我不是在寻找一种生成函数输入的方法,而是一个框架,该框架允许我声明性地指定对象的哪一部分是无副作用的,并使用基于该声明的一些排列来调用输入/输出规范。

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

如果您要测试这些函数是否没有副作用,那么使用随机参数进行调用实际上并不会减少它。 具有已知参数的随机调用序列也是如此。 或伪随机,具有随机或固定种子。 有很大的机会,(有害的)副作用只会在随机发生器选择的任何调用序列中发生。

不管输入是什么,都有可能在您进行的任何调用的输出中实际上看不到副作用。 它们的副作用可能会影响您不希望检查的其他一些相关对象。

如果您想测试这种事情,您确实需要实现“白盒”测试,在测试中您可以查看代码并尝试找出可能导致(不必要的)副作用的原因,并基于该知识创建测试用例。 。 但是我认为,更好的方法是仔细的手动代码检查,或使用自动的静态代码分析器...如果可以找到适合您的方法。

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

我不太确定我是否理解您的要求,但似乎Junit理论( http://junit.sourceforge.net/doc/ReleaseNotes4.4.html#theories )可能是答案。

在此示例中,您可以创建键/值对(输入/输出)的映射,并使用从映射中选取的值多次调用被测方法。 这不能证明该方法是可行的,但是会增加概率-这可能就足够了。

这是这种额外的可能起作用的测试的快速示例:

@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());
   }
}

可以根据命令模式来解决复杂性更高的问题:您可以将测试方法包装在命令对象中,将命令对象添加到列表中,将列表随机排列,然后根据该列表执行命令(=嵌入式测试)。

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

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

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

试图证明一种方法是真正无副作用的,这有点不寻常。 单元测试通常试图证明一种方法能够正确执行并符合合同规定,但是它们并不意味着取代检查代码。 通常,检查一种方法是否有任何可能的副作用是一项非常容易的练习。 如果您的方法从不设置字段的值并且从不调用任何非功能性方法,则说明该功能是有功能的。

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

我随便逛逛,发现了关于这个主题的某人的硕士论文 也许他有可用的工作代码。

不过,我会重复一遍,这是我的建议,建议您将注意力集中在其他地方。 尽管您可以证明一种方法根本没有副作用,但在很多情况下,通过目视检查快速验证该方法并将剩余时间集中在其他更基础的测试上可能会更好。

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

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

然后,Junit将以您的方法将这些数据存储。 但是正如我所说,我找不到详细(正确)示例的链接(忘记了关键字)。

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

在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[]>字段中获取数据,并生成所有排列。

我认为您所缺少的术语是“ 参数化测试 ”。 但是,与.Net风格相比,在jUnit中似乎更加乏味。 在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中,然后按一下按钮即可正确设置其格式。 您也需要在此处编写一个夹具类。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM