HTTP 요청 테스트: 개발자의 생존 도구

게시 됨: 2022-03-11

테스팅 스위트가 불가능할 때 해야 할 일

우리(프로그래머 및/또는 고객)는 예상되는 결과물과 해당 결과물에 대한 자동화된 테스트를 모두 작성하는 데 리소스가 제한적일 때가 있습니다. 응용 프로그램이 충분히 작으면 기능을 추가하거나 버그를 수정하거나 리팩터링할 때 코드의 다른 곳에서 (대부분) 어떤 일이 발생하는지 기억하기 때문에 모서리를 잘라내고 테스트를 건너뛸 수 있습니다. 즉, 우리가 항상 작은 응용 프로그램으로 작업하는 것은 아니며 시간이 지남에 따라 점점 더 커지고 복잡해지는 경향이 있습니다. 이것은 수동 테스트를 어렵고 짜증나게 만듭니다.

지난 몇 개의 프로젝트에서는 자동화된 테스트 없이 강제로 작업을 해야 했고 솔직히 말해서 코드 푸시 후 클라이언트가 나에게 이메일을 보내어 내가 코드를 건드리지도 않은 곳에서 애플리케이션이 중단되고 있다고 말하는 것이 창피했습니다.

따라서 내 고객에게 자동화된 테스트 프레임워크를 추가할 예산이나 계획이 없는 경우 각 개별 페이지에 HTTP 요청을 보내고 응답 헤더를 구문 분석하고 '200'을 찾는 방식으로 전체 웹사이트의 기본 기능을 테스트하기 시작했습니다. 응답. 단순하고 단순해 보이지만 실제로 테스트, 단위, 기능 또는 통합을 작성하지 않고도 충실도를 보장하기 위해 할 수 있는 일이 많이 있습니다.

자동화된 테스트

웹 개발에서 자동화된 테스트는 단위 테스트, 기능 테스트 및 통합 테스트의 세 가지 주요 테스트 유형으로 구성됩니다. 우리는 종종 단위 테스트를 기능 및 통합 테스트와 결합하여 모든 것이 전체 애플리케이션으로 원활하게 실행되는지 확인합니다. 이러한 테스트가 동시에 실행되거나 순차적으로(단일 명령 또는 클릭으로) 실행될 때 단위 여부에 관계없이 자동화된 테스트라고 부르기 시작합니다.

이 테스트의 대부분(적어도 웹 개발에서는)의 목적은 모든 애플리케이션 페이지가 문제 없이, 치명적인(애플리케이션 정지) 오류나 버그 없이 렌더링되는지 확인하는 것입니다.

단위 테스트

단위 테스트는 코드의 가장 작은 부분인 단위가 올바른 작동을 위해 독립적으로 테스트되는 소프트웨어 개발 프로세스입니다. 다음은 Ruby의 예입니다.

 test “should return active users” do active_user = create(:user, active: true) non_active_user = create(:user, active: false) result = User.active assert_equal result, [active_user] end

기능 테스트

기능 테스트는 오류 경로 및 경계 사례를 포함한 모든 사용자 상호 작용 시나리오를 다루도록 설계된 시스템 또는 소프트웨어의 기능을 확인하는 데 사용되는 기술입니다.

참고: 모든 예제는 Ruby에 있습니다.

 test "should get index" do get :index assert_response :success assert_not_nil assigns(:object) end

통합 테스트

모듈이 단위 테스트되면 순차적으로 하나씩 통합되어 조합 동작을 확인하고 요구 사항이 올바르게 구현되었는지 검증합니다.

 test "login and browse site" do # login via https https! get "/login" assert_response :success post_via_redirect "/login", username: users(:david).username, password: users(:david).password assert_equal '/welcome', path assert_equal 'Welcome david!', flash[:notice] https!(false) get "/articles/all" assert_response :success assert assigns(:articles) end

이상적인 세계에서의 테스트

테스트는 업계에서 널리 받아들여지고 있으며 타당합니다. 좋은 테스트를 통해 다음을 수행할 수 있습니다.

  • 최소한의 인력으로 전체 애플리케이션의 품질을 보장합니다.
  • 테스트 실패로 인해 코드가 중단되는 위치를 정확히 알기 때문에 버그를 더 쉽게 식별할 수 있습니다.
  • 코드에 대한 자동 문서 생성
  • Stack Overflow의 어떤 친구에 따르면 '코딩 변비'를 피하세요. "다음에 무엇을 작성해야 할지 모르거나 어려운 일이 있을 때 작게 작성하는 것부터 시작하세요. .”

나는 테스트가 얼마나 멋진지, 그리고 그들이 세상을 어떻게 바꿨는지에 대해 계속 말할 수 있지만 요점은 알 수 있습니다. 개념적으로 테스트는 굉장합니다.

관련 항목: 단위 테스트, 테스트 가능한 코드 작성 방법 및 중요한 이유

실제 세계에서의 테스트

세 가지 유형의 테스트 모두에 장점이 있지만 대부분의 프로젝트에서 작성되지는 않습니다. 왜요? 글쎄, 내가 그것을 분해하자 :

시간/마감

모든 사람에게는 마감일이 있으며 새로운 테스트를 작성하면 마감일을 맞추는 데 방해가 될 수 있습니다. 애플리케이션 해당 테스트를 작성하는 데 시간이 반(또는 그 이상) 소요될 수 있습니다. 자, 여러분 중에는 궁극적으로 시간이 절약된다는 점을 들어 이에 동의하지 않는 분들도 계시지만 저는 그렇지 않다고 생각합니다. 그 이유는 '의견의 차이'에서 설명하겠습니다.

클라이언트 문제

종종 클라이언트는 테스트가 무엇인지 또는 애플리케이션에 가치가 있는 이유를 제대로 이해하지 못합니다. 클라이언트는 신속한 제품 제공에 더 관심을 갖는 경향이 있으므로 프로그래밍 방식 테스트를 비생산적인 것으로 간주합니다.

또는 클라이언트가 이러한 테스트를 구현하는 데 필요한 추가 시간을 지불할 예산이 없는 것처럼 간단할 수도 있습니다.

지식 부족

실제 세계에는 테스트가 존재한다는 사실을 모르는 상당한 수준의 개발자 부족이 있습니다. 모든 회의, 모임, 콘서트 (내 꿈에서도)에서 나는 테스트 작성 방법, 테스트 내용, 테스트 프레임 워크 설정 방법 등을 모르는 개발자를 만납니다. 켜짐. 테스트는 학교에서 정확히 가르치지 않으며 테스트를 실행하기 위해 프레임워크를 설정/학습하는 것이 번거로울 수 있습니다. 네, 진입장벽이 확실합니다.

'수고가 많다'

테스트 작성은 새로운 프로그래머와 숙련된 프로그래머 모두에게, 심지어 세상을 바꾸는 천재 유형에게도 압도적일 수 있으며 무엇보다도 테스트 작성은 흥미롭지 않습니다. "내 고객에게 깊은 인상을 줄 결과를 제공하는 주요 기능을 구현할 수 있는데 왜 내가 흥미롭지 않은 바쁜 업무에 참여해야 합니까?"라고 생각할 수 있습니다. 어려운 주장입니다.

마지막으로, 테스트를 작성하는 것이 어렵고 컴퓨터 공학 학생들은 이에 대한 훈련을 받지 않았습니다.

아, 그리고 단위 테스트로 리팩토링하는 것은 재미가 없습니다.

의견 차이

제 생각에는 단위 테스트가 알고리즘 논리에는 의미가 있지만 살아있는 코드를 조정하는 데에는 그다지 적합하지 않습니다.

사람들은 테스트 작성에 미리 추가 시간을 투자하고 있지만 나중에 코드를 디버깅하거나 변경할 때 시간을 절약할 수 있다고 주장합니다. 나는 다른 것을 간청하고 한 가지 질문을 제안합니다. 귀하의 코드는 정적입니까, 아니면 계속 변경됩니까?

우리 대부분은 항상 변하고 있습니다. 성공적인 소프트웨어를 작성하고 있다면 항상 기능을 추가하고, 기존 기능을 변경하고, 제거하고, 먹으며, 이러한 변경 사항을 수용하려면 테스트를 계속 변경해야 하며 테스트를 변경하는 데 시간이 걸립니다.

그러나 일종의 테스트가 필요합니다.

어떤 종류의 테스트도 없는 것이 최악의 경우라고 주장하는 사람은 아무도 없을 것입니다. 코드를 변경한 후에는 실제로 작동하는지 확인해야 합니다. 많은 프로그래머가 수동으로 기본 사항을 테스트하려고 합니다. 페이지가 브라우저에서 렌더링됩니까? 양식이 제출되고 있습니까? 올바른 콘텐츠가 표시되고 있습니까? 등등이 있지만 제 생각에는 이것은 야만적이고 비효율적이며 노동 집약적입니다.

내가 대신 사용하는 것

웹 앱을 테스트하는 목적은 수동이든 자동화든 상관없이 주어진 페이지가 치명적인 오류 없이 사용자의 브라우저에서 렌더링되고 콘텐츠가 올바르게 표시되는지 확인하는 것입니다. 이를 달성하는 한 가지 방법(대부분의 경우 더 쉬운 방법)은 앱의 끝점에 HTTP 요청을 보내고 응답을 구문 분석하는 것입니다. 응답 코드는 페이지가 성공적으로 전달되었는지 여부를 알려줍니다. HTTP 요청의 응답 본문을 구문 분석하고 특정 텍스트 문자열 일치를 검색하여 콘텐츠를 쉽게 테스트하거나, 한 단계 더 좋아져서 nokogiri와 같은 웹 스크래핑 라이브러리를 사용할 수 있습니다.

일부 엔드포인트에 사용자 로그인이 필요한 경우 로그인을 위한 기계화 또는 특정 링크 클릭과 같은 상호 작용(통합 테스트를 수행할 때 이상적)을 자동화하도록 설계된 라이브러리를 사용할 수 있습니다. 실제로 자동화된 테스트의 큰 그림에서 이것은 통합 또는 기능 테스트(사용 방법에 따라 다름)처럼 보이지만 훨씬 빠르게 작성하고 기존 프로젝트에 포함하거나 새 프로젝트에 추가할 수 있습니다. , 전체 테스트 프레임워크를 설정하는 것보다 적은 노력으로 수행할 수 있습니다. 에 딱 맞다!

관련: 프리랜서 QA 엔지니어의 상위 3%를 고용하십시오.

에지 케이스는 값의 범위가 넓은 대규모 데이터베이스를 다룰 때 또 다른 문제를 나타냅니다. 예상되는 모든 데이터 세트에서 애플리케이션이 원활하게 작동하는지 테스트하는 것은 어려울 수 있습니다.

그것에 대해 가는 한 가지 방법은 모든 극단적인 경우를 예상하고(단순히 어려울 뿐만 아니라 종종 불가능한 경우도 있음) 각각에 대한 테스트를 작성하는 것입니다. 이것은 쉽게 수백 줄의 코드가 될 수 있고(공포를 상상해보십시오) 유지 관리가 번거롭습니다. 그러나 HTTP 요청과 단 한 줄의 코드를 사용하여 개발 머신이나 스테이징 서버에서 로컬로 다운로드한 프로덕션 데이터에서 직접 이러한 엣지 케이스를 테스트할 수 있습니다.

물론, 이 테스트 기술은 만병통치약이 아니며 다른 방법과 마찬가지로 많은 단점이 있지만 이러한 유형의 테스트를 작성하고 수정하는 것이 더 빠르고 더 쉽다는 것을 알게 되었습니다.

실습: HTTP 요청으로 테스트하기

어떤 종류의 테스트도 수반하지 않고 코드를 작성하는 것은 좋은 생각이 아니라는 것을 이미 확인했기 때문에 전체 애플리케이션에 대한 가장 기본적인 테스트는 모든 페이지에 로컬로 HTTP 요청을 보내고 응답 헤더를 구문 분석하는 것입니다. 200 (또는 원하는) 코드.

예를 들어 위의 테스트(특정 내용과 치명적인 오류를 찾는 테스트)를 대신 HTTP 요청(Ruby에서)으로 작성한다면 다음과 같을 것입니다.

 # testing for fatal error http_code = `curl -X #{route[:method]} -s -o /dev/null -w "%{http_code}" #{Rails.application.routes.url_helpers.articles_url(host: 'localhost', port: 3000) }` if http_code !~ /200/ return “articles_url returned with #{http_code} http code.” end # testing for content active_user = create(:user, name: “user1”, active: true) non_active_user = create(:user, name: “user2”, active: false) content = `curl #{Rails.application.routes.url_helpers.active_user_url(host: 'localhost', port: 3000) }` if content !~ /#{active_user.name}/ return “Content mismatch active user #{active_user.name} not found in text body” #You can customise message to your liking end if content =~ /#{non_active_user.name}/ return “Content mismatch non active user #{active_user.name} found in text body” #You can customise message to your liking end

curl -X #{route[:method]} -s -o /dev/null -w "%{http_code}" #{Rails.application.routes.url_helpers.articles_url(host: 'localhost', port: 3000) } 많은 테스트 사례를 다룹니다. 기사 페이지에서 오류를 발생시키는 모든 방법은 여기에서 포착되므로 한 테스트에서 수백 줄의 코드를 효과적으로 처리합니다.

특히 콘텐츠 오류를 잡는 두 번째 부분은 페이지의 콘텐츠를 확인하기 위해 여러 번 사용할 수 있습니다. (더 복잡한 요청은 mechanize 를 사용하여 처리할 수 있지만 이는 이 블로그의 범위를 벗어납니다.)

이제 특정 페이지가 크고 다양한 데이터베이스 값 집합에서 작동하는지 테스트하려는 경우(예: 기사 페이지 템플릿이 프로덕션 데이터베이스의 모든 기사에 대해 작동함) 다음을 수행할 수 있습니다.

 ids = Article.all.select { |post| `curl -s -o /dev/null -w “%{http_code}” #{Rails.application.routes.url_helpers.article_url(post, host: 'localhost', port: 3000) }`.to_i != 200).map(&:id) return ids

그러면 렌더링되지 않은 데이터베이스의 모든 기사 ID 배열이 반환되므로 이제 수동으로 특정 기사 페이지로 이동하여 문제를 확인할 수 있습니다.

이제 독립 실행형 스크립트를 테스트하거나 이메일을 보내는 것과 같은 특정 경우에는 이러한 테스트 방법이 작동하지 않을 수 있으며 각 테스트에 대해 엔드포인트를 직접 호출하기 때문에 단위 테스트보다 느립니다. 단위 테스트나 기능 테스트, 또는 둘 다를 가질 수 없습니다. 이것은 없는 것보다 낫습니다.

이러한 테스트를 구성하는 방법은 무엇입니까? 복잡하지 않은 소규모 프로젝트의 경우 모든 테스트를 하나의 파일에 작성하고 변경 사항을 커밋하기 전에 매번 해당 파일을 실행할 수 있지만 대부분의 프로젝트에는 일련의 테스트가 필요합니다.

테스트 대상에 따라 일반적으로 엔드포인트당 2~3개의 테스트를 작성합니다. 개별 콘텐츠를 테스트할 수도 있지만(단위 테스트와 유사), 모든 단위에 대해 HTTP 호출을 수행하게 되므로 중복되고 느릴 것입니다. 그러나 다른 한편으로는 더 명확하고 이해하기 쉬울 것입니다.

각 주요 끝점에 고유한 파일이 있는 일반 테스트 폴더에 테스트를 넣는 것이 좋습니다(예: Rails에서 각 모델/컨트롤러에는 각각 하나의 파일이 있음). 이 파일은 우리가 무엇을 하느냐에 따라 세 부분으로 나눌 수 있습니다. 테스트 중입니다. 나는 종종 적어도 세 가지 테스트를 합니다.

테스트 1

페이지가 치명적인 오류 없이 반환되는지 확인합니다.

테스트 1은 페이지가 치명적인 오류 없이 반환되는지 확인합니다.

Post 에 대한 모든 엔드포인트 목록을 만들고 각 페이지가 오류 없이 렌더링되는지 확인하기 위해 반복했습니다. 모든 것이 잘 진행되고 모든 페이지가 렌더링되었다고 가정하면 터미널에 다음과 같은 내용이 표시됩니다. ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of failed url(s) -- []

페이지가 렌더링되지 않으면 다음과 같은 내용이 표시됩니다(이 예에서는 posts/index page 에 오류가 있으므로 렌더링되지 않음). ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of failed url(s) -- [{:url=>”posts_url”, :params=>[], :method=>”GET”, :http_code=>”500”}]

테스트 2

예상되는 모든 콘텐츠가 있는지 확인합니다.

테스트 2에서는 모든 예상 콘텐츠가 있는지 확인합니다.

예상한 모든 콘텐츠가 페이지에서 발견되면 결과는 다음과 같습니다(이 예에서는 posts/:id 에 게시물 제목, 설명 및 상태가 있는지 확인합니다): ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of content(s) not found on Post#show page with post id: 1 -- []

페이지에서 예상한 콘텐츠를 찾을 수 없는 경우(여기에서는 페이지에 게시물 상태가 표시될 것으로 예상됩니다. 게시물이 활성화된 경우 '활성', 게시물이 비활성화된 경우 '비활성화됨') 결과는 다음과 같습니다. ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of content(s) not found on Post#show page with post id: 1 -- [“Active”]

테스트 3

페이지가 모든 데이터세트(있는 경우)에서 렌더링되는지 확인합니다.

테스트 3은 페이지가 모든 데이터세트에서 렌더링되는지 확인합니다.

모든 페이지가 오류 없이 렌더링되면 빈 목록이 표시됩니다. ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of post(s) with error in rendering -- []

일부 레코드의 콘텐츠에 렌더링 문제가 있는 경우(이 예에서는 ID가 2 및 5인 페이지에서 오류가 발생함) 결과는 다음과 같습니다. ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of post(s) with error on rendering -- [2,5]

위의 데모 코드를 만지작거리고 싶다면 여기 내 github 프로젝트가 있습니다.

그래서 어느 것이 더 낫습니까? 때에 따라 다르지…

HTTP 요청 테스트는 다음과 같은 경우에 가장 적합할 수 있습니다.

  • 웹 앱으로 작업 중입니다.
  • 당신은 시간 촉박에 있고 빨리 뭔가를 쓰고 싶어
  • 테스트가 작성되지 않은 기존 프로젝트인 큰 프로젝트로 작업하고 있지만 여전히 코드를 확인하는 방법이 필요합니다.
  • 귀하의 코드에는 간단한 요청 및 응답이 포함됩니다.
  • 테스트를 유지 관리하는 데 많은 시간을 할애하고 싶지 않은 경우
  • 애플리케이션이 기존 데이터베이스의 모든 값에서 작동하는지 테스트하려는 경우

기존 테스트는 다음과 같은 경우에 이상적입니다.

  • 스크립트와 같은 웹 애플리케이션이 아닌 다른 것을 다루고 있습니다.
  • 복잡한 알고리즘 코드를 작성 중입니다.
  • 테스트 작성에 할애할 시간과 예산이 있습니다.
  • 비즈니스에 버그가 없거나 낮은 오류율이 필요함(금융, 대규모 사용자 기반)

기사를 읽어주셔서 감사합니다. 이제 기본으로 할 수 있는 테스트 방법이 있어야 합니다. 시간이 촉박할 때 신뢰할 수 있는 방법입니다.

관련된:
  • 성능 및 효율성: HTTP/3 작업
  • 암호화 유지, 안전하게 유지: ESNI, DoH 및 DoT 작업