altair의 프로젝트 일기
image_compressor 라이브러리 개선하기(2) - 테스트 개선하기 본문
먼저 구 테스트 코드를 분석해보자.
이 두 함수는 매 테스트의 처음과 끝에 실행된다. 테스트를 위해 원본 폴더, 목적지 폴더를 각각 생성하고 맨 마지막에 그 두 폴더를 삭제한다. 샘플 파일들이 담긴 폴더에서 필요한 파일을 원본 폴더에 복사한 후 테스트하는 구조로 만들어져 있다.
위 예시 테스트를 보자. jpg 이미지 하나를 압축하는 기능을 테스트하고 있다. 다른 모든 테스트들에게서 나타나는 공통적인 문제를 모두 포함한다.
- 파일 경로가 직접적으로 코드에 포함된다. 샘플 파일과 그 경로에 대한 종속성이 대단히 높다.
- 테스트할 파일 형식이 코드에 포함된다. 샘플 파일의 형식에 대한 종속성 또한 높다.
- 테스트 결과 중 일부를 출력한다. 유닛 테스트는 실패 또는 성공 두 상태만 존재하는 것이 좋다. 무엇보다 압축 파일 생성 여부만 테스트하면 되고 그 파일의 자세한 정보는 필요 없다.
만약 이 레포지토리를 받아 테스트를 실행해보는 사용자가 있다면 다음과 같은 문제가 일어날 것이다.
- 테스트를 위한 샘플 파일이 레포지포리에 없기 때문에 모든 테스트가 실패한다.
- 테스트 파일을 직접 구한다고 해도 매 테스트마다 필요한 파일 이름과 경로, 형식을 알기 어렵다.
- 테스트 실행 중 의미를 알기 어려운 내용이 출력된다(러스트는 기본적으로 테스트의 출력이 나타나지 않긴 한다).
따라서 다음과 같이 개선해야 한다.
- 테스트할 모든 이미지 파일은 테스트 코드 안에서 직접 생성해야 한다. 그래야 외부 파일에 종속성을 갖지 않는다.
- 테스트의 성공과 실패를 명확한 조건으로 바꾼다.
/// Create test directory and a image file in it.
fn setup<T: AsRef<Path>>(test_name: T) -> (PathBuf, Vec<PathBuf>) {
let test_dir = test_name.as_ref().to_path_buf();
if test_dir.is_dir() {
fs::remove_dir_all(&test_dir).unwrap();
}
fs::create_dir_all(&test_dir).unwrap();
const WIDTH: u32 = 256;
const HEIGHT: u32 = 256;
let img_stripe = ImageBuffer::from_fn(WIDTH, HEIGHT, |x, _| {
if x % 2 == 0 {
image::Luma([0u8])
} else {
image::Luma([255u8])
}
});
let stripe_path = test_dir.join("img_stripe.png");
img_stripe.save(&stripe_path).unwrap();
let img_random_rgb = ImageBuffer::from_fn(WIDTH, HEIGHT, |_, _| {
let r = rand::thread_rng().gen_range(0..256) as u8;
let g = rand::thread_rng().gen_range(0..256) as u8;
let b = rand::thread_rng().gen_range(0..256) as u8;
image::Rgb([r, g, b])
});
let rgb_path = test_dir.join("img_random_rgb.gif");
img_random_rgb.save(&rgb_path).unwrap();
let grad = colorgrad::CustomGradient::new()
.html_colors(&["deeppink", "gold", "seagreen"])
.build()
.unwrap();
let mut img_jpg = ImageBuffer::new(WIDTH, HEIGHT);
for (x, _, pixel) in img_jpg.enumerate_pixels_mut() {
let rgba = grad.at(x as f64 / WIDTH as f64).to_rgba8();
*pixel = image::Rgba(rgba);
}
let jpg_path = test_dir.join("img_jpg.jpg");
img_jpg.save(&jpg_path).unwrap();
(test_dir, vec![stripe_path, rgb_path, jpg_path])
}
fn cleanup<T: AsRef<Path>>(test_dir: T) {
if test_dir.as_ref().is_dir() {
fs::remove_dir_all(&test_dir).unwrap();
}
}
먼저 setup, cleanup 함수를 수정해보았다. setup 함수는 테스트 폴더에 총 세 이미지를 생성한다. 스트라이프 이미지, 랜덤 RGB 이미지, 그레디언트 이미지를 PNG, GIF, JPG 형식으로 생성한다. 테스트 파일을 이런 식으로 생성하면 파일에 대한 종속성을 없앨 수 있다.
이미지 파일을 jpg로 변환하는 함수를 테스트하는 코드다. 압축하지 않고 단지 변환만 하기 때문에, image 크레이트의 Reader를 사용해 변환한 데이터가 jpg가 맞는지 테스트한다. 생성한 모든 이미지에 대해 테스트하고 파일들을 삭제해 마무리한다. 애매한 결과를 출력하는 것이 아니라 기능을 실행한 후 결과가 원하는 대로 바뀌었는지 직접 테스트한다. 문제였던 점이 고쳐졌다.
위에서 보였던 구 버전 테스트 코드는 다음과 같이 바뀌었다.
마무리
처음이 라이브러리를 만들었을 때는 테스트에 대한 개념이 부족했었던 것 같다. 로컬 파일에 종속적인 테스트라니, 아찔하다. 아직 부족하지만 그래도 이번 기회를 통해 유닛 테스트가 어떤 방식으로 작동해야 하는지 조금은 알게되었다.
'IT > 러스트' 카테고리의 다른 글
image_compressor 라이브러리 개선하기(2) (0) | 2024.03.04 |
---|---|
image_compressor 라이브러리 개선하기(1) (0) | 2023.07.03 |
러스트로 tar 직접 구현하기 (2) (0) | 2022.12.27 |
러스트로 tar 직접 구현하기 (1) (0) | 2022.12.14 |
ImageCompressor2 개발기 (0) | 2022.03.24 |