본문 바로가기
Develop Study/Java 2026. 3. 6.

단위테스트 코드의 가상 SFTP Mock/Embedded 활용

업무에서 SFTP를 활용해, 여러 서비스간 파일을 주고 받는 모듈이 존재를 한다.

하지만, 매번 SFTP 연결과 파일 제공, 수신부 테스트를 위해서 협의 요청, 서버상태 확인, 아이디/비밀번호 확인을 보안상으로 계속 확인해봐야하는 경우가 생겨,

Postman 의 API 를 활용하는 대신 Test Code를 활용해서 SFTP 연결과 관련 서비스의 테스트를 진행하기로 했다.

 

찾아보면서 일반적인 Mock처리가 아닌, 실제 SFTP 환경처럼 구성해 활용할 수 있었고,

많이 활용할 수 있기 때문에 샘플 코드도 써보고 동작시키면서,

아래 정리를 했다.


단위테스트에서의 가상 SFTP Mock/Embedded 활용

  • 메모리의 가상(Embedded) SFTP 서버 를 활용해서 테스트
  • 네트워크를 사용하는 실제 SFTP 서버를 단위테스트 마다 활용할 시, 서버 및 네트워크 상태, 계정 권한 등을 매번 확인해야함
    • 서버 점검 중일 경우 테스트 불가

동작 목적

  • 실제 로직 간 JSch 실행 테스트 가능
    • Mock 으로 메서드의 결과만 가짜로 가로채는 방식이 아닌 실제 SFTP와 동일하게 띄우기 때문에, 모든 JSch 메서드들이 실제 소스코드 그대로 끝까지 생략없이 실행 가능
    • JSch 내부의 암호화 처리, 타임아웃 계산, 버퍼 관리 로직이 모두 가동되므로, JSch 라이브러리 자체에 대한 검증 가능
  • 테스트 결정성 보장
    • 테스트는 항상 같은 결과가 나와야하므로, 서버 환경에 의한 테스트 실패를 방지
    • 서버 내에서 테스트에 필요한 파일이 삭제되거나, 네트워크가 불안정한 등
  • 추가 인프라 구축 리소스 효율
    • 테스트 를 또다른 서버를 구축해서 진행할 필요 없이, 의존성 추가를 통해서 Embedded 환경에서 곧바로 테스트 가능
  • 보안 예방
    • SFTP 연결에 사용되는 사용자 ID, PW 가 테스트 코드에 남아 유출되는 것을 방지

동작 원리

  • 인메모리 구동 (In-Memory Execution)
    • Apache MINA SSHD 의종성 주입, 라이브러리를 사용
    • Java 프로세스가 실행될 때 내부 메모리에 아주 가벼운 SFTP 서버 기능을 함께 실행
  • 루프백 통신 (Loopback):
    • 네트워크를 타고 외부로 나가는 대신, 자기 자신(127.0.0.1)에게 신호를 루프백
    • 물리적인 랜선이나 와이파이가 없어도 통신이 가능
  • 경로 매핑 (Virtual File System)
    • 실제 서버의 하드디스크 대신, 내 로컬 환경의 특정 폴더를 서버의 루트(/)인 것처럼 속이는 가상 파일 시스템 레이어를 사용 (일종의 디렉토리 매핑)

실제환경 VS 가상 서버 비교

구분  실제 SFTP 서버 연결 테스트 가상(Embedded) 서버 테스트
속도 네트워크 지연으로 느림 메모리 내 통신으로 매우 빠름
안정성 서버 상태에 따라 결과가 변함 항상 일관된 결과 (안정적)
설정 방화벽, 계정, 포트 설정 복잡 코드 몇 줄로 즉시 생성 가능
환경 인터넷 연결 필수 오프라인에서도 테스트 가능

테스트 코드 작성하기

의종성 주입

  • Gradle
dependencies {
    // Apache MINA SSHD 핵심 라이브러리
    // 1. SSH 서버의 공통 유틸리티 및 기본 설정
    testImplementation 'org.apache.sshd:sshd-common:2.10.0'
    // 2. SSH 서버의 핵심 기능 (연결, 채널 관리 등)
    testImplementation 'org.apache.sshd:sshd-core:2.10.0'
    // 3. SFTP 프로토콜 전용 서브시스템 (파일 전송 기능)
    testImplementation 'org.apache.sshd:sshd-sftp:2.10.0'
    
    // SFTP 기능을 구현하기 위한 서브시스템
    testImplementation 'org.apache.sshd:sshd-sftp:2.10.0'
}
  • Maven ( 이하 같음)
<dependency>
    <groupId>org.apache.sshd</groupId>
    <artifactId>sshd-common</artifactId>
    <version>2.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.sshd</groupId>
    <artifactId>sshd-core</artifactId>
    <version>2.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.sshd</groupId>
    <artifactId>sshd-sftp</artifactId>
    <version>2.10.0</version>
    <scope>test</scope>
</dependency>

테스트 코드

@DisplayName("SFTP Download Test")
public class SftpDownloadtest {

    private static SshServer sshd;
    private final String MOCK_HOST = "127.0.0.1";
    private final int MOCK_PORT = 2222;
    private final String MOCK_USER = "testuser";
    private final String MOCK_PW = "password";

    @BeforeEach
    @DisplayName("Start Mock SFTP Server")
    void startMockSftpServer() throws Exception {
        // 가짜(Mock) SFTP 서버 설정 시작
        sshd = SshServer.setUpDefaultServer();
        sshd.setPort(MOCK_PORT); // 테스트용 포트 번호 설정
        sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());

        // 로컬 PC의 특정 폴더를 서버의 루트(/) 경로로 매핑 (가상 파일 시스템)
        sshd.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get("C:/")));

        // SFTP 서브시스템 등록 및 사용자 인증 설정
        sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));

        // 아이디와 비밀번호가 미리 정의된 상수(MOCK_USER, MOCK_PW)와 일치하는지 확인
        sshd.setPasswordAuthenticator((u, p, s) -> u.equals(MOCK_USER) && p.equals(MOCK_PW));

        // 서버 가동
        sshd.start();
        System.out.println(">> Mock SFTP Server started on port: " + MOCK_PORT);
    }

    @Test
    @DisplayName("SFTP 다운로드 모듈 테스트")
    public void sftp_file_download() throws Exception {
        // given
        SFTPUtil sftpUtil = new SFTPUtil();

        ReflectionTestUtils.setField(sftpUtil, "ip", MOCK_HOST);
        ReflectionTestUtils.setField(sftpUtil, "port", MOCK_PORT);

        String serverPath = "/test_folder/";
        String localPath = "/temp/download/";
        String fileName = "test_file.dat";

        // when
        sftpUtil.fileDownloadLocal(serverPath, localPath, fileName);

        // then
        File downloadedFile = new File(localPath + fileName);
        assertTrue(downloadedFile.exists(), "파일이 지정된 로컬 경로에 존재해야 합니다.");
    }

    @AfterEach
    @DisplayName("Stop Mock SFTP Server")
    void stopMockSftpServer() throws Exception {
        if(sshd != null && sshd.isStarted()) {
            sshd.stop();
            System.out.println(">> Mock SFTP Server stopped on port: " + MOCK_PORT);
        }
    }
}

가상 SFTP 서버 띄우기

 private static SshServer sshd;
private final String MOCK_HOST = "127.0.0.1";
private final int MOCK_PORT = 2222;
private final String MOCK_USER = "testuser";
private final String MOCK_PW = "password";

@BeforeEach
@DisplayName("Start Mock SFTP Server")
void startMockSftpServer() throws Exception {
    // 가짜(Mock) SFTP 서버 설정 시작
    sshd = SshServer.setUpDefaultServer();
    sshd.setPort(MOCK_PORT); // 테스트용 포트 번호 설정
    sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());

    // 로컬 PC의 특정 폴더를 서버의 루트(/) 경로로 매핑 (가상 파일 시스템)
    sshd.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get("C:/")));

    // SFTP 서브시스템 등록 및 사용자 인증 설정
    sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));

    // 아이디와 비밀번호가 미리 정의된 상수(MOCK_USER, MOCK_PW)와 일치하는지 확인
    sshd.setPasswordAuthenticator((u, p, s) -> u.equals(MOCK_USER) && p.equals(MOCK_PW));

    // 서버 가동
    sshd.start();
    System.out.println(">> Mock SFTP Server started on port: " + MOCK_PORT);
}
  • sshd = SshServer.setUpDefaultServer();
    • Apache MINA SSHD 라이브러리에서 제공하는 기본 설정으로 SSH 서버 객체를 생성
    • 호화 알고리즘, 세션 트 등 SSH 통신에 필요한 표준 설정들을 기본값으로 세팅 준비
  • sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
    • 서버 증명용 비대칭 키 쌍을 설정
    • 기존의 SSH 서버에서는 키파일이 따로 있는데, 테스트마다 생성해서 관리하는 것이 아닌 실행 시점에서 가짜 키를 생성해서 적용해 부팅하는 방식
  • sshd.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get("C:/")));
    • 파일 시스템을 가상화 해서 로컬 디렉토리에 일종의 시스템 매핑을 하는 단계
    • C드라이브가 아니더라도, 해당 Paths 에 다른 경로를 넣으면, 그 경로에서 부터 매핑-
  • sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
    • SSH 서버에 SFTP 기능을 추가하는 선언
    • SSH 는 원격 터미널 접속용이기 때문에, SFTP 서브시스템을 등록해야만 비로소 파일 업로드/다운로드 프로토콜을 해석
  • sshd.setPasswordAuthenticator((u, p, s) -> u.equals(MOCK_USER) && p.equals(MOCK_PW));
    • 접속 시도 사용자/비밀번호 검증
    • 실시간 확인하면서 띄우기때문에 MOCK으로 지정해야 테스트 과정중에서도 해당 MOCK 값들로 SFTP 접속이 가능하게 됨

단위 테스트

@Test
@DisplayName("SFTP 다운로드 모듈 테스트")
public void sftp_file_download() throws Exception {
    // given
    SFTPUtil sftpUtil = new SFTPUtil();

    ReflectionTestUtils.setField(sftpUtil, "ip", MOCK_HOST);
    ReflectionTestUtils.setField(sftpUtil, "port", MOCK_PORT);

    String serverPath = "/test_folder/";
    String localPath = "/temp/download/";
    String fileName = "test_file.dat";

    // when
    sftpUtil.fileDownloadLocal(serverPath, localPath, fileName, MOCK_USER, MOCK_PW);

    // then
    File downloadedFile = new File(localPath + fileName);
    assertTrue(downloadedFile.exists(), "파일이 지정된 로컬 경로에 존재해야 합니다.");
}
  • SFTPUtil 에서는 저번 포스트에서 작성되었던 SFTP 연결을 하는 과정을 포함하고 있음
    • 따라서 IP PORT 를 먼저 Reflection을 통해 상수부분에 넣어줘야 로컬 Embedded 한 SFTP 서버에 접속
  • sftpUtil.fileDownloadLocal(serverPath, localPath, fileName, MOCK_USER, MOCK_PW);
    • 서버 디렉토리에서 → 로컬 디렉토리로 파일을 복사(다운로드)하는 메서드 (해당 메서드가 있다고 가정)
      • 여기서 ServerPath는 당연히 Mock을 띄운 SFTP 이므로, 로컬에서 "C:/test_folder/" 에서 해당 파일을 찾게됨
  • File downloadedFile = new File(localPath + fileName);
    • 따라서, C:/test_folder/ → C:/temp/download/ (윈도우기준 자동으로 C드라이브 기준이므로) 방향으로 파일이 복사되었을 것

가상 SFTP 서버 내리기

@AfterEach
@DisplayName("Stop Mock SFTP Server")
void stopMockSftpServer() throws Exception {
    if(sshd != null && sshd.isStarted()) {
        sshd.stop();
        System.out.println(">> Mock SFTP Server stopped on port: " + MOCK_PORT);
    }
}
  • 이부분은 서버가 게속 매 @Test 가 진행될때 마다 열려있으면 안되기 때문에, 닫아주는 단계
  • 테스트 결정성을 최대한 보장하기 위해 매번 SFTP 서버는 초기화 해야함

참고문헌

https://mina.apache.org/sshd-project/documentation.html

 

SSHD Documentation — Apache MINA

 

mina.apache.org

 

https://martinfowler.com/articles/practical-test-pyramid.html

 

The Practical Test Pyramid

Find out what kinds of automated tests you should implement for your application and learn by examples what these tests could look like.

martinfowler.com

 


가장 유용한 부부닝 JSch를 그대로 사용해서 내 PC에 SFTP 환경과 그대로 디렉토리와 샘플 파일을 만들어 놓기만 하면 항상 빠르게 할 수 있기 때문에 왜 계속 Postman으로 STG 서버에서 테스트만을 고집하지 않을 수 있었다.