모킹
파이썬에서 다양한 모킹을 사용하기 위해 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
이것은 파일 시스템과 상호 작용하는 모듈에 대한 단위 테스트를 작성하기 위해 mockall
및 mockall_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