12

次の動作を実現したいと考えています。テスト対象のクラスが他のクラスに依存しています。この依存関係を jMock でモックしたいと考えています。ほとんどのメソッドはいくつかの標準値を返しますが、スタブ実装を呼び出したいメソッドが 1 つありwill(...)ます。モックされたメソッドに渡されました。

テスト

@Test
public void MyTest(){
    Mockery context = new Mockery() {
        {
            setImposteriser(ClassImposteriser.INSTANCE);
        }
    };
    IDependency mockObject = context.mock(IDependency.class);
    Expectations exp = new Expectations() {         
        {
            allowing(mockObject).methodToInvoke(????);
            will(stubMethodToBeInvokedInstead(????));
        }       
    };      
}

インターフェース

public interface IDependency {
    public int methodToInvoke(int arg);
}

代わりに呼び出されるメソッド

public int stubMethodToBeInvokedInstead(int arg){
    return arg;
}

では、モックされているメソッドに渡されたパラメーターをキャプチャして、代わりにスタブ化されたメソッドに渡すにはどうすればよいでしょうか?

編集

INameSource別の例を挙げると、クラス Speaker をテストするために、次の (C#) コードで依存関係をモックしたいとします。

public class Speaker
{
  private readonly string firstName;
  private readonly string surname;
  private INameSource nameSource ;
 public Speaker(string firstName, string surname, INameSource nameSource)
  {
    this.firstName = firstName;
    this.surname = surname;
    this.nameSource = nameSource;
  }
  public string Introduce()
  {
    string name = nameSource.CreateName(firstName, surname);
    return string.Format("Hi, my name is {0}", name);
  }
}
public interface INameSource
{
  string CreateName(string firstName, string surname);
}

これは、Rhino Mocks for C# で実行できる方法です。Java にはデリゲートがないため、これほど簡単ではないことは理解しています。

4

3 に答える 3

22

Duncan のソリューションはうまく機能しますが、カスタム マッチャーに頼らない、より単純なソリューションもあります。CustomActions 呼び出しメソッドに渡される Invocation 引数を使用するだけです。この引数で、呼び出しから値を取得する getParameter(long i) メソッドを呼び出すことができます。

だからこれの代わりに

return matcher.getLastValue();

これを使って

return (Integer) invocation.getParameter(0);

これで、StoringMatcher はもう必要ありません: Duncans の例は次のようになります。

@RunWith(JMock.class)
public class Example {

  private Mockery context = new JUnit4Mockery();

  @Test
  public void Test() {

    final IDependency mockObject = context.mock(IDependency.class);

    context.checking(new Expectations() {
      {
        // No custom matcher required here
        allowing(mockObject).methodToInvoke(with(any(Integer.class)));

        // The action will return the first argument of the method invocation.
        will(new CustomAction("returns first arg") {
          @Override
          public Object invoke(Invocation invocation) throws Throwable {
            return (Integer) invocation.getParameter(0);
          }
        });
      }
    });

    Integer test1 = 1;
    Integer test2 = 1;

    // Confirm the object passed to the mocked method is returned
    Assert.assertEquals((Object) test1, mockObject.methodToInvoke(test1));
    Assert.assertEquals((Object) test2, mockObject.methodToInvoke(test2));
  }

  public interface IDependency {
    public int methodToInvoke(int arg);
  }
于 2012-11-12T19:52:29.737 に答える
1

アウグストのように、私はこれが一般的に良い考えであるとは確信していません。しかし、私はちょっとした遊びを我慢できませんでした。提供された引数を格納して返すカスタム マッチャーとカスタム アクションを作成しました。

注: これは本番環境で使用できるコードとはほど遠いものです。私はちょうどいくつかの楽しみを持っていました。ソリューションを証明する自己完結型の単体テストを次に示します。

@RunWith(JMock.class)
public class Example {

  private Mockery context = new JUnit4Mockery();

  @Test
  public void Test() {

    final StoringMatcher matcher = new StoringMatcher();
    final IDependency mockObject = context.mock(IDependency.class);

    context.checking(new Expectations() {
      {
        // The matcher will accept any Integer and store it
        allowing(mockObject).methodToInvoke(with(matcher));

        // The action will pop the last object used and return it.
        will(new CustomAction("returns previous arg") {
          @Override
          public Object invoke(Invocation invocation) throws Throwable {
            return matcher.getLastValue();
          }
        });
      }
    });

    Integer test1 = 1;
    Integer test2 = 1;

    // Confirm the object passed to the mocked method is returned
    Assert.assertEquals((Object) test1, mockObject.methodToInvoke(test1));
    Assert.assertEquals((Object) test2, mockObject.methodToInvoke(test2));
  }

  public interface IDependency {
    public int methodToInvoke(int arg);
  }

  private static class StoringMatcher extends BaseMatcher<Integer> {

    private final List<Integer> objects = new ArrayList<Integer>();

    @Override
    public boolean matches(Object item) {
      if (item instanceof Integer) {
        objects.add((Integer) item);
        return true;
      }

      return false;
    }

    @Override
    public void describeTo(Description description) {
      description.appendText("any integer");
    }

    public Integer getLastValue() {
      return objects.remove(0);
    }
  }
}

より良い計画

具体的な例を示したので、上記の JMock ハッカーに頼らずに Java でこれをテストする方法を紹介できます。

まず、あなたが投稿したもののいくつかの Java バージョン:

public class Speaker {
  private final String firstName;
  private final String surname;
  private final NameSource nameSource;

  public Speaker(String firstName, String surname, NameSource nameSource) {
    this.firstName = firstName;
    this.surname = surname;
    this.nameSource = nameSource;
  }

  public String introduce() {
    String name = nameSource.createName(firstName, surname);
    return String.format("Hi, my name is %s", name);
  }
}

public interface NameSource {
  String createName(String firstName, String surname);
}

public class Formal implements NameSource {
  @Override
  public String createName(String firstName, String surname) {
    return String.format("%s %s", firstName, surname);
  }    
}

次に、最初に求めていたものに頼ることなく、クラスのすべての便利な機能を実行するテスト。

@RunWith(JMock.class)
public class ExampleTest {

  private Mockery context = new JUnit4Mockery();

  @Test
  public void testFormalName() {
    // I would separately test implementations of NameSource
    Assert.assertEquals("Joe Bloggs", new Formal().createName("Joe", "Bloggs"));
  }

  @Test
  public void testSpeaker() {
    // I would then test only the important features of Speaker, namely
    // that it passes the right values to the NameSource and uses the
    // response correctly
    final NameSource nameSource = context.mock(NameSource.class);
    final String firstName = "Foo";
    final String lastName = "Bar";
    final String response = "Blah";

    context.checking(new Expectations() {
      {
        // We expect one invocation with the correct params
        oneOf(nameSource).createName(firstName, lastName);
        // We don't care what it returns, we just need to know it
        will(returnValue(response));
      }
    });

    Assert.assertEquals(String.format("Hi, my name is %s", response),
        new Speaker(firstName, lastName, nameSource).introduce());
  }
}
于 2012-09-04T13:45:59.163 に答える
0

JMockは、ユースケース(またはJavaで私が知っている他のモックフレームワーク)をサポートしていません。

私の頭の中には、あなたがやろうとしていることは理想的ではなく、単体テストが複雑になる可能性があるという小さな声があります(多分それはあまりにも多くのコード/ロジックをテストしていますか?)。私が見ている問題の1つは、それらのモックが返す必要のある値がわからず、他の何かをプラグインしているため、各実行が再現不可能になる可能性があることです。

于 2012-08-31T09:46:26.757 に答える