들어가며
본문에서 API 테스트란 http (또는 https) 요청 및 응답을 처리하는 API를 테스트 해보는 것을 의미합니다.
웹 서버를 구축한 뒤, 화면단 없이 http 요청 자체를 검증하는 것을 API 테스트라고 하는데요, 운영상의 동작 확인이나 localhost 서버를 띄운 후 테스트 해보는 등 다양한 목적으로 수행됩니다.
GET http://localhost:8080/users
POST http://localhost:8080/users/signup
Bash
복사
위 응답을 확인해보고 싶을 때 어떻게 하시나요?
메뉴 → Preferences → plugins → HTTP Client, 최근 버전에서는 내장되어있다.
기본적인 사용법은 아래 블로그를 참고해주셔요.
글 작성을 결심했을 때는 해당 기능과 관련된 글이 별로 없다고 생각했는데, @7/10/2022 기준으로 구글에서 인텔리제이 http로 재검색해보니 60개에 달하는 관련글이 나오는데요. -.-;;
그래도 운영환경에서 어떻게 사용하고 있는지, 또 후기를 다루는 글은 없는거 같아 그대로 진행해보겠습니다.
(예시는 실제 운영 코드가 아닌 예시 코드로 작성되었습니다.)
http 파일을 형상 관리하기
여기서 형상 관리는 Version Control, 달리 말해 git(혹은 기타 형상관리 프로그램)에 포함시키는 것을 의미합니다. 제가 근무하는 팀에선 http 디렉토리를 따로 관리합니다.
http 폴더를 통한 .http 파일 관리
http 디렉토리를 모듈별로 비슷한 방식으로 관리함으로써 팀의 개발자는 팀원이 어디에 http를 작성하였는지 쉽게 찾을 수 있고, 내용을 확인하거나 서버를 부팅하여 직접 실행해볼 수 있습니다.
메서드 구분자를 주석처럼 활용하기
http 파일에선 각각의 요청을 ### 구분자를 통해 구분합니다. 구분자 뒤에 해당 api에 대한 설명을 작성할 수 있는데요, 이를 활용해 해당 API가 어떤 방식으로 활용되는지 기입하고, 개발자의 API에 대한 이해를 돕습니다.
http host를 프로필 별로 관리하기
http client에선 {{변수명}} 스니펫을 활용해 변수를 활용할 수 있습니다.
이에 더해 환경 별로 실행환경 변수 (Environment variables) 를 지원하는데요, 이 기능을 활용해 로컬 환경, 개발 환경, 운영 환경 별로 host를 지정해 간단하게 동일한 API 테스트를 진행할 수 있습니다.
먼저 host를 특정한 변수로 변경합니다.
host 부분을 변수로 변경
http-client.env.json 이라는 이름의 파일을 프로젝트 root에 두고 관리하는데요, 다음과 같은 json 형식을 가집니다.
{
"local": {
"review-api": "localhost:8080"
}, // local end
"dev": {
"review-api": "my-dev-domain.com"
} // dev end
} // json end
JavaScript
복사
해당 파일이 인식되고 나면 http를 실행했을 때 다음과 같은 option이 추가됩니다. 옵션을 실행하면, 해당 환경에 배정된 변수의 값으로 인식됩니다. (만약 local에는 있지만 dev에는 없는 변수라면, dev 실행 시 오류 메시지와 함께 실패합니다.)
추가된 환경 별 실행하기
환경을 추가하고 싶다면 해당 파일 json root에 “enviorment name” : {} 형식으로 계속해서 추가하면 됩니다.
예컨데 test 환경과 prod 환경을 추가하려면 http-client.env.json을 다음과 같이 수정하세요.
{
"local": {
"review-api": "localhost:8080"
}, // local end
"dev": {
"review-api": "my-dev-domain.com"
}, // dev end
"test": {
"review-api": "my-beta-domain.com"
}, // test end
"prod": {
"review-api": "my-prod-domain.com"
} // prod end
} // json end
JavaScript
복사
http-client.env.json 파일이 저장되는 거의 즉시, 실행 환경에 추가됩니다.
실행환경에 추가됨
운영환경에서 상태를 바꾸는 (POST, PUT, DELETE…) 요청을 http에 넣는 것이 일견 위험해보이기도 하는데요, 이게 문제가 될 경우 다음과 같이 대응할 수 있겠습니다.
1.
조회성 api 호스트 변수명은 review-api-get 로, 상태 변경 api 호스트 변수명은 review-api-post-put-delete 으로 설정
2.
review-api-post-put-delete 는 prod 환경에 세팅하지 않음 (prod로 실행시 실행 안 됨)
3.
운영환경에서 해당 api를 실행해야할 필요가 있다면, 임시로 prod 환경에 변수를 추가하거나 밑에서 소개할 http-client.private.json 이용
상태를 변경하는 API는 pord에서 제외
민감정보를 변수로 이용할 때
간혹 민감 정보를 파라미터로 활용할 때가 있습니다. (ex. 로그인 api를 테스트할 때 body에 password를 넘김)
이 때 password를 하드코딩하여 사용하거나, 변수로 사용할 수 있을텐데요.
http 파일과 같은 맥락에서 http-client.env.json 파일을 형상관리하고 있기 때문에 하드코딩과 변수 두 방식 모두 remote 저장소에 민감정보가 업로드되는 문제가 생깁니다.
이런 경우를 방지하기 위해 http-client.private.json 을 활용할 수 있습니다.
인텔리제이에서 변수를 설정할 수 있는 두 가지 파일로 http-client.env.json, http-client.private.json 을 제공하고 있는데요.
두 개의 파일에 동일한 변수명이라면 private에 적혀있는 value로 override됩니다. 아래는 공식 홈페이지에 있는 예시입니다.
{
"development": {
"host": "localhost",
"id-value": 12345,
"username": "",
"password": "",
"my-var": "my-dev-value"
},
"production": {
"host": "example.com",
"id-value": 6789,
"username": "",
"password": "",
"my-var": "my-prod-value"
}
}
JavaScript
복사
http-client.env.json
{
"development": {
"username": "dev-user",
"password": "dev-password"
},
"production": {
"username": "user",
"password": "password"
}
}
JavaScript
복사
http-client.private.env.json, username과 password를 오버라이드 하고 있다.
동일한 방식으로 http-client.private.env.json 을 세팅하고, 간단한 컨트롤러를 만들어 토큰 방식으로 로그인 처리를 해보겠습니다.
@RestController
@Slf4j
public class LoginController {
@PostMapping("/login")
public ResponseEntity<TokenDto> login(@RequestBody LoginDto login) {
log.info(login.toString());
if (login.getPassword().equals("password")) {
return ResponseEntity.ok(TokenDto.builder()
.accessToken("very-very-secret")
.build());
}
return ResponseEntity.badRequest().build();
}
@Jacksonized
@Builder
@Getter
@ToString
public static class LoginDto {
private final String username;
private final String password;
}
@Jacksonized
@Builder
@Getter
@ToString
public static class TokenDto {
private final String accessToken;
}
}
Java
복사
login controller
{
"local": {
"username": "wedge",
"password": "password"
},
// ... (이하 생략)
}
JavaScript
복사
http-client.private.env.json
### 로그인
POST {{review-api}}/login
Content-Type: application/json
{
"username" : "{{username}}",
"password": "{{password}}"
}
Bash
복사
login.http
서버를 부팅하고 로그인 http를 실행하면, username과 password가 잘 전달되어 성공한 것을 볼 수 있습니다.
username=wedge, password=password가 잘 전달되어 로그인 성공한 모습
.gitignore에 http-client.private.env.json 추가하기
VCS tool로 git을 사용하고 있다면, 해당 파일을 .gitignore에 추가하여 해당 파일이 실수로 remote 저장소에 업로드 되는 일을 차단해줍니다.
git ignore 추가하기
tpl 파일로 어떤 변수가 필요한지 가이드 해주기
위 방식으로 private 파일을 처리하면, 어떤 개발자는 private 파일이 세팅되어 있지 않아 특정 민감정보 변수가 필요한 http 실행을 실패할 수 있습니다.
이를 예방하기 위해, http-client.private.env.json.tpl (tpl 확장자는 template라는 뜻으로 붙였습니다. 사실 어느 것이든 상관없습니다.)를 만들어 어떤 변수가 필요한지 가이드하는 방식으로 사용합니다.
개발환경 세팅 시 해당 파일을 복사하여 안내하도록 README.md 에 작성합니다.
{
"local": {
"username": "YOUR-ID",
"password": "YOUR-PASSWORD-HERE"
},
// ... (이하 생략
}
JavaScript
복사
http-client.private.env.json.tpl
(토큰 방식) 인증이 필요한 API 테스트하기
인증이 필요할 때, 그때 그때 토큰값이 변경되기 때문에 토큰값을 어디선가 얻어와 일시적으로 header에 Bearer 토큰을 복사 & 붙여넣기 하게되는 일이 잦은데요,
http가 한 두개면 괜찮지만 한 번에 여러 http를 확인해야 하는 상황이라면 상당히 불편해집니다.
세션 방식을 이용할 경우, http client는 자동으로 요청간의 세션값을 저장하고 전달합니다.
그러므로 인증 http 와 인증 요구 http를 순서대로 실행하기만 하면 됩니다.
세션 정보가 캐싱되는 http-client
본문에서는 token을 이용해 로그인 정보를 유지한다고 가정하겠습니다.
위 로그인 예제에 요청을 받는 api를 추가하고, http를 하나 만들겠습니다.
very-very-secret라는 값을 하드코딩하고, Authorization에 Bearer 양식으로 해당 토큰이 들어오면 200을 내려주는 코드입니다.
@PostMapping("/login")
public ResponseEntity<TokenDto> login(@RequestBody LoginDto login) {
log.info(login.toString());
if (login.getPassword().equals("password")) {
return ResponseEntity.ok(TokenDto.builder()
.accessToken("very-very-secret")
.build());
}
return ResponseEntity.badRequest().build();
}
@GetMapping("/me")
public ResponseEntity<Void> isMe(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
assert authorization != null;
boolean isMatch = authorization
.substring("Bearer ".length())
.equals("very-very-secret");
return isMatch ?
ResponseEntity.ok().build() :
ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Java
복사
### 로그인
POST {{review-api}}/login
Content-Type: application/json
{
"username" : "{{username}}",
"password": "{{password}}"
}
### 내 정보 확인
GET {{review-api}}/me
Content-Type: application/json
Authorization: Bearer ???
Bash
복사
만약 별도의 조치가 없으면, 내 정보 확인 api를 테스트 하기 위해서 1. 인증 api 실행, 2. 응답으로 내려온 토큰값 복사 & 붙여넣기 의 굴레를 벗어날 수 없습니다.
토큰 복붙 노가다..
이 과정을 자동화하기 위해 Response Handler를 이용할 수 있습니다.
응답값을 스크립틀릿을 열어 제어할 수 있는 문법으로, javascript ECMA 5.1을 기반으로 합니다. 해당 문법을 활용해 client 와 response 객체를 제어할 수 있는데요, 각 객체에 대한 설명은 다음과 같습니다.
이 기능을 활용해, 로그인 http를 먼저 진행한 후 인증 키 값을 전역 변수 목록에 저장해, 인증 정보가 필요한 http 요청에 활용할 수 있습니다.
구분 | 의미 | API 문서 |
client | 세션에 대한 메타데이터(전역 변수 목록을 포함한)를 저장해놓은 객체 | |
response | HTTP Response 값 (헤더, 바디, 상태 등)을 저장해놓은 객체 |
위 문법을 사용하면, 응답값으로 내려온 토큰을 전역 변수 목록에 저장할 수 있습니다. 추가로 응답값 로깅과 테스트도 진행해보겠습니다.
### 로그인
POST {{review-api}}/login
Content-Type: application/json
{
"username" : "{{user_name}}",
"password": "{{password}}"
}
> {%
client.test("Validate", function() { // Validate라는 이름의 테스트를 수행
client.assert(response.status === 200, "Response status is not 200"); // 응답값이 200이면 통과
client.assert(response.contentType.mimeType === "application/json", "Expected 'application/json'"); // 응답값이 application/json이면 통과
client.assert(response.body.accessToken.trim().length > 0); // 토큰 길이가 0보다 크면 통과
});
client.log(response.body.accessToken); // 토큰 값 로깅, body에는 응답받은 json이 객체 형식으로 저장된다. 즉 { accessToken:"value" }가 응답이라면, 여기선 value라 로깅된다.
client.global.set("access_token",response.body.accessToken) // 토큰 값을 global 변수에 set
%}
JavaScript
복사
위 테스트를 실행해 보겠습니다. Response Handler를 활용하면 Response Handler 탭이, client.test를 활용하면 Tests 탭이 생성됩니다.
response handler 결과가 적혀있는 탭. 로깅이나 실패에 대한 에러 응답이 남는다.
Tests 결과가 남는 테스트 결과 탭.
문법상, 논리상 틀린 부분이 없었으므로 스크립트와 테스트가 통과했습니다. 이 스크립트는 http 응답이 끝난 후 실행되기 때문에, 스크립트가 고장나거나 test가 실패한다고 http 요청/응답 자체에는 영향을 주지 않습니다.
위 스크립트를 거치고 나면 access_token 라는 전역 변수에 토큰 값이 세팅됩니다. 인증 요구 http가 이 변수를 활용할 수 있도록 .http 파일을 수정합니다.
### 내 정보 확인
GET {{review-api}}/me
Content-Type: application/json
Authorization: Bearer {{access_token}}
JavaScript
복사
두 개를 연달아 시도해보면, 잘 성공하는 것을 볼 수 있습니다.
access_token을 복사 붙여놓기 하는 과정을 자동화 하였다
마치며
저희 팀에서는 http-client 기능을 적극적으로 활용하고 있는데요, 주로 두 가지 용도로 활용합니다.
1.
로컬 환경에서 API를 테스트 하는 용도
2.
매우 복잡한 파라미터를 가진 API를 실험해 볼 수 있는 템플릿
이 기능은 서버를 구동하여야 한다는 점에서 통합테스트를 완전히 대체하기엔 불완전하지만, 간단하게 api를 테스트 해보고자 하는 요구에는 부합합니다. 또한 팀 차원에서 api 활용법을 공유할 수 있다는 점에서도 의의가 있습니다.
이 글이 누군가에게 작은 도움이 되길 바라며~~