모킹

파이썬에서 다양한 모킹을 사용하기 위해 pytest-mock 플러그인이 자주 사용됩니다.

pip install pytest-mock

유닉스 파일 시스템에서 파일을 지우는 코드를 모킹을 사용해 테스트해보면 다음과 같습니다.

import os

class UnixFS:
    @staticmethod
    def rm(filename):
        os.remove(filename)

def test_unix_fs(mocker):
    mocker.patch('os.remove')
    UnixFS.rm('file')
    os.remove.assert_called_once_with('file')

러스트에서의 모킹은 파이썬과는 매우 다르게 사용됩니다. 일반적으로 mockall 크레이트를 사용하는데, 파이썬과 달리 객체를 직접 모킹할 수 없습니다.

cargo add mockall mockall_double

이것은 파일 시스템과 상호 작용하는 모듈에 대한 단위 테스트를 작성하기 위해 mockallmockall_double 크레이트를 사용하는 방법을 보여주는 Rust 코드입니다. fs_api 모듈은 std::fs::remove_file 함수를 래핑하는 remove_file 메서드가 있는 FS 구조체를 정의합니다.

여기서 mockall::automock 은 테스트 코드에서 사용될 모킹된 MockFS 구조체를 자동으로 생성합니다.

#![allow(unused)]
fn main() {
#[allow(dead_code)]
mod fs_api {
    use std::fs;

    pub struct FS {}

    #[cfg_attr(test, mockall::automock)]
    impl FS {
        pub fn new() -> Self {
            Self {}
        }
        pub fn remove_file(&self, filename: &str) -> Result<(), std::io::Error> {
            fs::remove_file(filename)
        }
    }
}
}

mockall_double#[double] 어트리뷰트 매크로를 제공하는 크레이트입니다. 이 매크로는 테스트 빌드에서 구조체 또는 트레이트의 모의 버전을 자동으로 생성하는 데 사용할 수 있습니다. 일반 코드에서는 위에서 정의한 FS 구조체가, 테스트에서는 자동으로 생성된 MockFS 구조체가 사용됩니다.

#![allow(unused)]
fn main() {
use mockall_double::double;

#[double]
use fs_api::FS;
}

UnixFS 구조체는 FS 구조체의 인스턴스를 사용하여 파일을 제거하는 rm 메서드를 정의합니다.

#![allow(unused)]
fn main() {
pub struct UnixFS {}

impl UnixFS {
    pub fn rm(fs: &FS, filename: &str) -> Result<(), std::io::Error> {
        fs.remove_file(filename)
    }
}
}

이 코드에는 mockall 를 사용하여 FS 구조체에 대한 모의 객체를 생성하고 UnixFS::rm 메서드의 동작을 테스트하는 테스트 모듈도 포함되어 있습니다. use mockall::predicate::*; 는 모킹한 함수 expect_remove_file의 예상 입력을 찾을 수 있도록 하는 함수입니다. 즉 입력으로 문자열 슬라이스 "file"이 들어오는 경우에 이 모킹 함수가 작동합니다.

#![allow(unused)]
fn main() {
#[cfg(test)]
mod test {
    use super::*;
    use mockall::predicate::*;

    #[test]
    fn test_remove_file() {
        let mut fs = FS::default();

        fs.expect_remove_file()
            .with(eq("file"))
            .returning(|_| Ok(()));

        UnixFS::rm(&fs, "file").unwrap();
    }
}

}

main 함수를 포함한 전체 코드는 다음과 같습니다.

use mockall_double::double;

mod fs_api {
    use std::fs;

    #[cfg(test)]
    use mockall::automock;

    pub struct FS {}

    #[allow(dead_code)]
    #[cfg_attr(test, automock)]
    impl FS {
        pub fn new() -> Self {
            Self {}
        }
        pub fn remove_file(&self, filename: &str) -> Result<(), std::io::Error> {
            fs::remove_file(filename)
        }
    }
}

#[double]
use fs_api::FS;

pub struct UnixFS {}

impl UnixFS {
    pub fn rm(fs: &FS, filename: &str) -> Result<(), std::io::Error> {
        fs.remove_file(filename)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use mockall::predicate::*;

    #[test]
    fn test_remove_file() {
        let mut fs = FS::default();

        fs.expect_remove_file()
            .with(eq("file"))
            .returning(|_| Ok(()));

        UnixFS::rm(&fs, "file").unwrap();
    }
}

fn main() {
    let fs = FS::new();
    if let Err(e) = UnixFS::rm(&fs, "file") {
        println!("Error: {}", e);
    };
}

실행 결과

running 1 test
test test::test_remove_file ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s