如何正确地对从FTP服务器获取文件的类进行单元测试

时间:2017-02-03 08:49:46

标签: junit mockito ftp-client

事实上,我的问题分为两部分。

  1. 如何从外部隔离测试,但仍然确保功能有效?
  2. 如何使用Mockito模拟apache commons FtpClient类?当我嘲笑它时,我得到null:
  3.   

    InputStream inputStream =            ftpClient.retrieveFileStream(ftpParameters.getSourceFileName());

    这是测试类:

    public class SimpleFtpFileImporterTest {
       FtpParameters ftpParams =new FtpParameters();
       SimpleFtpFileImporter fileImporter=new SimpleFtpFileImporter();
       FTPClient ftpMock= mock(FTPClient.class);
    
    
    
    @Before
    public void startup(){
        this.ftpParams.setServer("10.0.206.126");
        this.ftpParams.setPort(21);
        this.ftpParams.setUserName("mikola");
        this.ftpParams.setPassword("password");
        this.ftpParams.setSourceFileName("readme.txt");
    }
    
    
    @Test
    public void returnNullWhenFileCouldNotBeFetchedCompletely() throws IOException{
        when(ftpMock.completePendingCommand()).thenReturn(false);
        fileImporter=new SimpleFtpFileImporter(ftpMock);
        byte[] bytes= fileImporter.downloadFile(ftpParams);
        assertNull(bytes);
    }
    
    }
    

    这是被测系统:

    public class SimpleFtpFileImporter implements IFileImporter {
    
    private FTPClient ftpClient;
    
    static Logger logger = Logger.getLogger(SimpleFtpFileImporter.class);
    
    static {
        PropertyConfigurator.configure("config/log4j.properties");
    }
    
    /**
     * Creates a SimpleFtpFileImporter class instance passing an FtpClient.
     * This constructor helps create unit tests by passing any kind of FTPClient, eg. an http ftp client.
     *
     * @param ftpClient An FTPClient object
     */
    public SimpleFtpFileImporter(FTPClient ftpClient) {
        this.ftpClient = ftpClient;
    }
    
    public SimpleFtpFileImporter() {
    
    }
    /**
     * Gets the file specified from the specified FTP server
     *
     * @param ftpParameters An FtpParametrs object that bears the needed information
     * @return File in byte array if successful, otherwise null
     */
    public byte[] downloadFile(FtpParameters ftpParameters) {
        if (this.ftpClient == null)
            this.ftpClient = new FTPClient();
        if (!ftpParameters.isProperlyPopulated()) {
            logger.warn("Not all FTP parameters have been set. Execution will halt.");
            throw new FtpParametersNotSetException("Ftp parameters not properly set.");
        }
        try {
            ftpClient.connect(ftpParameters.getServer());
            ftpClient.login(ftpParameters.getUserName(), ftpParameters.getPassword());
            ftpClient.enterLocalPassiveMode();
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            logger.info("FTP connection succesfully established. Preparing to retrieve file:"+ftpParameters.getSourceFileName());
    
            InputStream inputStream = ftpClient.retrieveFileStream(ftpParameters.getSourceFileName());
            if (inputStream != null) {
                byte[] bytes = IOUtils.toByteArray(inputStream);
                boolean success = ftpClient.completePendingCommand();
                logger.info("File received");
                inputStream.close();
                if (success) {
                    return bytes;
                } else{
                    logger.warn("File fetching process could not be through. Returning null.");
                    return null;
                }
            }else{
                logger.warn("Wrong file name specified. File name:"+ftpParameters.getSourceFileName());
                throw new RuntimeException("Wrong file name specified");
            }
    
    
        } catch (IOException ex) {
            logger.error("Problem while trying to get file from remote FTP. Message: " + ex.getMessage() + " \n\r" + ex);
        }
    
        return null;
    }
    

    }

    虽然我提供了所有需要的参数(主机,端口,用户名和密码),但似乎模拟FtpClient对象不适合制作真实的东西

1 个答案:

答案 0 :(得分:3)

要回答到目前为止可以回答的部分 - 您必须学习如何编写可测试的代码;一个好的起点是videos

你身边已经存在误解:似乎嘲笑FtpClient对象不适合制作真实的东西

完全。模拟是模拟,空测试存根。它没有做任何真实的事情。这是使用模拟的全部要点。它们是空壳,除了提供您为它们指定的行为之外什么都不做。 我的意思是:Mockito创建的对象是真正的FtpClient。它只是一个模拟,看起来像一个FtpClient。它与“真正的”FtpClient“类没有任何连接。换句话说:是的,你可以调用FtpClient上的方法,但它们都为空。他们什么也不做(好吧,他们做你指定他们做的事情)。

关键是:你使用模拟来完全与外部实现分离。通过为测试中的代码提供模拟,您只需忽略“真实”类正在执行的所有操作。

查看您的代码和“实际”问题:

retrieveFileStream()

为了实现这一目标,您必须配置 模拟,以便在when(ftpMock.completePendingCommand()).thenReturn(false); 时返回有用的内容叫!

我的意思是:你已经做了:

completePendingCommand()

告诉Mockito:调用false时,请返回public SimpleFtpFileImporter() { } 。您需要为正在测试的代码正在调用的每个方法执行此操作!

除此之外;您的代码存在许多问题,例如:

public SimpleFtpFileImporter() {
 this(new FtpClient());
}

应为空;相反,你应该做的事情如下:

private final FTPClient ftpClient;

简单回答:默认情况下字段应为 final (除非您有充分的理由不让它们成为最终字段):

{{1}}

并且编译器会告诉你忘记初始化该字段了!在你的情况下,绝对没有理由保持该字段在构建时不会初始化。这只会让全班更复杂,更难以测试。