0

私は、SFTP サービスをテストできるモック フレームワークを起動して実行するのに非常に苦労しました。EasyMock、PowerMock、JMockit には慣れていましたが、最終的に GMock に行き着きました。test ('org.gmock:gmock:0.8.2') { excludes 'junit' }

ハッピー パス テストが正常に実行されたので、再試行ロジックを作成してから、失敗シナリオを作成します。私は今、2つの問題に直面しています。Grails と GMock のほとんどすべてがまばらに文書化されているため、これらの解決策を見つけることができないようです。

テスト中のメソッド:このブログの SFTP を JCraft の JSch の例で使用しており、ニーズに合わせて少し拡張しました。接続用の資格情報とファイル名を取得します。FileOutputStream を作成し、SFTP サーバーに接続します。例外が発生した場合は、n 回再試行します (ここでは SO の目的で簡略化しています)。

/**
 * Transfers the file from the remote input server to the local output server.
 *
 * @param fileName
 *      - the file name
 * @param inputFtpCredential
 *      - the input server
 * @param outputFtpCredential
 *      - the output server
 * @param mode
 *      - the mode for the transfer (defaults to {@link ChannelSftp#OVERWRITE}
 * @throws SftpException if any IO exception occurs. Anything other than
 *      {@link ChannelSftp#SSH_FX_NO_SUCH_FILE SSH_FX_NO_SUCH_FILE} or {@link
 *      ChannelSftp#SSH_FX_PERMISSION_DENIED SSH_FX_PERMISSION_DENIED} may cause a retry
 */
public void transferRemoteToLocal(String fileName, FtpCredential inputFtpCredential, FtpCredential outputFtpCredential, Integer mode = ChannelSftp.OVERWRITE) {
    for (retryCounter in 0 .. maxRetries) {
        FileOutputStream output
        try {
            File file = new File(outputFtpCredential.remoteBaseDir, fileName);
            // set stream to append if the mode is RESUME
            output = new FileOutputStream(file, (mode == ChannelSftp.RESUME));

            /*
             * getting the file length of the existing file. This is only used
             * if the mode is RESUME
             */
            long fileLength = 0
            if (file.exists())
                fileLength = file.length()
            load (output, fileName, inputFtpCredential, mode, fileLength)
            // success
            return
        } catch (exception) {
            // if an exception is thrown then retry a maximum number of times
            if (retryCounter < maxRetries) {
                // let the thread sleep so as to give time for possible self-resets
                log.info "Retry number ${retryCounter+1} of file $fileName transfer after $sleepDuration ms"
                Thread.sleep(sleepDuration)
                mode = ChannelSftp.RESUME
            } else {
                int exceptionID = (exception instanceof SftpException)?(exception as SftpException).id:0
                throw new SftpException(exceptionID, "Max number of file transfer retries ($maxRetries) exceeded on file $fileName", exception)
            }
        } finally {
            if (output != null)
                output.close()
        }
    }
}

def load(OutputStream outputStream, String fileName, FtpCredential ftpCredential, Integer mode, Long fileIndex = 0)
throws SocketException, IOException, SftpException, Exception  {
    connect(ftpCredential) { ChannelSftp sftp ->
        sftp.get(fileName, outputStream, mode, fileIndex)
    }
}

したがって、これはブログのメソッドと連携して機能します。ハッピー パスのシナリオを書き、GMock で動作するようにしました。

public void testSavingRemoteToLocal_Success() throws JSchException {
    // Holders for testing
    String fileToTransfer = 'test_large_file.txt'
    FtpCredential localCredential = new FtpCredential()
    // populate credential
    FtpCredential remoteCredential = new FtpCredential()
    // populate credential

    // Mocks
    File mockFile = mock(File, constructor(localCredential.remoteBaseDir, fileToTransfer))
    mockFile.exists().returns(false)

    FileOutputStream mockFOS = mock(FileOutputStream, constructor(mockFile, false))

    // connection
    JSch mockJSch = mock(JSch, constructor())
    Session mockSession = mock(Session)
    ChannelSftp mockChannel = mock(ChannelSftp)

    mockJSch.getSession(remoteCredential.username, remoteCredential.server, remoteCredential.port).returns(mockSession)
    mockSession.setConfig ("StrictHostKeyChecking", "no")
    mockSession.password.set(remoteCredential.password)
    mockSession.connect().once()
    mockSession.openChannel("sftp").returns(mockChannel)
    mockChannel.connect()
    mockChannel.cd(remoteCredential.remoteBaseDir).once()

    // transfer
    mockChannel.get (fileToTransfer, mockFOS, ChannelSftp.OVERWRITE, 0)

    // finally method mocks
    mockChannel.exit()
    mockSession.disconnect()
    mockFOS.close()

    // Test execution
    play {
        service.transferRemoteToLocal(fileToTransfer, remoteCredential, localCredential)
    }
}

エラー 1 : 次に、単純なコピー/貼り付けを行い、テスト メソッド名以外を変更しなかったため、次のエラーが発生しました。

java.lang.StackOverflowError
at java.lang.ref.SoftReference.get(SoftReference.java:93)
at org.codehaus.groovy.util.ManagedReference.get(ManagedReference.java:41)
at org.codehaus.groovy.util.ManagedConcurrentMap$Entry.isEqual(ManagedConcurrentMap.java:62)
at org.codehaus.groovy.util.AbstractConcurrentMap$Segment.getOrPut(AbstractConcurrentMap.java:91)
at org.codehaus.groovy.util.AbstractConcurrentMap.getOrPut(AbstractConcurrentMap.java:35)
at org.codehaus.groovy.reflection.ClassInfo.getClassInfo(ClassInfo.java:103)
at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:227)
at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallStaticSite(CallSiteArray.java:59)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:146)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:55)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:55)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:55)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)

そしてこれがしばらく続きます。

エラー 2:次に、ハッピー パスをコメント アウトして、再試行シナリオを実行することにしました。だから私はどこでも .times(2) を使用しようとしましたが、コンストラクターの .times(2) が好きではありませんでした。そうしないと、再試行によってすべてが閉じられ、再試行時に再インスタンス化されるため、コンストラクターが 2 回呼び出されるため、エラーが発生します。

次に、失敗するまでのすべての 2 つのモックを作成しようとしましたが、2 番目の FileOutputStream モックの構築中に何らかの NPE がスローされました。ファイルで比較を行っているようです。

public void testSavingRemoteToLocal_RetryOnce() throws JSchException {
    // Holders for testing
    String fileToTransfer = 'test_large_file_desktop.txt'
    FtpCredential localCredential = new FtpCredential()
    // populate credential
    FtpCredential remoteCredential = new FtpCredential()
    // populate credential

    // Mocks
    // First loop that fails
    File mockFile2 = mock(File, constructor(inputCredential.remoteBaseDir, fileToTransfer))
    mockFile2.exists().returns(false)

    FileOutputStream mockFIO2 = mock(FileOutputStream, constructor(mockFile2, false))

    // connection
    JSch mockJSch2 = mock(JSch, constructor())
    Session mockSession2 = mock(Session)

    mockJSch2.getSession(outputCredential.username, outputCredential.server, outputCredential.port).returns(mockSession2)
    mockSession2.setConfig ("StrictHostKeyChecking", "no")
    mockSession2.password.set(outputCredential.password)
    mockSession2.connect().raises(new SftpException(0, "throw an exception to retry"))
    mockSession2.disconnect()
    mockFIO2.close()

    // second loop that passes
    File mockFile = mock(File, constructor(inputCredential.remoteBaseDir, fileToTransfer))
    mockFile.exists().returns(false)

    FileOutputStream mockFIO = mock(FileOutputStream, constructor(mockFile, true)) // <-- Fails here with a NPE in mockFile.compareTo

    // connection
    JSch mockJSch = mock(JSch, constructor())
    Session mockSession = mock(Session)
    ChannelSftp mockChannel = mock(ChannelSftp)

    mockJSch.getSession(outputCredential.username, outputCredential.server, outputCredential.port).returns(mockSession)
    mockSession.setConfig ("StrictHostKeyChecking", "no")
    mockSession.password.set(outputCredential.password)
    mockSession.connect()
    mockSession.openChannel("sftp").returns(mockChannel)
    mockChannel.connect()
    mockChannel.cd(outputCredential.remoteBaseDir)

    // transfer
    mockChannel.get (fileToTransfer, mockFIO, FtpMonitor.getInstance(assetId), ChannelSftp.RESUME, 0)

    // finally method mocks
    mockChannel.exit()
    mockSession.disconnect()
    mockFIO.close()

    // Test execution
    play {
        service.sleepDuration = 200
        service.sftpCopyFrom(outputCredential, inputCredential, fileToTransfer, assetId )
    }

    // Assert the results
}
4

1 に答える 1

1

Gmock 0.8.3 を試しましたか? これに関連するいくつかのバグを修正したことを覚えています。

于 2013-07-03T15:05:13.653 に答える