BDD 테스팅이란 무엇인가요? 행동 주도 개발 프레임워크(Behavior-Driven Development Framework)
⚡ 스마트 요약
BDD 테스트는 애플리케이션 동작을 명료한 Given-When-Then 언어로 설명하여 분석가, 개발자, 테스터가 하나의 명세를 공유하도록 합니다. 이 가이드에서는 Behave를 사용하여 REST API 테스트에 이러한 접근 방식을 적용하는 방법을 보여줍니다. Python 설정, 기능 파일, 단계별 구현, 실행 및 보고를 포함하는 프레임워크입니다.

BDD(행동 중심 개발) 테스트란 무엇입니까?
BDD(행동 중심 개발) 테스트 BDD는 애자일 소프트웨어 개발 기법 중 하나이며, TDD(테스트 주도 개발)의 확장 개념입니다. BDD에서는 테스트 케이스를 프로그래밍 경험이 없는 사람도 읽을 수 있는 자연어로 작성합니다.
BDD 테스트는 어떻게 작동하나요?
인터넷 뱅킹 애플리케이션에 자금 이체 모듈을 개발하는 업무를 맡았다고 가정해 보세요.
이를 테스트하는 방법은 여러 가지가 있습니다.
- 송금은 송금 계좌에 충분한 잔액이 있는 경우에만 이루어져야 합니다.
- 목적지 a/c 세부 정보가 정확한 경우 자금 이체가 이루어져야 합니다.
- 사용자가 입력한 거래 비밀번호/RSA 코드/보안 인증이 올바른 경우에만 자금 이체가 진행됩니다.
- 은행 휴일이라 하더라도 자금 이체는 이루어져야 합니다.
- 자금 이체는 계좌 소유자가 설정한 미래 날짜에 이루어져야 합니다.
The 테스트 시나리오 일정 금액 X를 Y일 또는 Y개월 동안 이체하거나, 중지하는 등의 추가 기능을 고려할수록 더욱 정교하고 복잡해집니다.ping 총액이 Z에 도달하면 예정된 이체가 이루어지는 식입니다.
개발자들은 일반적으로 기능을 먼저 개발하고 테스트 코드는 나중에 작성하는 경향이 있습니다. 위 사례에서 볼 수 있듯이, 테스트 케이스 이곳의 개발은 복잡하기 때문에 개발자는 작업을 미룰 것입니다. 지원 출시 전까지는 괜찮지만, 출시 시점에 이르면 빠르지만 비효율적인 테스트가 수행됩니다.
이러한 문제를 극복하기 위해 행동 주도 개발(BDD)이 고안되었습니다. BDD는 개발자가 전체 테스트 프로세스를 더 쉽게 수행할 수 있도록 해줍니다.
BDD에서는 무엇이든 작성해야 합니다. 주어진 시기와 그때 단계. 위와 동일한 예시를 BDD 방식으로 살펴보겠습니다.
Given that a fund transfer module in net banking application has been developed And I am accessing it with proper authentication When I shall transfer with enough balance in my source account Or I shall transfer on a Bank Holiday Or I shall transfer on a future date And destination a/c details are correct And transaction password / rsa code / security authentication for the transaction is correct And press or click send button Then amount must be transferred And the event will be logged in log file
이 양식은 작성, 읽기, 이해하기 쉽습니다. 자금 이체 모듈에 대한 모든 가능한 테스트 케이스를 포괄하며, 더 많은 테스트 케이스를 수용하도록 신속하게 수정할 수 있습니다. 또한 모듈에 대한 살아있는 문서처럼 읽힙니다. BDD는 TDD에서 발전했기 때문에, 툴링으로 넘어가기 전에 둘의 차이점을 명확히 이해하는 것이 중요합니다.
BDD와 TDD의 주요 차이점
두 방식 모두 테스트 우선이며, 피드백 주기를 단축합니다. 차이점은 테스트를 작성하는 사람, 사용하는 언어, 그리고 애플리케이션의 어떤 계층을 테스트하는지에 있습니다.
| 아래 | TDD(테스트 주도 개발) | BDD (행동 주도 개발) |
|---|---|---|
| 초점 | 코드 단위가 내부적으로 작동하는 방식 | 사용자 관점에서 시스템의 동작 방식 |
| Language | 프로그래밍 언어 어설션 | 자연어 Given-When-Then 시나리오 |
| 주요 저자 | 개발자 | 비즈니스 분석가, 제품 책임자, 테스터, 개발자가 함께 |
| Readable 프로그래머가 아닌 사람들에 의해 | 아니 | 가능 |
| 일반적인 범위 | 단위 수준 | 기능, API 및 승인 수준 |
| 일반적인 도구 | JUnit, pytest, NUnit | 행동하다, Cucumber스펙플로우, JBehave |
두 가지는 경쟁 관계라기보다는 상호 보완적입니다. 팀은 일반적으로 다음과 같은 관계를 유지합니다. TDD 단위 수준 설계를 수행하고, 이해관계자들이 실제로 승인하는 동작을 설명하기 위해 BDD 시나리오를 추가합니다. REST 엔드포인트는 바로 이러한 승인 계층에 위치하므로 BDD에 매우 적합합니다.
REST API 테스트란 무엇입니까?
REST가 API 구축에 널리 사용되는 방식이 되면서, UI 테스트 케이스와 마찬가지로 REST API 테스트 케이스 자동화 또한 중요해졌습니다. API 테스트 이 테스트는 POST, GET, PUT, DELETE 메서드를 각각 사용하여 CRUD(생성-읽기-업데이트-삭제) 작업을 테스트하는 것을 포함합니다.
행동이란 무엇입니까?
행동은 인기있는 것 중 하나입니다 Python BDD 테스트 프레임워크. Behave의 작동 방식은 다음과 같습니다.
- 기능 파일은 비즈니스 분석가, 스폰서 또는 해당 동작 시나리오를 담당하는 사람이 작성합니다. 기능 파일은 자연어 형식으로 기능 또는 기능의 일부를 설명하고 예상 결과에 대한 대표적인 예시를 포함합니다.
- 이러한 시나리오 단계는 작성된 단계 구현에 매핑됩니다. Python.
- 선택적으로 환경 제어 기능을 사용하여 단계, 시나리오, 기능 또는 전체 실행 전후에 코드를 실행할 수 있습니다.
각 파일의 역할이 명확해졌으므로 이제 프레임워크를 설치할 수 있습니다.
BDD 테스트 프레임워크 동작 설정 Windows
설치:
- 다운로드 및 설치 Python 3에서 https://www.python.org/
- Behave를 설치하려면 명령 프롬프트에서 다음 명령을 실행하십시오.
pip install behave- IDE : PyCharm 여기서는 커뮤니티 에디션이 사용되었습니다. https://www.jetbrains.com/pycharm/download/
프로젝트 설정 :
- 새 프로젝트를 만듭니다
- 다음과 같은 디렉토리 구조를 생성하십시오.
위 스크린샷은 Behave가 예상하는 레이아웃을 보여줍니다: a 풍모 기능 파일을 담고 있는 디렉토리와 그 안에 중첩된 디렉토리 단계 디렉토리 Python 구현체입니다. Behave는 이름으로 둘 다 검색하므로 폴더 이름이 정확히 일치해야 합니다.
기능 파일:
이제 기능 파일을 만들어 보겠습니다. Sample_REST_API_Testing.feature이 기능은 '게시물' 서비스에 대한 CRUD 작업을 수행하는 것입니다.
이 예제에서는 https://jsonplaceholder.typicode.com/ 이 게시물은 쓰기 요청을 수락하고 변경 사항을 저장하지 않고 실제와 유사한 응답을 반환하는 무료 가짜 REST API인 샘플 REST 서비스를 제공합니다.
예제 POST 시나리오
Scenario: POST post example -> creating a new post item using the 'posts' service Given I set post posts API endpoint -> prerequisite: sets the URL of the posts service When I set HEADER param request content type as "application/json" And set request body And send POST HTTP request -> the actual test step Then I receive valid HTTP response code 201 And Response body "POST" is non-empty -> verification of the response body
마찬가지로 나머지 시나리오도 다음과 같이 작성할 수 있습니다.
Sample_REST_API_Testing.feature
Feature: Test CRUD methods in Sample REST API testing framework Background: Given I set sample REST API url Scenario: POST post example Given I Set POST posts api endpoint When I Set HEADER param request content type as "application/json" And Set request Body And Send a POST HTTP request Then I receive valid HTTP response code 201 And Response BODY "POST" is non-empty Scenario: GET posts example Given I Set GET posts api endpoint "1" When I Set HEADER param request content type as "application/json" And Send GET HTTP request Then I receive valid HTTP response code 200 for "GET" And Response BODY "GET" is non-empty Scenario: UPDATE posts example Given I Set PUT posts api endpoint for "1" When I Set Update request Body And Send PUT HTTP request Then I receive valid HTTP response code 200 for "PUT" And Response BODY "PUT" is non-empty Scenario: DELETE posts example Given I Set DELETE posts api endpoint for "1" When I Send DELETE HTTP request Then I receive valid HTTP response code 200 for "DELETE"
단계 구현
이제 위 시나리오에서 사용된 기능 단계에 대한 구현을 작성할 수 있습니다. Python "steps" 디렉토리 내의 파일들.
Behave 프레임워크는 데코레이터를 기능 파일 술어와 비교하여 단계 함수를 식별합니다. 예를 들어, 주어진 피처 파일 시나리오의 술어는 다음 조건을 만족하는 계단 함수를 검색합니다. @given 데코레이터. 동일한 매칭이 다른 경우에도 적용됩니다. 인셀덤 공식 판매점인 그때'But'과 'And'의 경우, 단계 함수는 이전 단계와 동일한 데코레이터를 사용합니다. 예를 들어, 'And'가 'Given' 다음에 오는 경우, 해당하는 단계 함수 데코레이터는 다음과 같습니다. @given.
예를 들어, POST 요청의 'When' 단계는 다음과 같이 구현할 수 있습니다. 어떻게 구현하는지 주목하세요. "application/json" 피처 파일의 값이 중괄호로 묶인 플레이스홀더로 전달됩니다. 이를 파라미터화라고 합니다.
# Decorator: the braced name captures a value from the feature file @when(u'I Set HEADER param request content type as "{header_content_type}"') def step_impl(context, header_content_type): # Step implementation: set the content type on the request header request_headers['Content-Type'] = header_content_type
마찬가지로, 해당 단계의 다른 단계 구현도 마찬가지입니다. Python 파일은 다음과 같습니다 :
샘플_단계_구현.py
from behave import given, when, then, step import requests api_endpoints = {} request_headers = {} response_codes = {} response_texts = {} request_bodies = {} api_url = None @given(u'I set sample REST API url') def step_impl(context): global api_url api_url = 'https://jsonplaceholder.typicode.com' # START POST Scenario @given(u'I Set POST posts api endpoint') def step_impl(context): api_endpoints['POST_URL'] = api_url + '/posts' print('url :' + api_endpoints['POST_URL']) @when(u'I Set HEADER param request content type as "{header_content_type}"') def step_impl(context, header_content_type): request_headers['Content-Type'] = header_content_type # "And" or "But" steps are renamed by behave to match their preceding step @when(u'Set request Body') def step_impl(context): request_bodies['POST'] = {"title": "foo", "body": "bar", "userId": "1"} @when(u'Send POST HTTP request') def step_impl(context): # send the request and save the response object response = requests.post(url=api_endpoints['POST_URL'], json=request_bodies['POST'], headers=request_headers) response_texts['POST'] = response.text print("post response :" + response.text) response_codes['POST'] = response.status_code @then(u'I receive valid HTTP response code 201') def step_impl(context): print('Post rep code ;' + str(response_codes['POST'])) assert response_codes['POST'] == 201 # END POST Scenario # START GET Scenario @given(u'I Set GET posts api endpoint "{id}"') def step_impl(context, id): api_endpoints['GET_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['GET_URL']) @when(u'Send GET HTTP request') def step_impl(context): response = requests.get(url=api_endpoints['GET_URL'], headers=request_headers) response_texts['GET'] = response.text response_codes['GET'] = response.status_code @then(u'I receive valid HTTP response code 200 for "{request_name}"') def step_impl(context, request_name): print('Get rep code for ' + request_name + ':' + str(response_codes[request_name])) assert response_codes[request_name] == 200 @then(u'Response BODY "{request_name}" is non-empty') def step_impl(context, request_name): print('request_name: ' + request_name) print(response_texts) assert response_texts[request_name] is not None # END GET Scenario # START PUT/UPDATE @given(u'I Set PUT posts api endpoint for "{id}"') def step_impl(context, id): api_endpoints['PUT_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['PUT_URL']) @when(u'I Set Update request Body') def step_impl(context): request_bodies['PUT'] = {"title": "foo", "body": "bar", "userId": "1", "id": "1"} @when(u'Send PUT HTTP request') def step_impl(context): response = requests.put(url=api_endpoints['PUT_URL'], json=request_bodies['PUT'], headers=request_headers) response_texts['PUT'] = response.text print("update response :" + response.text) response_codes['PUT'] = response.status_code # END PUT/UPDATE # START DELETE @given(u'I Set DELETE posts api endpoint for "{id}"') def step_impl(context, id): api_endpoints['DELETE_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['DELETE_URL']) @when(u'I Send DELETE HTTP request') def step_impl(context): response = requests.delete(url=api_endpoints['DELETE_URL']) response_texts['DELETE'] = response.text print("DELETE response :" + response.text) response_codes['DELETE'] = response.status_code # END DELETE
⚠️ 참고: 상태 코드를 비교하는 것 == 보다는 is 중요한 문제입니다. is 연산자는 객체의 동일성을 테스트하는 것이지 동등성을 테스트하는 것이 아닙니다. Python 3.8 그리고 나중에 제기합니다 구문 경고: 리터럴과 함께 "is" 사용 해당 패턴의 경우. 다음과 같이 작성된 모든 주장은 assert code is 201 다음과 같이 다시 작성되어야 합니다. assert code == 201.
테스트 실행
테스트 스크립트 개발이 완료되었으므로 테스트를 실행해 보겠습니다. 명령 프롬프트에서 다음 명령을 실행하여 기능 파일을 실행하세요.
:: Run a single feature file with the pretty formatter behave -f pretty features\feature_files_folder\Sample_REST_API_Testing.feature :: Run every feature in the project behave
다음은 테스트 실행 결과입니다.
콘솔에 보고서 표시
콘솔 출력은 개발 중에 편리하지만, 이해 관계자들은 일반적으로 읽기 쉬운 보고서를 선호하며, Allure는 바로 그러한 보고서를 제공합니다.
보고서
먼저 Allure Behave 포맷터와 Allure 명령줄 도구를 설치하세요. 포맷터는 다음과 같습니다. Python 패키지 형태이며, 명령줄 도구는 별도로 다운로드해야 합니다. 자세한 내용은 해당 설명서를 참조하십시오. Allure 보고서 문서.
:: 1. Install the formatter pip install allure-behave :: 2. Run the tests and write raw results to a folder behave -f allure_behave.formatter:AllureFormatter -o reports/allure-results features/ :: 3. Render and open the HTML report allure serve reports/allure-results
이렇게 하면 테스트 결과 보고서가 다음과 같이 보기 좋고 유익한 형식으로 생성됩니다.
HTML 형식의 테스트 보고서
개별 시나리오 결과를 표시하는 테스트 보고서
API가 확장됨에 따라 가독성을 유지할 때만 제대로 작동하는 스위트가 유용하며, 바로 이 점에서 기능 파일의 체계적인 관리가 빛을 발합니다.
Behave 기능 파일을 작성하기 위한 최고의 모범 사례
Behave 제품군은 시나리오에서 동작 대신 클릭과 페이로드를 설명할 경우 성능이 빠르게 저하됩니다. 아래의 모범 사례는 비즈니스 이해 관계자가 기능을 쉽게 이해할 수 있도록 하면서 동시에 Behave 제품군의 성능을 유지하는 데 도움이 됩니다.ping 전에, Python 엔지니어가 유지 관리할 수 있는 계층입니다.
- 구현 방식이 아닌 동작 방식을 설명하세요. "고객의 잔액이 충분할 경우"라고 작성하고, "잔액 열이 500일 경우"라고 작성하지 마십시오. 기능 파일에는 시스템이 수행하는 작업이 명시되어 있고, 단계 파일에는 해당 작업이 어떻게 확인되는지가 명시되어 있습니다.
- 시나리오별로 하나의 행동만 유지하세요. 상태 코드, 응답 본문 및 데이터베이스 행을 확인하는 시나리오는 실제로 세 가지 시나리오로 나눌 수 있습니다. 이렇게 분리하면 오류가 발생했을 때 원인을 직접적으로 파악할 수 있습니다.
- 공유 설정 화면을 백그라운드로 이동합니다. 위 예시는 다음을 사용합니다.
Background: Given I set sample REST API url모든 시나리오가 기본값을 상속받도록 URL 반복하지 않고. - 복제하는 대신 매개변수화하세요. 중괄호로 묶인 자리 표시자(예: ...)
"{request_name}"하나의 스텝 함수로 GET, PUT, DELETE 어설션을 모두 처리할 수 있으므로, 이 예제는 시나리오보다 스텝 정의가 훨씬 적게 필요합니다. - 데이터 변형에 대해서는 시나리오 개요를 사용하십시오. 여러 입력에 대해 동일한 동작을 증명해야 할 때,
Examples:표가 복사된 시나리오보다 더 명확합니다. - 시나리오 간의 종속성을 피하십시오. Behave는 모든 구성에서 POST 시나리오가 GET 시나리오보다 먼저 실행된다는 것을 보장하지 않습니다. 각 시나리오는 필요한 상태를 직접 생성해야 합니다.
- 모듈 수준의 전역 변수를 컨텍스트로 대체합니다. 이 예제에서는 간결성을 위해 엔드포인트와 응답을 모듈 딕셔너리에 저장합니다. 실제 운영 환경에서는 Behave의 저장소에 저장하십시오.
context객체이므로 시나리오 간에 상태가 깔끔하게 재설정됩니다. - 선택적 실행을 위한 태그 시나리오. 태그 (예: ...)
@smokeor@regression수behave --tags=@smoke이를 통해 파이프라인 피드백 속도가 빨라집니다.
첫 번째 기능 파일부터 이러한 습관을 적용하면 BDD 스위트는 초기 CRUD 예제를 넘어 훨씬 더 오랫동안 유용하게 활용될 수 있습니다. 이 접근 방식을 더욱 확장하는 팀은 종종 다음과 같은 방법과 함께 사용합니다. Cucumber JVM 프로젝트의 경우 또는 Postman 탐색적 API 점검용입니다.






