Bash 테스트
셸 스크립트를 위한 테스트 프레임워크와 패턴, shunit2와 shellspec에 중점을 둡니다.
프레임워크 선택
| 프레임워크 | 강점 | 최적 사용 사례 |
|---|---|---|
| shunit2 | xUnit 스타일, 간단함, 이식성 좋음 | 단위 테스트, 함수 테스트 |
| shellspec | BDD 스타일, 현대적, 광범위함 | 동작 명세, 전체 프로젝트 |
shunit2
설치
# 직접 다운로드
curl -L https://github.com/kward/shunit2/raw/master/shunit2 -o shunit2
# 패키지 관리자 이용
apt install shunit2
brew install shunit2
기본 테스트 구조
#!/usr/bin/env bash
# 테스트할 스크립트 불러오기
source ./my_script.sh
# 테스트 함수는 test_로 시작
test_addition() {
result=$(add 2 3)
assertEquals "2 + 3은 5여야 함" "5" "$result"
}
test_string_output() {
result=$(greet "World")
assertEquals "Hello, World" "$result"
}
test_file_creation() {
create_temp_file
assertTrue "임시 파일이 존재해야 함" "[ -f /tmp/testfile ]"
}
test_exit_code() {
validate_input "valid"
assertEquals "유효한 입력은 0을 반환해야 함" 0 $?
}
# 설정 및 정리
oneTimeSetUp() {
# 모든 테스트 전에 한 번 실행
export TEST_DIR=$(mktemp -d)
}
oneTimeTearDown() {
# 모든 테스트 후에 한 번 실행
rm -rf "$TEST_DIR"
}
setUp() {
# 각 테스트 전에 실행
cd "$TEST_DIR"
}
tearDown() {
# 각 테스트 후에 실행
rm -f "$TEST_DIR"/*
}
# shunit2 불러오기
source shunit2
shunit2 어설션
# 동등
assertEquals [메시지] 예상값 실제값
assertNotEquals [메시지] 예상하지_않은값 실제값
# Null/Empty
assertNull [메시지] 값
assertNotNull [메시지] 값
# Boolean/상태
assertTrue [메시지] 조건
assertFalse [메시지] 조건
# 동일 참조 (문자열 비교)
assertSame [메시지] 예상값 실제값
assertNotSame [메시지] 예상하지_않은값 실제값
# 포함 (문자열 내)
assertContains [메시지] 컨테이너 내용
# 종료 상태
assertEquals 0 $?
함수 격리 테스트
#!/usr/bin/env bash
# test_my_functions.sh
source ./my_functions.sh
test_calculate_sum_empty() {
result=$(calculate_sum)
assertEquals "빈 합계는 0이어야 함" "0" "$result"
}
test_calculate_sum_single() {
result=$(calculate_sum 5)
assertEquals "5" "$result"
}
test_calculate_sum_multiple() {
result=$(calculate_sum 1 2 3 4 5)
assertEquals "15" "$result"
}
test_validate_email_valid() {
validate_email "[email protected]"
assertTrue "유효한 이메일은 통과해야 함" $?
}
test_validate_email_invalid() {
validate_email "not-an-email"
assertFalse "유효하지 않은 이메일은 실패해야 함" $?
}
source shunit2
shellspec
설치
# curl 이용
curl -fsSL https://git.io/shellspec | sh
# Homebrew 이용
brew install shellspec
# 패키지 관리자 이용
apt install shellspec
디렉토리 구조
project/
├── lib/
│ └── functions.sh
├── spec/
│ ├── spec_helper.sh
│ ├── functions_spec.sh
│ └── support/
│ └── fixtures/
└── .shellspec
설정 (.shellspec)
--require spec_helper
--format documentation
--color
기본 Spec 구조
shellspec 매처
# 출력 매처
The output should eq "정확히 일치"
The output should include "부분"
The output should start with "접두사"
The output should end with "접미사"
The output should match pattern "*glob*"
The output should be blank
# 상태 매처
The status should be success # 종료 0
The status should be failure # 종료 0 아님
The status should eq 1 # 특정 코드
# 변수 매처
The variable VAR should eq "값"
The variable VAR should be defined
The variable VAR should be undefined
# 파일 매처
The file "경로" should be exist
The file "경로" should be file
The file "경로" should be directory
The file "경로" should be readable
# 경로 매처
The path "file.txt" should be exist
shellspec 모킹
Describe 'deploy 함수'
# 외부 명령 모킹
curl() {
echo "모킹된 응답"
return 0
}
It 'API 엔드포인트 호출'
When call deploy "server"
The output should include "모킹된 응답"
End
End
Describe '파일 작업'
# 함수 오버라이드로 모킹
Mock rm
echo "rm 호출됨: $*"
End
It '파일 제거 시도'
When call cleanup_temp
The output should include "rm 호출됨"
End
End
Spec 헬퍼
# spec/spec_helper.sh
# 공통 함수 불러오기
spec_helper_precheck() {
minimum_version "0.28.0"
}
spec_helper_loaded() {
# 테스트 환경 설정
export TEST_MODE=true
}
spec_helper_configure() {
# 프로젝트 함수 가져오기
import 'lib/functions.sh'
}
테스트 패턴
종료 코드 테스트
# shunit2
test_success_exit() {
run_command "valid_input"
assertEquals 0 $?
}
test_error_exit() {
run_command "invalid_input"
assertEquals 1 $?
}
# shellspec
It '성공 시 0 종료'
When call run_command "valid_input"
The status should eq 0
End
stdout 및 stderr 테스트
# shunit2
test_stdout() {
result=$(my_function 2>/dev/null)
assertEquals "예상 출력" "$result"
}
test_stderr() {
error=$(my_function 2>&1 >/dev/null)
assertContains "$error" "오류 메시지"
}
# shellspec
It 'stdout으로 출력'
When call my_function
The stdout should eq "예상 출력"
End
It 'stderr로 오류 출력'
When call my_function
The stderr should include "오류 메시지"
End
픽스처 테스트
# 테스트 픽스처 설정
setUp() {
TEST_DIR=$(mktemp -d)
cat > "$TEST_DIR/config.json" <<EOF
{
"key": "value"
}
EOF
}
tearDown() {
rm -rf "$TEST_DIR"
}
test_config_parsing() {
result=$(parse_config "$TEST_DIR/config.json")
assertEquals "value" "$result"
}
인터랙티브 함수 테스트
# heredoc으로 입력 제공
test_interactive() {
result=$(my_prompt <<EOF
yes
EOF
)
assertEquals "confirmed" "$result"
}
# 또는 printf 사용
test_with_input() {
result=$(printf 'yes\n' | my_prompt)
assertEquals "confirmed" "$result"
}
테스트 실행
# shunit2
./test_script.sh
# shellspec
shellspec # 모든 spec 실행
shellspec spec/file_spec.sh # 특정 spec 실행
shellspec --format tap # TAP 출력
shellspec --jobs 4 # 병렬 실행
모범 사례
- 실용적일 때 하나의 테스트에 하나의 어설션
- 설명적인 테스트 이름 - 무엇을 테스트하는지 설명
- 테스트 격리 - 테스트 간 의존성 없음
- 경계 사례 테스트 - 빈 입력, 특수 문자, 대용량 데이터
- tearDown에서 리소스 정리
- 외부 명령 모킹 - curl을 테스트하지 말고, 자체 로직을 테스트하세요
- 출력뿐만 아니라 종료 코드도 테스트