1
오늘날 많은 프로그래밍 언어가 있지만, Rust는 시스템 프로그래밍 언어로서 안전성과 성능을 모두 갖춘 독특한 언어입니다. 그중에서도 Rust의 테스팅과 오류 처리 방식은 매우 체계적이고 견고합니다. 이번 문서에서는 Rust의 테스트 작성 방법과 오류 처리 기법에 대해 자세히 알아보겠습니다.
단위 테스트(Unit Tests)
단위 테스트는 개별 함수나 메서드의 정확성을 검증하는 작업입니다. Rust에서는 테스트 모듈을 직접 생성하거나 각 함수/메서드 옆에 테스트 코드를 작성할 수 있습니다. 테스트 코드는 #[test] 어노테이션으로 표시되며, 실행 시 cargo test 명령어를 사용합니다.
// 실제 코드
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 테스트 코드
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}
}
위 예시에서는 add 함수에 대한 두 가지 테스트 케이스를 작성했습니다. assert_eq! 매크로를 사용하여 실제 결과와 예상 결과를 비교합니다. 만약 둘이 다르다면 테스트가 실패하게 됩니다.
Rust는 다양한 테스트 어서션을 제공하므로, 상황에 맞게 적절한 어서션을 선택할 수 있습니다. 예를 들어 assert_ne!는 두 값이 다른지 확인하고, assert!는 부울 조건을 검사합니다.
참고: 테스트 코드는 모듈 파일이나 통합 테스트 파일에 작성할 수 있습니다. 모듈 파일에 작성하면 관련 코드와 가까이 있어 이해하기 쉽고, 통합 테스트 파일에 작성하면 여러 모듈을 한 번에 테스트할 수 있습니다.
통합 테스트(Integration Tests)
통합 테스트는 여러 모듈이나 크레이트(Rust의 패키지 단위)가 제대로 상호작용하는지 검증하는 작업입니다. 통합 테스트는 프로젝트 루트 디렉토리의 tests 폴더에 작성합니다. 각 통합 테스트 파일은 tests/my_test.rs 형식으로 생성되며, cargo test 명령어로 실행할 수 있습니다.
// tests/integration_test.rs
use my_crate::add;
#[test]
fn test_add_integration() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}
위 예시에서는 my_crate 크레이트의 add 함수를 테스트합니다. 통합 테스트는 실제 애플리케이션 환경과 유사한 상황에서 코드를 검증할 수 있습니다. 예를 들어 데이터베이스나 외부 API와의 상호작용, 멀티스레딩 등을 테스트할 수 있습니다.
참고: 통합 테스트는 상대적으로 느리고 복잡할 수 있습니다. 따라서 단위 테스트와 함께 사용하는 것이 바람직합니다. 단위 테스트는 코드의 작은 부분을 빠르게 검증하고, 통합 테스트는 전체 시스템을 검증하는 방식입니다.
오류 처리
Rust는 오류 처리를 위해 Result 열거체와 ? 연산자를 제공합니다. Result 열거체는 Ok 변형과 Err 변형으로 이루어져 있습니다. 성공적인 결과는 Ok(값)으로 표현되고, 오류는 Err(오류 값)으로 표현됩니다.
use std::fs::File;
use std::io::Error;
fn open_file(path: &str) -> Result<File, Error> {
File::open(path)
}
fn main() {
let result = open_file("myfile.txt");
match result {
Ok(file) => println!("File opened successfully!"),
Err(error) => println!("Error opening file: {}", error),
}
}
위 예시에서 open_file 함수는 파일을 열고, 성공하면 Ok(File) 객체를 반환하고, 실패하면 Err(Error) 객체를 반환합니다. main 함수에서는 match 문을 사용하여 결과를 처리합니다.
? 연산자는 오류 처리 코드를 더욱 간결하게 만들어줍니다. ? 연산자는 Result 값을 받아, Ok 변형이면 값을 반환하고, Err 변형이면 즉시 오류를 전파합니다.
use std::fs::File;
use std::io::{self, Error};
fn open_file(path: &str) -> Result<File, Error> {
File::open(path)
}
fn read_file(path: &str) -> Result<String, Error> {
let file = open_file(path)?; // 오류 발생 시 즉시 전파
let mut contents = String::new();
file.read_to_string(&mut contents)?; // 오류 발생 시 즉시 전파
Ok(contents)
}
fn main() {
let result = read_file("myfile.txt");
match result {
Ok(contents) => println!("File contents: {}", contents),
Err(error) => println!("Error reading file: {}", error),
}
}
위 예시에서 read_file 함수는 파일을 열고 내용을 읽어옵니다. ? 연산자를 사용하여 오류 처리 코드를 줄일 수 있습니다. 만약 open_file이나 read_to_string에서 오류가 발생하면, 즉시 오류를 전파하고 함수를 종료합니다.
참고: Rust에서는 오류를 복구 가능한 오류(recoverable errors)와 복구 불가능한 오류(unrecoverable errors)로 나눕니다. 복구 가능한 오류는 Result로 처리하고, 복구 불가능한 오류는 panic! 매크로로 처리합니다.
오류 종류 정의 (Custom Error Types)
Rust에서는 std::error::Error 트레이트를 구현하여 사용자 정의 오류 타입을 정의할 수 있습니다. 이를 통해 오류 메시지를 더욱 명확하게 전달하고, 오류 처리 로직을 개선할 수 있습니다.
use std::fmt;
use std::error::Error;
#[derive(Debug)]
struct MyError {
message: String,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MyError: {}", self.message)
}
}
impl Error for MyError {}
fn divide(a: i32, b: i32) -> Result<i32, MyError> {
if b == 0 {
Err(MyError {
message: "Cannot divide by zero".to_string(),
})
} else {
Ok(a / b)
}
}
fn main() {
let result = divide(10, 0);
match result {
Ok(result) => println!("Result: {}", result),
Err(error) => println!("Error: {}", error),
}
}
위 예시에서 MyError 구조체를 정의하고, std::fmt::Display 트레이트와 std::error::Error 트레이트를 구현했습니다. divide 함수는 두 수를 나누는데, 나누는 수가 0이면 MyError를 반환합니다. main 함수에서는 divide 함수의 결과를 처리합니다.
이처럼 사용자 정의 오류 타입을 정의하면, 오류 메시지를 더욱 명확하게 전달할 수 있습니다. 또한 오류 정보를 확장하거나 다른 오류 타입과 합성할 수 있습니다.
오류 처리 전략
Rust에서는 다양한 오류 처리 전략을 사용할 수 있습니다. 적절한 전략을 선택하면 코드의 안전성과 가독성을 높일 수 있습니다.
- 오류 전파(Propagating Errors): 오류를 처리할 수 없는 함수에서는 오류를 상위 함수로 전파합니다. 이를 통해 오류 처리 로직을 한 곳에 모을 수 있습니다.
- 오류 변환(Translating Errors): 오류를 다른 오류 타입으로 변환하여 처리할 수 있습니다. 예를 들어 여러 종류의 오류를 하나의 오류 타입으로 통합할 수 있습니다.
- 오류 래핑(Wrapping Errors): 오류에 추가 정보를 덧붙여 더욱 상세한 오류 메시지를 제공할 수 있습니다. 이를 통해 오류 추적이 용이해집니다.
- 오류 무시(Ignoring Errors): 오류를 무시하고 기본값을 반환하는 방식입니다. 이 방식은 주의해서 사용해야 합니다.
- 오류 복구(Recovering from Errors): 오류가 발생했을 때 적절한 조치를 취하여 프로그램을 계속 실행할 수 있습니다.
적절한 오류 처리 전략을 선택하면 안전하고 견고한 코드를 작성할 수 있습니다. Rust의 오류 처리 기능을 활용하여 예외 상황을 효과적으로 처리할 수 있습니다.
참고 자료
[1] The Rust Programming Language Book, Steve Klabnik and Carol Nichols, No Starch Press, 2018.
[2] "Writing Automated Tests in Rust", Rust Documentation, https://doc.rust-lang.org/book/ch11-00-testing.html
[3] "Error Handling in Rust", Rust Documentation, https://doc.rust-lang.org/book/ch09-00-error-handling.html
[4] "Custom Error Types in Rust", Steve Klabnik, https://steveklabnik.com/writing/rust-custom-error-types
[5] "Error Handling in Rust", Alice Archer, https://blog.burntsushi.net/rust-error-handling/
한 고대 문서 이야기
여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오
gospel79.tistory.com
유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법
올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을
stock79.tistory.com
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
'IT > Rust 기초 완전 정복' 카테고리의 다른 글
Rust 라이프타임과 클로저 개념 (15) (0) | 2024.04.24 |
---|---|
Rust 제네릭과 트레이트 이해하기 (14) (0) | 2024.04.24 |
Rust 참조자와 슬라이스 활용법 (7) (0) | 2024.04.22 |
Rust 소유권 개념과 borrowing 이해하기 (6) (0) | 2024.04.22 |
Rust 함수 정의와 호출 방식 (5) (0) | 2024.04.22 |
댓글