사이드 프로젝트 중 파일 업데이트 관련 코드를 짜고, 이와 관련된 테스트 코드를 돌려보던 중 파일 업데이트 시 base64 부분에서 에러가 나는 부분을 확인하였다.
@ExtendWith(MockitoExtension.class)
class FileServiceTest {
@InjectMocks
private FileService fileService;
@Mock
private FileRepository fileRepository;
private File file;
@BeforeEach
void setUp() {
file = new File("originalName.jpg", "generateName", 1024L, "image/jpeg", "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD");
}
...
@Test
void updateFile() throws IOException {
//Given
when(fileRepository.findById(1L)).thenReturn(Optional.of(file));
when(fileRepository.save(any(File.class))).thenReturn(file);
//Mocking MultipartFile
MultipartFile mockFile = mock(MultipartFile.class);
String base64Image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD";
byte[] imageData = Base64.getDecoder().decode(base64Image.split(",")[1]); // Base64 데이터 부분만 디코딩
when(mockFile.getBytes()).thenReturn(imageData);
when(mockFile.getContentType()).thenReturn("image/jpeg");
//When
File updateFile = fileService.updateFile(1L, "newName.jpg", mockFile);
//Then
assertNotNull(updateFile);
assertEquals("newName.jpg", updateFile.getStoreFilename());
assertEquals(base64Image, updateFile.getBase64Data());
File retrievedFile = fileService.getFileById(updateFile.getId());
assertEquals("newName.jpg", retrievedFile.getStoreFilename());
assertEquals(base64Image, retrievedFile.getBase64Data());
verify(fileRepository, times(1)).save(any(File.class)); }
JUnit 테스트 코드에서 파일 업데이트 후 Base64 데이터를 비교하는 부분에서 예상한 값과 실제 값이 다르게 나왔다. 특히, Expected 값과 Actual 값에서 Base64 문자열의 끝에 패딩 차이가 발생했다.
특히
assertEquals(base64Image, updateFile.getBase64Data());
이 부분에서 다음과 같은 오류가 떴다.
Expected: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD
Actual: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAA=
두 값의 차이는 마지막에 붙은 "A=" 이 부분이었다. 여러 군데 찾아보니, 이 오류는 Base64 패딩에서 발생하는 문제로, 동일한 데이터를 인코딩할 때 패딩 문제가 다를 경우 비교 시 오류가 발생한 것이라고 한다.
1. 문제 분석:
Base64 인코딩에서는 데이터를 3바이트씩 묶어 4개의 문자로 변환하며, 데이터의 길이가 3으로 나누어떨어지지 않으면 패딩을 추가한다. 이로 인해 파일의 실제 내용이 동일하더라도 패딩이 다를 수 있다. (참고 자료 : base64 인코딩 알고리즘)
이러한 점을 고려하지 않고, 테스트에서 assertEquals로 비교를 했을 때 패딩이 달라서 불일치 오류가 발생한 것이다.
2. 해결 방안:
Base64 데이터를 비교할 때 assertEquals() 대신 assertArrayEquals()를 사용하여 바이트 배열을 비교하는 방식으로 변경하였다. 이렇게 하면 패딩 차이를 무시하고 실제 파일 내용이 일치하는지 정확히 비교할 수 있다고 한다.
더 구체적으로 보면,
assertEquals()는 두 문자열을 비교한다. 즉, 문자열의 문자 그대로를 비교하며, 패딩(=) 차이나 공백 같은 작은 차이를 구분한다. 그래서 Base64 문자열에서 패딩 차이가 나면, assertEquals()는 이를 다르게 인식한다.
assertArrayEquals()는 두 배열을 비교한다. Base64 문자열을 디코딩하여 바이트 배열로 변환한 후, 그 배열의 내용을 비교한다. 이 방식은 문자열에 있는 패딩이나 공백과 상관없이 실제 데이터(즉, 바이너리 내용)가 동일한지 비교한다.
즉, Base64 인코딩에서 패딩(=)은 중요한 요소는 아니지만, 문자열로 직접 비교할 때는 차이를 일으킬 수 있다.
아까 예제처럼,
Expected: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD
Actual: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAA=
단순 assertEquals()과 같은 비교로는 믄자열로 직접 비교하기 때문에 패딩 차이로 인한 오류를 일으킬 수 있다. 따라서 assertArrayEquals()를 통해 비교함으로써 패딩이나 공백 상관없이 실제 데이터를 비교해야 한다.
byte[] expectedBase64Data = Base64.getDecoder().decode(base64Image.split(",")[1]);
byte[] actualBase64Data = Base64.getDecoder().decode(updateFile.getBase64Data().split(",")[1]);
assertArrayEquals(expectedBase64Data, actualBase64Data); // 파일 내용 비교
그래서 assertEquals()를 사용해서 문자열을 비교한 기존 코드에서, assertArrayEquals()를 사용한 배열 비교로 수정하여 실제 파일 내용의 일치 여부를 정확하게 검증한다.
이번 문제는 JUnit 테스트에서 Base64 데이터를 비교할 때 발생한 패딩 문제를 해결하는 이었다. assertEquals()로 문자열을 비교하는 것만으로는 Base64 인코딩된 데이터의 미세한 차이(ex. "=")를 처리하기 어려웠다. 이러한 문제를 assertArrayEquals()를 사용하여 실제 데이터를 비교함으로써 해결할 수 있었고, 테스트를 성공적으로 완료 할 수 있었다.
이 경험을 통해 Base64 비교 시 주의할 점을 더 잘 이해하게 되었고, 실제 값을 비교할 때 배열로 비교해야 한다는 점을 깨달았다. 앞으로 파일 처리와 관련된 테스트 코드 작성 시 더 신경을 써야겠다는 점을 까먹지 말아야겠다.