Computer >> 컴퓨터 >  >> 프로그램 작성 >> BASH 프로그래밍

BATS로 Bash 테스트하기

Java, Ruby 및 Python과 같은 언어로 응용 프로그램을 작성하는 소프트웨어 개발자는 시간이 지남에 따라 소프트웨어의 무결성을 유지하는 데 도움이 되는 정교한 라이브러리를 보유하고 있습니다. 그들은 소프트웨어의 모든 측면이 예상대로 작동하는지 확인하기 위해 구조화된 환경에서 일련의 실행을 통해 애플리케이션을 실행하는 테스트를 만듭니다.

이러한 테스트는 소스 리포지토리로 푸시할 때마다 테스트가 실행되고 테스트가 실패하면 개발자에게 즉시 알림이 전송되는 CI(지속적 통합) 시스템에서 자동화될 때 훨씬 더 강력합니다. 이 빠른 피드백은 애플리케이션의 기능 무결성에 대한 개발자의 확신을 높여줍니다.

Bash BATS(자동 테스트 시스템)를 사용하면 Bash 스크립트 및 라이브러리를 작성하는 개발자가 Java, Ruby, Python 및 기타 개발자가 사용하는 것과 동일한 방식을 Bash 코드에 적용할 수 있습니다.

BATS 설치

BATS GitHub 페이지에는 설치 지침이 포함되어 있습니다. 더 강력한 어설션을 제공하거나 BATS에서 사용하는 TAP(Test Anything Protocol) 출력 형식에 대한 재정의를 허용하는 2개의 BATS 도우미 라이브러리가 있습니다. 이들은 표준 위치에 설치될 수 있으며 모든 스크립트에서 제공됩니다. 테스트 중인 각 스크립트 또는 라이브러리 세트에 대해 Git 리포지토리에 BATS 및 해당 도우미 라이브러리의 전체 버전을 포함하는 것이 더 편리할 수 있습니다. 이것은 git 하위 모듈을 사용하여 수행할 수 있습니다. 시스템.

다음 명령은 BATS 및 해당 도우미 라이브러리를 테스트에 설치합니다. Git 저장소의 디렉터리입니다.

git submodule init
git submodule add https://github.com/sstephenson/bats test/libs/bats
git submodule add https://github.com/ztombol/bats-assert test /libs/bats-assert
git submodule add https://github.com/ztombol/bats-support test/libs/bats-support
git add .
git commit -m '설치됨 박쥐'

Git 저장소를 복제하고 하위 모듈을 동시에 설치하려면

--recurse-submodules를 사용하세요. git clone 플래그 .

각 BATS 테스트 스크립트는 박쥐가 실행해야 합니다. 실행 가능. 소스 코드 저장소의 test/libs에 BATS를 설치한 경우 디렉토리에서 다음을 사용하여 테스트를 호출할 수 있습니다.

./test/libs/bats/bin/bats <path to test script> 

또는 각 BATS 테스트 스크립트의 시작 부분에 다음을 추가하십시오.

#!/usr/bin/env ./test/libs/bats/bin/bats
'libs/bats-support/load' 로드
'libs/bats-assert/load' 로드

chmod +x <테스트 스크립트 경로> . 이것은 a) ./test/libs/bats에 설치된 BATS로 실행 가능하게 만듭니다. b) 이러한 도우미 라이브러리를 포함합니다. BATS 테스트 스크립트는 일반적으로 테스트에 저장됩니다. 디렉토리 및 테스트 중인 스크립트의 이름을 사용하지만 .bats 확대. 예를 들어 bin/build를 테스트하는 BATS 스크립트 test/build.bat라고 해야 합니다. .

BATS에 정규식을 전달하여 전체 BATS 테스트 파일 세트를 실행할 수도 있습니다(예:./test/lib/bats/bin/bats test/*.bats). .

BATS 적용을 위한 라이브러리 및 스크립트 구성

Bash 스크립트와 라이브러리는 내부 작업을 BATS에 효율적으로 노출하는 방식으로 구성되어야 합니다. 일반적으로 호출되거나 실행될 때 많은 명령을 실행하는 라이브러리 함수 및 셸 스크립트는 효율적인 BATS 테스트를 수행할 수 없습니다.

예를 들어 build.sh는 많은 사람들이 작성하는 일반적인 스크립트입니다. 본질적으로 큰 코드 더미입니다. 어떤 사람들은 이 코드 더미를 라이브러리의 함수에 넣을 수도 있습니다. 그러나 BATS 테스트에서 많은 양의 코드를 실행하고 별도의 테스트 케이스에서 발생할 수 있는 모든 유형의 실패를 다루는 것은 불가능합니다. 이 코드 더미를 충분한 적용 범위로 테스트하는 유일한 방법은 코드를 여러 개의 작고 재사용 가능하며 가장 중요하게는 독립적으로 테스트 가능한 기능으로 나누는 것입니다.

라이브러리에 더 많은 기능을 추가하는 것은 간단합니다. 추가 이점은 이러한 기능 중 일부가 그 자체로 놀라울 정도로 유용해질 수 있다는 것입니다. 라이브러리 기능을 여러 개의 작은 기능으로 나누면 소스 BATS의 라이브러리를 테스트하고 테스트하기 위한 다른 명령과 마찬가지로 기능을 실행합니다.

Bash 스크립트는 또한 스크립트가 실행될 때 스크립트의 주요 부분이 호출해야 하는 여러 기능으로 분해되어야 합니다. 또한 BATS를 사용하여 Bash 스크립트를 훨씬 쉽게 테스트할 수 있는 매우 유용한 트릭이 있습니다. 스크립트의 주요 부분에서 실행되는 모든 코드를 가져 와서 run_main<과 같은 함수로 옮깁니다. /강한> . 그런 다음 스크립트 끝에 다음을 추가합니다.

if [[ "${BASH_SOURCE[0]}" =="${0}" ]]
그러면
 run_main
fi

이 추가 코드는 특별한 일을 합니다. source가 있는 환경으로 가져올 때와 스크립트로 실행될 때 스크립트가 다르게 작동하도록 합니다. . 이 트릭을 사용하면 라이브러리를 소싱하고 개별 기능을 테스트하여 라이브러리를 테스트하는 것과 동일한 방식으로 스크립트를 테스트할 수 있습니다. 예를 들어, 다음은 BATS 테스트 가능성을 높이기 위해 리팩토링된 build.sh입니다.

테스트 작성 및 실행

위에서 언급했듯이 BATS는 JUnit, RSpec 또는 Jest와 같은 다른 TAP 호환 테스트 제품군을 사용한 사용자에게 친숙한 구문 및 출력이 있는 TAP 호환 테스트 프레임워크입니다. 테스트는 개별 테스트 스크립트로 구성됩니다. 테스트 스크립트는 하나 이상의 설명이 포함된 @test로 구성됩니다. 테스트 중인 애플리케이션의 단위를 설명하는 블록. 각 @test 블록은 테스트 환경을 준비하고, 테스트할 명령을 실행하고, 테스트된 명령의 종료 및 출력에 대한 어설션을 만드는 일련의 명령을 실행합니다. bat를 사용하여 많은 어설션 함수를 가져옵니다. , 배트 주장 , 및 박쥐 지원 BATS 테스트 스크립트의 시작 부분에서 환경에 로드되는 라이브러리. 다음은 일반적인 BATS 테스트 블록입니다.

@test "CI_COMMIT_REF_SLUG 환경 변수 필요" {
  unset CI_COMMIT_REF_SLUG
  assert_empty "${CI_COMMIT_REF_SLUG}"
  some_command 실행
  assert_failure
"_assert_COMMIT_ "
}

BATS 스크립트에 설정이 포함된 경우 및/또는 해체 기능은 각 테스트 블록이 실행되기 전후에 BATS에 의해 자동으로 실행됩니다. 이를 통해 환경 변수, 테스트 파일을 만들고 하나 또는 모든 테스트에 필요한 기타 작업을 수행한 다음 각 테스트가 실행된 후 이를 분해할 수 있습니다. Build.bat 새로 형식화된 build.sh의 전체 BATS 테스트입니다. 스크립트. (mock_docker 이 테스트의 명령은 아래의 mocking/stubbing 섹션에서 설명합니다.)

테스트 스크립트가 실행될 때 BATS는 exec를 사용합니다. 각 @test 실행 별도의 하위 프로세스로 차단합니다. 이를 통해 하나의 @test에서 환경 변수와 기능까지 내보낼 수 있습니다. 다른 @test에 영향을 주지 않고 s 또는 현재 쉘 세션을 오염시킵니다. 테스트 실행의 출력은 사람이 이해할 수 있고 TAP 소비자가 프로그래밍 방식으로 구문 분석하거나 조작할 수 있는 표준 형식입니다. 다음은 CI_COMMIT_REF_SLUG에 대한 출력의 예입니다. 실패 시 테스트 블록:

 ✗에는 CI_COMMIT_REF_SLUG 환경 변수가 필요합니다.
   (테스트 파일 test/ci_deploy.bats의 test/libs/bats-assert/src/assert.bash, 줄 231의 'assert_output' 함수에서,
   26행)
     `assert_output --partial "CI_COMMIT_REF_SLUG"' 실패

   -- 출력에 하위 문자열이 포함되지 않음 --
   하위 문자열(1줄):
     CI_COMMIT_REF_SLUG
   출력(3줄):
     ./bin/deploy.sh:join_string_by:명령을 찾을 수 없음
     oc 오류
     로그인할 수 없음
   --

   ** 테스트가 실패하여 삭제하지 않았습니다. **

테스트 1개, 실패 1개

다음은 성공적인 테스트의 결과입니다.

✓ requires CI_COMMIT_REF_SLUG environment variable 

도우미

모든 셸 스크립트 또는 라이브러리와 마찬가지로 BATS 테스트 스크립트에는 테스트 간에 공통 코드를 공유하거나 해당 기능을 향상시키는 도우미 라이브러리가 포함될 수 있습니다. bat-assert와 같은 이러한 도우미 라이브러리 및 박쥐 지원 , BATS로 테스트할 수도 있습니다.

라이브러리는 BATS 스크립트와 동일한 테스트 디렉토리 또는 test/libs에 배치할 수 있습니다. 테스트 디렉토리의 파일 수가 감당하기 힘든 경우 디렉토리. BATS는 부하를 제공합니다. 테스트 중인 스크립트와 관련된 Bash 파일의 경로를 취하는 함수(예:test , 우리의 경우) 해당 파일의 소스입니다. 파일은 .bash 접두사로 끝나야 합니다. , 그러나 로드에 전달된 파일의 경로 함수는 접두사를 포함할 수 없습니다. build.bat bat-assert 로드 및 박쥐 지원 라이브러리, 작은 helpers.bash 라이브러리 및 docker_mock.bash 인터프리터 매직 라인 아래 테스트 스크립트 시작 부분에 다음 코드가 있는 라이브러리(아래 설명):

'libs/bats-support/load' 로드
'libs/bats-assert/load' 로드
'helpers' 로드
'docker_mock' 로드

스터빙 테스트 입력 및 외부 호출 조롱

대부분의 Bash 스크립트와 라이브러리는 실행될 때 함수 및/또는 실행 파일을 실행합니다. 종종 종료 상태 또는 출력(stdout , 표준 오류 ) 이러한 기능 또는 실행 파일. 이러한 스크립트를 적절하게 테스트하려면 "stubbing"이라는 프로세스인 특정 테스트 중에 특정 방식으로 작동하도록 설계된 이러한 명령의 가짜 버전을 만들어야 하는 경우가 많습니다. 또한 테스트 중인 프로그램을 감시하여 특정 명령을 호출하는지 확인하거나 특정 인수가 있는 특정 명령을 호출하는지 확인해야 할 수도 있습니다. 이에 대한 자세한 내용은 모든 테스트 시스템에 적용되는 Ruby RSpec의 mocking 및 stubbing에 대한 훌륭한 토론을 확인하세요.

Bash 셸은 BATS 테스트 스크립트에서 조롱 및 스터빙을 수행하는 데 사용할 수 있는 트릭을 제공합니다. 모두 Bash 내보내기를 사용해야 합니다. -f 명령 원래 함수 또는 실행 파일을 재정의하는 함수를 내보내는 플래그입니다. 이것은 테스트된 프로그램이 실행되기 전에 수행되어야 합니다. 다음은 cat을 재정의하는 간단한 예입니다. 실행 가능:

function cat() { echo "이 고양이 ${*}" }
내보내기 -f 고양이

이 메서드는 같은 방식으로 함수를 재정의합니다. 테스트가 테스트 중인 스크립트 또는 라이브러리 내의 함수를 재정의해야 하는 경우 함수가 스텁되거나 모의되기 전에 테스트된 스크립트 또는 라이브러리를 소싱하는 것이 중요합니다. 그렇지 않으면 스크립트가 소싱될 때 스텁/모의가 실제 함수로 대체됩니다. 또한 테스트 중인 명령을 실행하기 전에 stub/mock을 수행해야 합니다. 다음은 build.bat의 예입니다. 인상을 조롱하는 build.sh에 설명된 기능 로그인 기능에 의해 특정 오류 메시지가 발생하도록 하려면:

@test ".login 발생 시 오류 발생" {
  source ${profile_script}
  function raise() { echo "${1} 발생"; }
  export -f raise
  로그인 실행
  assert_failure
  assert_output -p "로그인할 수 없음"
}

일반적으로 테스트 후에 스텁/모의 기능을 설정 해제할 필요가 없습니다. export exec 동안 현재 하위 프로세스에만 영향을 줍니다. 현재 @test 차단하다. 그러나 모의/스텁 명령(예:cat , 세드 등) BATS가 주장 * 함수는 내부적으로 사용합니다. 이러한 모의/스텁 기능은 설정 해제해야 합니다. 이러한 assert 명령이 실행되기 전에 그렇지 않으면 제대로 작동하지 않습니다. 다음은 build.bat의 예입니다. sed를 조롱하는 , build_deployable 실행 기능 및 sed 설정 해제 어설션을 실행하기 전에:

@test ".build_deployable은 정보를 인쇄하고 dry_run이 아닌 경우 수정된 Dockerfile.production 및 publish_image에서 docker 빌드를 실행합니다." {
  local expected_dockerfile='Dockerfile.production'
  local application='application '
  로컬 환경='환경'
  로컬 expected_original_base_image="${application}"
  local expected_candidate_image="${application}-candidate:${environment}"
  로컬 expected_deployable_image ="${application}:${environment}"
  소스 ${profile_script}
  mock_docker 빌드 --build-arg OAUTH_CLIENT_ID --build-arg OAUTH_REDIRECT --build-arg DDS_API_BASE_URL -t "${ expected_deployable_image}" -
  function publish_image() { echo "publish_image ${*}"; }
  export -f publish_image
  function sed() {
    echo "sed ${*}">&2;
    echo "응용 프로그램 후보:환경";
  }
  export -f sed
  run build_deployable "${application}" "${environment}"
  assert_success
  unset sed
  assert_output --regexp "sed. *${expected_dockerfile}"
  assert_output -p "${expected_candidate_image}에서 ${expected_deployable_image} 배포 가능한 ${expected_original_base_image} 빌드"
  assert_output -p "FROM ${expected_candidate_image}가 파이프됨"
 -p "빌드 --build-arg OAUTH_CLIENT_ID --build-arg OAUTH_REDIRECT --build-arg DDS_API_BASE_URL -t ${expected_deployable_image} -"
  assert_output -p "publish_image ${expected_deployable_image}"
}

때때로 같은 명령, 예를 들어 foo는 테스트 중인 동일한 함수에서 다른 인수로 여러 번 호출됩니다. 이러한 상황에서는 함수 집합을 만들어야 합니다.

  • mock_foo:예상 인수를 입력으로 받아 TMP 파일에 유지
  • foo:예상 인수의 지속 목록으로 각 호출을 처리하는 모의 명령 버전입니다. export -f로 내보내야 합니다.
  • cleanup_foo:분해 기능에 사용하기 위해 TMP 파일을 제거합니다. 이것은 제거하기 전에 @test 블록이 성공했는지 확인하기 위해 테스트할 수 있습니다.

이 기능은 종종 다른 테스트에서 재사용되기 때문에 다른 라이브러리처럼 로드할 수 있는 도우미 라이브러리를 만드는 것이 좋습니다.

좋은 예는 docker_mock.bash입니다. . build.bat에 로드됩니다. Docker 실행 파일을 호출하는 함수를 테스트하는 모든 테스트 블록에서 사용됩니다. docker_mock을 사용하는 일반적인 테스트 블록 다음과 같이 보입니다:

@test "도커 푸시가 실패하면 .publish_image가 실패합니다." {
  setup_publish
  local expected_image="image"
  local expected_publishable_image="${CI_REGISTRY_IMAGE}/${expected_image}"
  소스 ${profile_script}
  mock_docker 태그 "${expected_image}" "${expected_publishable_image}"
  mock_docker push "${expected_publishable_image}" and_fail
  publish_image "${expected_image}" 실행
  assert_failure
  assert_output -p "${expected_image}를 ${expected_publishable_image}로 태그 지정"
  assert_output -p "태그 ${expected_image} ${expected_publishable_image}"
  assert_output -p " gitlab 레지스트리에 이미지 푸시"
  assert_output -p "push ${expected_publishable_image}"
}

이 테스트는 Docker가 다른 인수로 두 번 호출될 것이라는 예상을 설정합니다. Docker에 대한 두 번째 호출이 실패하면 테스트된 명령을 실행한 다음 종료 상태와 Docker에 대한 예상 호출을 테스트합니다.

mock_docker.bash에 의해 도입된 BATS의 한 측면 ${BATS_TMPDIR}입니다. 테스트 및 도우미가 표준 위치에서 TMP 파일을 만들고 파괴할 수 있도록 BATS가 처음에 설정하는 환경 변수입니다. mock_docker.bash 라이브러리는 테스트가 실패할 경우 지속된 모의 파일을 삭제하지 않지만 보고 삭제할 수 있도록 해당 위치를 인쇄합니다. 이 디렉토리에서 오래된 모의 파일을 주기적으로 정리해야 할 수도 있습니다.

mocking/stubbing에 대한 주의 사항:build.bats test는 다음과 같은 테스트 원칙을 의식적으로 위반합니다. 소유하지 않은 것을 조롱하지 마십시오! 이 말은 docker와 같이 테스트 개발자가 작성하지 않은 명령에 대한 호출을 요구합니다. , 고양이 , 세드 등은 자체 라이브러리에 래핑되어야 하며, 이를 사용하는 스크립트 테스트에서 조롱되어야 합니다. 그런 다음 외부 명령을 조롱하지 않고 래퍼 라이브러리를 테스트해야 합니다.

이것은 좋은 조언이며 이를 무시하면 비용이 따릅니다. Docker CLI API가 변경되면 테스트 스크립트가 이 변경 사항을 감지하지 못하므로 테스트된 build.sh까지 매니페스트되지 않는 가양성이 발생합니다. 스크립트는 새 버전의 Docker가 있는 프로덕션 설정에서 실행됩니다. 테스트 개발자는 이 표준을 얼마나 엄격하게 준수할지 결정해야 하지만 결정과 관련된 장단점을 이해해야 합니다.

결론

모든 소프트웨어 개발 프로젝트에 테스트 체제를 도입하면 a) 코드와 테스트를 개발하고 유지 관리하는 데 필요한 시간과 조직의 증가와 b) 개발자가 수명 기간 동안 응용 프로그램의 무결성에 대해 더 많은 신뢰를 갖게 됩니다. 테스트 방식은 모든 스크립트와 라이브러리에 적합하지 않을 수 있습니다.

일반적으로 다음 중 하나 이상을 충족하는 스크립트 및 라이브러리는 BATS로 테스트해야 합니다.

  • 소스 제어에 저장할 가치가 있음
  • 중요한 프로세스에 사용되며 장기간 일관되게 실행됩니다.
  • 기능을 추가/제거/수정하려면 주기적으로 수정해야 합니다.
  • 다른 사람들이 사용함

테스트 원칙을 하나 이상의 Bash 스크립트 또는 라이브러리에 적용하기로 결정하면 BATS는 다른 소프트웨어 개발 환경에서 사용할 수 있는 포괄적인 테스트 기능을 제공합니다.

감사의 말:저에게 BATS 테스트를 소개해주신 Darrin Mann에게 감사드립니다.