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 への呼び出しを自動的に繰り返すことができます。関数が機能的であることを証明するための可能な順列。

たとえば、上記の (唯一の) 2 つの可能な順列は次のとおりです。

  • 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

If you are trying to test that the functions are side-effect free, then calling with random arguments isn't really going to cut it. The same applies for a random sequence of calls with known arguments. Or pseudo-random, with random or fixed seeds. There's a good chance are that a (harmful) side-effect will only occur with any of the sequence of calls that your randomizer selects.

There is also a chance that the side-effects won't actually be visible in the outputs of any of the calls that you are making ... no matter what the inputs are. They side-effects could be on some other related objects that you didn't think to examine.

If you want to test this kind of thing, you really need to implement a "white-box" test where you look at the code and try and figure out what might cause (unwanted) side-effects and create test cases based on that knowledge. But I think that a better approach is careful manual code inspection, or using an automated static code analyser ... if you can find one that would do the job for you.

OTOH, if you already know that the functions are side-effect free, implementing randomized tests "just in case" is a bit of a waste of time, 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

It sounds like you're attempting to test that invoking a particular method on a class doesn't modify any of its fields. This is a somewhat odd test case, but it's entirely possible to write a clear test for it. For other "side effects", like invoking other external methods, it's a bit harder. You could replace local references with test stubs and verify that they weren't invoked, but you still won't catch static method calls this way. Still, it's trivial to verify by inspection that you're not doing anything like that in your code, and sometimes that has to be good enough.

Here's one way to test that there are no side effects in a call:

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

It's somewhat unusual to try to prove that a method is truly side effect free. Unit tests generally attempt to prove that a method behaves correctly and according to contract, but they're not meant to replace examining the code. It's generally a pretty easy exercise to check whether a method has any possible side effects. If your method never sets a field's value and never calls any non-functional methods, then it's functional.

Testing this at runtime is tricky. What might be more useful would be some sort of static analysis. Perhaps you could create a @Functional annotation, then write a program that would examine the classes of your program for such methods and check that they only invoke other @Functional methods and never assign to fields.

Randomly googling around, I found somebody's master's thesis on exactly this topic. Perhaps he has working code available.

Still, I will repeat that it is my advice that you focus your attention elsewhere. While you CAN mostly prove that a method has no side effects at all, it may be better in many cases to quickly verify this by visual inspection and focus the remainder of your time on other, more basic tests.

于 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があります。入力データがHTML/Wiki形式であるという事実を除けば、DDStepsと同じくらい優れています。ExcelシートからFitnessに貼り付けることができ、ボタンを押すだけで正しくフォーマットされます。ここでもフィクスチャクラスを作成する必要があります。
于 2010-08-09T10:19:18.963 に答える
0

http://fitnesse.org/をご覧ください: 受け入れテストによく使用されますが、膨大な量のデータに対して同じテストを実行する簡単な方法であることがわかりました

于 2010-08-09T08:53:49.577 に答える
0

リンクが見つからないのではないかと心配していますが、Junit 4 にはテストデータを生成するためのヘルプ機能がいくつかあります。そのような:

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

Junit は、メソッドがこのデータを処理します。しかし、私が言ったように、詳細な (そして正しい) 例へのリンクがもう見つかりません (キーワードを忘れました)。

于 2010-08-09T07:54:57.163 に答える
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 に答える