3

モックされた呼び出しを行う前にタイムスタンプが自動的に更新されるこのようなものに最適な方法は何でしょうか?

ここに私がテストしようとしているもののいくつかのダミーコードがあります:

public class ThingWithATimestamp {
    public Long timestamp;
    public String name;

    public ThingWithATimestamp(String name) {
        this.name = name;
    }
}

public class TheClassThatDoesStuff {
    private ThingConnector connector;

    public TheClassThatDoesStuff(ThingConnector connector) {
        this.connector = connector;
    }

    public void updateTheThing(MyThingWithATimestamp thing) {
        thing.timestamp = currentTimestamp();
        connector.update(thing);            
    }
}

ここに私がテストしたいものがあります:

public class TheClassThatDoesStuffTests {
    @Test
    public void canUpdateTheThing() {
        ThingConnector connector = mock(ThingConnector.class);
        TheClassThatDoesStuff doer = new ThisClassThatDoesStuff(connector);

        doer.updateTheThing(new ThingWithATimestamp("the name"));

        verify(connector, times(1)).update(SomeMatcherThatICantFigureOut);
    }

このコードがかなり馬鹿げていることはわかっていますが、私が検証しようとしていることを正確に表していると思います。基本的に、タイムスタンプが現在の時刻の X 以内にあることを確認するためにテストに記入するマッチャーが必要です。これにより、タイムスタンプが正しく更新さconnector.updateれ、オブジェクトの適切なタイムスタンプで呼び出されたことがわかります。

4

3 に答える 3

9

タイム クリティカルなコードを処理する最も堅牢な方法は、タイム クリティカルな関数をすべて独自のクラスにラップすることです。私は通常それを呼びますTimeHelper。したがって、このクラスは次のようになります。

import java.util.Date;
public class TimeHelper{
    public long currentTimeMillis(){
        return System.currentTimeMillis();
    }
    public Date makeDate(){
        return new Date();
    }
}

また、同じタイプのメソッドがさらにある場合があります。現在、そのような関数を使用するクラスには、(少なくとも) 2 つのコンストラクターが必要です。アプリケーションで使用する通常のコンストラクターと、aTimeHelperがパラメーターであるパッケージ プライベートのコンストラクターです。これTimeHelperは、後で使用するために保管する必要があります。

public class ClassThatDoesStuff {
    private ThingConnector connector;
    private TimeHelper timeHelper;

    public ClassThatDoesStuff(ThingConnector connector) {
        this(connector, new TimeHelper());
    }

    ClassThatDoesStuff(ThingConnector connector, TimeHelper timeHelper) {
        this.connector = connector;
        this.timeHelper = timeHelper;
    } 
}

さて、クラス内で、 を書く代わりにSystem.currentTimeMillis()、 を書きtimeHelper.currentTimeMillis()ます。もちろん、これはまったく同じ効果があります。ただし、クラスは魔法のようにはるかにテストしやすくなっています。

クラスをテストするときは、 のモックを作成しますTimeHelper。このモックを (Mockito のwhenand thenReturn、または代わりにを使用してdoReturn) 任意の時間値 (テストに必要なものは何でも) を返すように構成します。currentTimeMillis()テスト中にを複数回呼び出す場合は、ここで複数の値を返すこともできます。

次に、2 番目のコンストラクターを使用して、テストするオブジェクトを作成し、モックを渡します。これにより、テストで使用される時間値を完全に制御できます。アサーションまたは検証で、正確に正しい値が使用されていることをアサーションすることができます。

public class ClassThatDoesStuffTest{
    @Mock private TimeHelper mockTime;
    @Mock private ThingConnector mockConnector;
    private ClassThatDoesStuff toTest;

    @Test
    public void doesSomething(){
        // Arrange
        initMocks(this);
        when(mockTime.currentTimeMillis()).thenReturn(1000L, 2000L, 5000L);
        toTest = new ClassThatDoesStuff(mockConnector, mockTime);

        // Act
        toTest.doSomething();

        // Assert
        // ... ???
    }
}        

これを行うと、テストが常に機能し、オペレーティング システムのタイム スライス ポリシーに依存することがないことがわかります。また、タイムスタンプがおおよその間隔内にあると主張するのではなく、タイムスタンプの正確な値を検証する権限もあります。

于 2012-06-15T08:22:13.693 に答える
2

初め:

class TimestampInInterval extends ArgumentMatcher< ThingWithATimestamp > {
      public boolean matches(Object arg) {
          ThingWithATimestamp thing = (ThingWithATimestamp) arg;
          long delta = thing.timestamp - System.currentTimeMillis();
          return delta < INTERVAL;
      }
   }

2番:

verify(connector, times(1)).update(argThat(new TimestampInInterval());
于 2012-06-14T23:00:25.233 に答える