사실 이 이야기는 Effective Java 6장 - 불필요한 객체 생성을 피하라 에 나오는 아주 기초적인 이야기 이다. 그러나 습관이 되지 않아 생각없이 개발하다보면 어느새 이 규칙을 어겨서 어김없이 리팩토링 대상이 되곤 하는데다가, 막상 개발하면서 쓰려다 보면 사용 방법이 잘 생각나지 않아 메모할 겸 짧은 글을 작성 해본다.
(요즘 좀 정신이 나가 있어서 공부는 꾸준히 하기는 한다만, 공부하는 양은 좀 줄었고, 진도는 좀처럼 나가지가 않는다. 에라이)

일반적으로 Java에서 흔히 사용할 수 있는 정규 표현식의 예를 보자. (Junit5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Utils {
public static boolean hasOnlyNumbers(String s) {
return s.matches("[\\d]*");
}
}

@DisplayName("Utils 테스트")
class UtilsTest {
@Test
@DisplayName("숫자 외의 문자가 있을 경우 false를 return 해야 한다")
void should_return_false_when_has_other_characters() {
String numbersWithCharacter = "1a2s3d4f5g6";
boolean expected = false;

boolean result = Utils.hasOnlyNumbers(numbersWithCharacter);

assertEquals(expected, result);
}
@Test
@DisplayName("숫자외의 문자가 없을 경우 true를 return 해야 한다")
void should_return_true_when_has_only_numbers() {
String numbersWithCharacter = "123456789";
boolean expected = true;

boolean result = Utils.hasOnlyNumbers(numbersWithCharacter);

assertEquals(expected, result);
}
}

오케이, 원하는 바를 손쉽게 얻었다. 그러나 지금 얻은 결과는 성능상 좋지 못하다. 사실 일반적인 상황에서 String의 matches 메소드를 사용하는게 엄청나게 성능에 문제를 주는 경우는 별로 없으나, 습관이 중요한데다가, 실제로 이런 코드들이 쌓여 문제를 발생하는 경우도 있으니 좋은 습관을 들이는 게 좋다.
이펙티브 자바는 String.matches를 사용하는 것보다 더 고급지고어렵고 성능이 좋은 방법을 알고 있다.

Pattern 클래스를 사용해 보자

바로 Pattern클래스를 사용해 같은 일을 더 성능 좋게 사용할 수 있다.
사실 위 예제의 코드는 메소드가 실행 될때마다 내부적으로 Pattern 인스턴스를 만들고 사용 후 버리는 작업을 수행하게 되는데, 이 Pattern을 final static 객체로 미리 선언해두고 사용하게 되면 메소드가 호출될 때마다 새로 생성하고 버리는 작업이 없어지게 된다.
아래 코드를 보자

1
2
3
4
5
6
public class Utils {
private static final Pattern ONLY_NUMBERS_PATTERN = Pattern.compile("[\\d]*");
public static boolean hasOnlyNumbers(String s) {
return ONLY_NUMBERS_PATTERN.matcher(s).matches();
}
}

테스트를 잘 통과하는 것을 볼 수 있다. 자, 그럼 얼마만큼의 성능 차이가 날까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@DisplayName("Utils 테스트")
class UtilsTest {

final int times = 10000000;

@Test
@DisplayName("리팩토링 후")
void test_refactor() {
final String numbersWithCharacter = "1a2s3d4f5g6";
for(int i=0; i < times; i++){
Utils.hasOnlyNumbersRefactor(numbersWithCharacter);
}

}
@Test
@DisplayName("리팩토링 전")
void test_before_refactor() {
final String numbersWithCharacter = "1a2s3d4f5g6";
for(int i=0; i < times; i++){
Utils.hasOnlyNumbersOriginal(numbersWithCharacter);
}
}
}

컴퓨팅 성능마다, JVM 셋팅마다 다르겠지만, 메소드 수행을 1000만회 진행 했을때 꽤나 차이가 나는 것을 눈으로 볼 수 있었다.
(2.35초 -> 0.83초)

이펙티브 자바의 아이템6 에는 이 외에도 다른 팁들도 있으나 일단 오늘은 정규 표현식 부분만 정리했다.

정규 표현식을 사용할때는 기억하자.

  1. static final Pattern 으로 불변객체를 생성해서 사용하자
  2. Pattern p = Pattern.compile(“정규식”);
  3. p.matcher(string).matches();

Comment and share

Spring boot 에서는 application.yml (또는 application.properties) 파일의 내용을 빈의 변수에 바인딩하도록 도와주는 어노테이션이 있다.
@Value("${property.value}") 뭐 이런식으로 사용하는데 변수가 늘어남에 따라 어노테이션이 많아져서 가독성을 해치는 경우가 있고, 일일히 지정해주기가 귀찮을 경우 쓸 수 있는 방법인 @ConfigurationProperties 어노테이션에 대해서 간단히 적어 보려 한다.

사용방법

@ConfigurationProperties 는 클래스에 선언하는 어노테이션이다.
간단히 아래 예시와 같이 사용한다.

먼저 gradle(또는 maven 빌드 툴)에 아래 dependency를 설정한다.

1
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

yml에 필요한 값들을 설정하고

1
2
3
4
user:
conf:
property1: "value1"
property2: "value2"

값들을 사용하고자 하는 클래스에 @ConfigurationProperties 어노테이션을 추가한다. 여기서 prefix도 설정할 수 있다. (user.conf는 예시를 위해 작성해 놓은 것이고, prefix는 다른 값으로도 사용할 수 있다.)
주의할 점은 @Value 어노테이션을 활용해 값을 바인딩 하는것과 다르게, 반드시 setter 메소드를 필요로 한다.
@ConfigurationProperties는 setter 메소드를 사용해 설정값을 바인딩 하기 때문이다. 여기서는 클래스에 @Setter 롬복 어노테이션을 사용해 한번에 정의했다.

1
2
3
4
5
6
@Setter
@ConfigurationProperties(prefix = "user.conf")
public class SomeClass {
private String property1;
private String property2;
}

Comment and share

IntelliJ에서 .http로 테스트 하기

나는 java 개발을 할 때 IntelliJ를 사용한다. 지금 다니고 있는 회사에 입사하기 전까지는 eclipse 만 사용해봤었고,
IntelliJ는 사실 존재 자체도 모르고 있었다. 그러나 지금은 intelliJ 없이는 java개발을 할 수 없는 몸이 되어버렷!! 다..
IntelliJ는 여러 편의 기능을 번들로 제공하고 있는데 그 중 하나가 지금 이야기 하고자 하는 http client 툴이다.

사실 IntelliJ의 Http 툴의 사용방법을 설명한 글을 이전에 블로그에 한번 작성한 적이 있었으나 안타깝게도 블로그와 함께 날려먹었고, 이 툴에 관해 잘 설명해 놓은 글은 굳이 내가 정리하지 않더라도 많이 있다. 그럼에도 불구하고 또 이 툴에 대해서 정리하는 이유는 내가 잘 보려고

회사에서 개발 일을 하면서 많은 지식을 익히고 이 지식들로 코딩을 하지만, 돌아서고 나면 잊어버린다. 물론 코드에 작업 내용이 남아있어 다시 열어본다면 이해하고 동일한 코드를 만들 수 있지만, 사실 이 코드의 소유는 내가 아니라 회사다. 퇴사를 한다면?? 내가 동일한 코드를 만들 수 있을까? 그래서 내 것으로 만들기 위해 하나씩 정리 하려고 한다.

.http 소개

이 툴의 명칭은 HTTP Client 쯤 되는것 같다. (HTTP client in IntelliJ IDEA code editor)
안타깝게도 ultimate 딱지가 붙어 있는걸 봐서는 intelliJ 커뮤니티 에디션에서는 사용하지 못하는 듯 하다. 레퍼런스 페이지는 #여기

간단히 말해 http 호출을 해볼 수 있는 툴이다.

.http 를 쓰는 이유

익히지 않고 직관적으로 쓰기에는 물론 postman이 더 낫다. swagger도 많이 쓴다. 설치하기 귀찮으면 curl을 사용하기도 한다.
내가 .http 를 쓰는 가장 큰 이유는

  1. Git을 통한 형상 관리
  2. Git으로 팀원과 쉽게 공유가 가능
  3. 테스트 툴이므로 초록 막대를 보며 마음의 안정을 얻을 수 있다. (또는 빨간 막대를 볼수도..)

뭐 이 외에도 쓰다보면 여러 장점들을 느낄 수 있겠지만, 일단 이 세가지만으로도 충분히 좋다.

기본 사용법

  1. intelliJ의 프로젝트 explorer에서 적당한 곳에 마우스 우클릭 > NEW > HTTP Request

  1. 적당한 파일명을 입력 후 엔터를 치면 .http 로 끝나는 파일이 생성 되고 에디터로 그 파일을 연다.

이 때 주의할 점 파일명을 status로는 하지 말자. 왠지 모르겠지만, status.http 파일에서는 http 테스트가 활성화가 되지 않는다.

  1. 이제부터 테스트를 만들면 된다. 아직 우리는 사용법을 잘 모르므로 Examples를 눌러보자. 그럼 친절하게 아래와 같은 내용의 파일이 열린다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
### GET request with a header
GET https://httpbin.org/ip
Accept: application/json

### GET request with parameter
GET https://httpbin.org/get?show_env=1
Accept: application/json

### GET request with environment variables
GET {{host}}/get?show_env={{show_env}}
Accept: application/json

### GET request with disabled redirects
# @no-redirect
GET http://httpbin.org/status/301

### GET request with dynamic variables
GET http://httpbin.org/anything?id={{$uuid}}&ts={{$timestamp}}

###

  1. 각 테스트는 ###로 구분한다.
  2. 제일 윗줄에 메소드 URL 형식으로 호출하고자 하는 URL을 정의한다.
  3. 헤더는 URL 바로 아랫줄에 키: 밸류 형태로, 붙이고자 하는 헤더가 여러개일 경우 계속 줄을 이어서 써준다.
  4. 예제에는 안 나왔지만 requestBody를 같이 보내고자 할 경우, 헤더 밑으로 두줄 바꾸고 그 밑에 입력해주면 된다.

기본 사용방법은 이정도이고, {{$uuid}}{{host}} 같은게 보이는 것 같지만 기분탓이라 생각하고 일단 넘어가자.
대략 사용방법은 봤으니, 우리가 테스트 하고자 하는걸 입력 해보자.

1
GET https://google.com

난 이렇게 입력 했다. 좀 편한 방법을 쓰고자 한다면 우측 상단 메뉴에 Add Request 를 마우스로 누르면 snippet 이 나오니 참고할 수 있다.

그리고 초록색 재생 화살표를 누르면 실행되고, 화면 아래에 결과가 나온다.

► 눌러눌러!!

구글 호출 했을때 나오는 결과를 볼 수가 있다.

환경변수 설정해서 사용하기

자세한 설명 링크

위에 예시로 제시한 테스트 중에 {{host}} 같이 이중 블릿으로 감싼 아이들이 보인다. .http 테스트에서 환경 변수들을 사용할 수 있는데, 여러 환경에 따른 값들을 미리 설정해놓고 필요할때마다 선택해서 실행 할 수 있도록 편리한 기능을 제공해 준다.

.http client는 두 종류의 변수를 사용할 수 있는데, Dynamic 변수와 직접 정의한 환경 변수이다. 다이나믹 변수는 $로 시작하고, 아래 세 종류가 있다.

  • $uuid: 우리가 알고있는 UUID
  • $timestamp: Unix Timestamp형태의 현재 시각
  • $randomInt: 0에서 1000 사이의 랜덤 숫자

그리고 우리가 알아볼 환경변수. 환경변수는 프로젝트 내에 rest-client.env.json (또는 http-client.env.json도 가능) 라는 이름의 json 파일을 생성해서 사용한다. 레퍼런스 문서에서 제공하는 예제를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"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"
}
}

이 예제에서는 development, production 환경을 사용할 수 있고, 그 내부에 정의 돼있는 host, id-value 와 같은 값들을 미리 정의 해놨다고 이해하면 된다. 테스트 할때 위 예제에서 봤듯이 http://{{host}}/index.html 와 같은 형식으로 사용할 수 있다. 물론 이 값들은 url 뿐만 아니라 헤더나 request body 영역에서도 치환해서 사용할 수 있다.

username과 password는 비어두었는데, 개인정보 등 공유되어서는 안될 민감한 정보들을 별도로 정의하기 위해서 비워두었다. rest-client.private.env.json (또는 http-client.private.env.json) 라는 파일 명으로 별도로 정의하여 사용할 수 있는데, 이는 형상관리에서 기본으로 제외되도록 설정돼있다고 한다.

1
2
3
4
5
6
7
8
9
10
11
{
"development": {
"username": "dev-user",
"password": "dev-password"
},

"production": {
"username": "user",
"password": "password"
}
}

rest-client.env.json 와 같이 이런 형태로 정의 해서 사용할 수 있다.

테스트 결과값 받아서 사용하기

이렇게 환경변수를 설정하는 방법은 rest-client.env.json 파일을 통해 미리 정의하는 방법 말고 또 한가지가 존재한다. 바로 http 호출 후 response handler를 정의해서 변수로 설정하는 방법이다. 아래 예제를 살펴보자. (참고로 response handler는 javascript 문법을 사용한다. 또한 자동완성까지 지원한다. ㄴㅇㄱ)

1
2
3
4
5
6
7
8
9
POST http://localhost/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=read_profile

> {% //response handler
client.global.set("access_token", response.body.access_token);
client.log(client.global.get("access_token"));
%}

POST로 http://localhost/oauth/token를 호출하고 받는 response에서 response.body 의 access_token 값을 받아 client.global.set()이라는 함수로 변수 설정이 가능한데, 이 스크립트의 경우 “access_token”이라는 변수에 response.body 객체의 access_token 값을 할당 했다. (response.body 가 json형태일 경우다)

response 객체는 body 말고도 다른 속성들도 제공 해준다. 에디터에서 response.을 찍어서 우리의 친절한 intelliJ 자동완성의 도움을 받아 살펴보자.

body 외에 contentType, headers, status 값들을 제공한다.

이를 잘 활용하면 ouath 토큰을 발급받아서 환경변수에 넣는 스크립트를 만들고, 그 이후에 실행하는 스크립트에서 발급받은 토큰을 포함하여 request 테스트를 하는 등의 케이스에서 활용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
### Get Token
POST http://localhost/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=read_profile

> {% //response handler
client.global.set("access_token", response.body.access_token);
client.log(client.global.get("access_token"));
%}

###

GET http://localhost/data
Authorization: bearer {{access_token}}

response handler는 이렇게 테스트 스크립트 내에 inline으로 작성하지만 별도의 js 파일로 만들어서 include 하는 방법도 존재한다.

1
2
3
GET host/api/test

> scripts/my-script.js

초록 막대. 초록 막대를 보자!!

거의 다 와 간다. 글 머리에 초록 막대를 볼 수 있다고 적어 놓았는데, 이제 초록 막대를 보는 방법, 즉 테스트 케이스 답게 스크립트로 만드는 방법을 알아보자.

간단하게는 아래와 같은 방법으로 사용할 수 있다.

1
2
3
4
5
6
7
GET https://httpbin.org/status/200

> {%
client.test("Request executed successfully", function() {
client.assert(response.status === 200, "Response status is not 200");
});
%}

client.assert만으로도 잘 작동하기는 하나, 제대로 된 테스트 케이스는 테스트 정의가 잘 돼 있어야 하므로 client.test로 테스트 케이스가 작동하는 상황에 대해서 설명한 후 두번째 파라미터 함수로 실제 수행되는 테스트 코드를 넣어주면 된다.

client.assert는 첫번째 파라미터에 테스트 하고자 하는 수식, 두번째 파라미터로 테스트가 실패할 경우 표시될 메세지를 넣어준다.

이제 ►를 눌러 실행해보자.

이제 마음의 평안을 얻을 수 있다. response.status 뿐만 아니라 body에서 어떤 결과를 받아왔는지 등으로도 테스트 케이스를 만들 수 있으니 잘 활용하면 좋다.

intelliJ에서 http로 테스트하기 끗!!

Comment and share

  • page 1 of 1

shockshot@naver.com

author.bio


author.job