이 글의 대부분의 내용은 우아한 형제들 Gif-flow 글에서 참고했음을 밝힙니다.

 

우린 Git-flow를 사용하고 있어요 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요. 우아한형제들 배민프론트개발팀에서 안드로이드 앱 개발을 하고 있는 나동호입니다. 오늘은 저희 안드로이드 파트에서 사용하고 있는 Git 브랜치 전략을 소개하려고 합

techblog.woowahan.com


Git flow

 

Git-Flow는 각각 feature가 독립적이면서 연속적으로 개발 될 수 있도록 하는 전략 중에 하나입니다.

 

Git flow를 이보다 잘 설명할 수 있을까

Git-flow에는 5가지 종류의 브랜치가 존재합니다. 항상 유지되는 메인 브랜치들(master, develop)과 일정 기간 동안만 유지되는 보조 브랜치들(feature, release, hotfix)이 있습니다.

  • master : 제품으로 출시될 수 있는 브랜치
  • develop : 다음 출시 버전을 개발하는 브랜치
  • feature : 기능을 개발하는 브랜치
  • release : 이번 출시 버전을 준비하는 브랜치
  • hotfix : 출시 버전에서 발생한 버그를 수정 하는 브랜치

git에서는 git flow 명령어를 제공하기도 합니다. git flow 전략을 그대로 명령어로 만든 형태입니다.

 

git-flow cheatsheet

 

danielkummer.github.io

 

action은 init, start, finish, publish, pull이 있고 아래는 주로 사용하게 되는 feature branch 관리 코드 예시입니다.

# 브랜치 추가
# feature/FeatureName 브랜치가 develop를 base로 생성됩니다.
$ git flow feature start [FeatureName]

# 브랜치 종료
# feature/FeatureName 브랜치가 develop로 merge되고 그 후 삭제됩니다.
$ git flow feature finish [FeatureName]

# 그 외 publish와 pull도 있습니다.

 

git flow를 위한 상황별 git 명령어들

 

하지만, 우리는 정해진 기능만 사용할 수가 없습니다. 기능 구현을 위해 일부 기능이 필요하거나, 개발하다가 코드가 충돌하거나 등등의 다양한 상황이 생기기 때문이죠.

 

몇 가지 상황별로 명령어를 모아 준비해봤습니다.

 

1. develop 브랜치에서 feature 브랜치를 생성

$ git switch develop
$ git pull origin develop
$ git checkout -b feature/user –track develop

생성 후에 소스코드 수정을 해서 변경사항을 commit 해줍니다.

 

 

2. 불필요하게 나눠진 커밋 합치기

 

아래와 같이 3개의 커밋(first commit, second commit, third commit)을 했다.

 

 

3개의 커밋을 1개의 커밋으로 합치고 싶은데 어떻게 해야할까?

이 때 git rebase를 사용하면 되는데, 명령어는 아래와 같습니다.

 

git rebase -i HEAD~3

 

 

그러고 나서, second commit, third commit 앞에 pick이라고 되어 있는 부분을 squash라고 수정한 뒤,

 

 

저장하고 빠져 나오면 아래와 같은 화면이 나옵니다.

 

 

제일 윗 줄에 새로 추가할 커밋 메시지를 작성해줍니다.

주의) 그 아래에 있는 first commit, second commit, third commit을 그대로 놔두면 커밋 메시지에 같이 찍히게 됩니다.

 

 

그리고 git log를 통해 합쳐진 내용을 확인하면 커밋이 잘 합쳐진 것을 확인할 수 있습니다.

git log


이미지 출처: DevNote

 

 

3. develop 변경사항을 feature로 가져와서 push 하기

 

작업을 할 때 브랜치의 수명은 되도록 짧게 가져가는 게 좋지만, feature 브랜치에서 기능을 완료하는데 해야 할 작업들이 많아서 오래 걸리는 경우 들이 있습니다. 그러다 보면 develop에 추가된 기능들이 필요한 경우가 종종 생기게 됩니다. 그럴 때는 feature 브랜치에 develop의 변경사항들을 가져와야 합니다.

# feature/user 브랜치에 develop 브랜치를 merge 합니다.
$ git switch feature/user
$ git merge –no-ff develop

# develop의 변경사항이 merge된 feature/user를 origin에 push 합니다.
$ git push origin feature/user

 

4. 앱 출시

 

발생하는 버그들을 모두 수정했다면 이젠 출시를 준비할 때입니다. release 브랜치를 master 브랜치와 develop 브랜치에 merge하고 마지막으로 master 브랜치에서 버전 태그를 달아줍니다.

  1. release 브랜치를 최신 상태로 갱신합니다.
    (release-1.0.0)]$ git pull upstream release-1.0.0​
  2. release 브랜치를 develop 브랜치에 merge 합니다.
    (release-1.0.0)]$ git checkout develop
    (develop)]$ git pull upstream develop
    (develop)]$ git merge –no-ff release-1.0.0​
  3. develop 브랜치를 upstream에 push 합니다.
    (develop)]$ git push upstream develop​
  4. release 브랜치를 master 브랜치에 merge 합니다.
    (develop)]$ git checkout master
    (master)]$ git pull upstream master
    (master)]$ git merge –no-ff release-1.0.0​
  5. 1.0.0 태그를 추가합니다.
    (master)]$ git tag 1.0.0​
  6. master 브랜치와 1.0.0 태그를 upstream에 push 합니다.
    (master)]$ git push upstream master 1.0.0​

 

EC2 프리티어의 메모리, 1GB는 터무니 없이 작다.

Jenkins 하나만 돌려도 메모리 1GB의 대부분을 사용한다. 적은 메모리로 프로그램을 돌리다보면 인스턴스 자체가 죽어버리는 일이 자주 볼 수 있다.

 

그 중 하나의 해결책으로 하드디스크를 가상메모리로 사용하여 메모리 양을 늘려주는 방법에 대해 소개하겠다.

 

본 글은 아래 2개의 포스팅을 참고하여 작성하였다.

 

 

 

스왑 파일을 사용하여 Amazon EC2 인스턴스의 스왑 공간으로 메모리 할당

1.    dd 명령을 사용하여 루트 파일 시스템에 스왑 파일을 생성합니다. 명령에서 bs는 블록 크기이고 count는 블록 수입니다. 스왑 파일의 크기는 dd 명령의 블록 크기 옵션에 블록 수 옵션을 곱

aws.amazon.com

 

 

OKKY | AWS EC2 프리티어 쓰시는분들 참고하세요!

일기장에 기록해둔건데 프리티어 쓰시는 분들에겐 좋은 정보일것 같아 남깁니다. 제 일기장에서 긁어온거라 폼이 많이 깨지긴 했는데 감안하고 봐주세요!   AWS 프리티어 EC2를 사용 중 겪은 문

okky.kr


하드디스크를 가상 메모리로 사용하기 

 

AWS에서는 메모리의 양에 따라 스왑 메모리의 크기를 아래와 같이 권장하고 있다.

물리적 RAM의 양 권장 스왑 공간
RAM 2GB 이하 RAM 용량의 2배(최소 32MB)
RAM 2GB 초과, 32GB 미만 4GB + (RAM – 2GB)
RAM 32GB 이상 RAM 용량의 1배

참고: 스왑 공간은 절대로 32GB 미만이 되지 않아야 한다.

 

스왑 파일 생성

$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16

1. dd 명령을 사용하여 루트 파일 시스템에 스왑 파일을 생성한다.

명령에서 bs는 블록 크기이고 count는 블록 수이다.

지정한 블록 크기는 인스턴스에서 사용 가능한 메모리보다 작아야 한다.

그렇지 않으면 memory exhausted 오류가 발생한다.

 

프리티어의 메모리는 1GB이니, 권장사항대로라면 2GB를 증설시켜야 한다.

이 예제 dd 명령에서 스왑 파일은 2GB(128MB x 16 = 2,048MB)이다.

4GB를 늘리고 싶다면 4GB(128MB * 32)에 해당되도록 블록 count 값을 32로 설정해주면 된다.

 

$ sudo chmod 600 /swapfile

2. 스왑 파일에 대한 읽기 및 쓰기 권한을 업데이트

 

$ sudo mkswap /swapfile

3. Linux 스왑 영역을 설정

 

$ sudo swapon /swapfile

4. 스왑 공간에 스왑 파일을 추가하여 스왑 파일을 즉시 사용할 수 있도록 만든다

 

$ sudo swapon -s

5. 절차가 성공했는지 확인

 

$ sudo vi /etc/fstab

6. /etc/fstab 파일을 편집하여 부팅 시 스왑 파일을 활성화

 

/swapfile swap swap defaults 0 0

편집기에서 파일을 연 후 파일 끝에 다음 줄을 새로 추가하고 파일을 저장한 다음 종료한다.

 

7. free를 다시 입력하여 메모리를 확인해본다. 아래처럼 Swap 영역이 2GB로 되어있으면 성공.

$ free
              total        used        free      shared  buff/cache   available
Mem:         989140      151748      198108         444      639284      695068
Swap:       2097148           0     2097148

 

'AWS' 카테고리의 다른 글

서버리스  (0) 2023.09.05
클라우드 컴퓨팅, IaaS, PaaS, SaaS  (0) 2021.07.28

Java 8 버전은 지원 종료가 머지 않았으므로 Java 11 버전 기준으로 설치하였다.

각설하고 바로 설명하겠다.


1. AWS EC2 인스턴스를 만든다. (글 링크)

2. EC2에 접속한다. (글 링크)

    - Windows 라면 Putty를 사용해 접속하면 된다. (링크)

 

3. 아래 script로 Java를 설치한다.

sudo yum update -y

# 자바 11 설치
# 설치 가능한 Java 버전 확인
# yum list java*jdk-devel

## Example)
## Available Packages
## java-1.7.0-openjdk-devel.x86_64  1:1.7.0.261-2.6.22.2.amzn2.0.2 amzn2-core
## java-1.8.0-openjdk-devel.x86_64  1:1.8.0.312.b07-1.amzn2.0.2    amzn2-core

# Amazon Linux에 Java 11 설치
sudo amazon-linux-extras install java-openjdk11

# yum 패키지의 Java 8 설치
sudo yum install -y java-1.8.0-openjdk
# 자바 버전 11로 설정
# 아래 처럼 나오면 Java 11인 1번을 선택
sudo alternatives --config java

# $ sudo alternatives --config java

# There are 2 programs which provide 'java'.

#   Selection    Command
# -----------------------------------------------
#  + 1           java-11-openjdk.x86_64 (/usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.amzn2.0.3.x86_64/bin/java)
# *  2           java-1.8.0-openjdk.x86_64 (/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-1.amzn2.0.2.x86_64/jre/bin/java)

# Enter to keep the current selection[+], or type selection number: 1

+ Java Version 확인

# 자바 버전 확인
java -version

# openjdk version "11.0.13" 2021-10-19 LTS
# OpenJDK Runtime Environment 18.9 (build 11.0.13+8-LTS)
# OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8-LTS, mixed mode, sharing)

 

4. Jenkins, Git, Docker 설치

# jenkins 패키지 추가
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key
# install docker, git
sudo yum install -y git docker

# 자바 11버전에서 젠킨스 설치
sudo yum install -y epel-release # 혹시 안되면  sudo amazon-linux-extras install epel 로 먼저 설치
sudo yum install -y java-11-openjdk-devel
sudo yum install -y jenkins

# 젠킨스 실행
service jenkins start

 

5. Jenkins로 접속

   - 해당 AWS EC2의 "public IP 주소:8080" 으로 접속하면 다음처럼 화면이 나온다.

   - 아애 접속이 안된다면 해당 AWS EC2의 보안그룹의 inbound rule를 확인해보길 바란다. (링크)

 

그 후 아래 명령어로 비밀번호를 확인하고 입력하여 Continue를 누른다.

# 젠킨스 비밀번호 확인
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

 

다음과 같은 화면이 나오면 lock을 풀고 성공적으로 접속한 것이다.

 

6. Plugins 설치

제시된 플러그인 외에 특정한 플러그인이 필요하지 않아 왼쪽의 "Install suggested plugins"으로 설치하였다

 

7. Admin 계정 만들기

 

8. You're ready to use Jenkins

  •  계정을 만들어주고 젠킨스 주소 확인을 해주면 젠킨스를 사용할 준비를 마쳤다.

https://youtu.be/mPabXfWKWYE


'개발 관심사' 카테고리의 다른 글

소프트웨어 신뢰성 선언문  (0) 2024.03.04
MSA 관련 자료집  (0) 2022.04.13
웹 서비스 서버 성능 지표들  (0) 2022.02.21
Gradle과 Maven  (0) 2022.02.16

우리가 Java Spring으로 API endpoint의 Controller를 만들 때, 값을 받아올 수 있도록 Spring은 여러 방법을 지원하고 있다.
주로 함수의 파라미터 형태로 값을 받아오는 형태이다. 구체적으로 그 방식에는 아래와 같은 것들이 있다.(Annotation이 포함된 코드입니다.)

  • @PathVariable
  • @RequestParam
  • @RequestBody
  • @RequestHeader

순서대로 하나씩 알아보자

  • 이 글에서는 용법을 주로 다루고 있습니다. 원리를 다루지 않고 있음을 알려드립니다.
  • Spring의 경우에는 내부의 Servlet의 메소드들의 동작을 기반으로 아래와 같은 Annotation이 지원됩니다.

Spring 기준입니다!


@PathVariable

  • 서버에서는 파라미터로 칭한다.
  • 요청 Url 형식 : 정해진 위치에 Variable의 값을 적은 형태이다.
http://localhost:8080/foos/abc

method의 parameter로 받아오는 코드

  • 예시와 같이 요청이 오면 id의 값은 abc가 되겠다.
@GetMapping("/foos/{id}")
@ResponseBody
public String getFooById(@PathVariable String id) {
    return "ID: " + id;
}

[Tip] PathVariable을 Optional로 활용하기

PathVariable을 사용하는 형태와 사용하지 않은 형태를 한 메소드로 둘 다 지원하는 형태다. (다형성)

http://localhost:8080/myfoos/optional/abc
----
ID: abc
http://localhost:8080/myfoos/optional
----
ID: null


method의 parameter로 받아오는 코드

required = false를 기재해주면 id 부분이 비어 있는 url 요청도 처리 가능하다.
단, 비어있다면 id 변수는 null로 처리되니 각별히 주의가 필요하다.

@GetMapping({"/myfoos/optional", "/myfoos/optional/{id}"})
@ResponseBody
public String getFooByOptionalId(@PathVariable(required = false) String id){
    return "ID: " + id;
}

@RequestParam

  • 서버에서는 Query Parameter로 칭한다.
  • 요청 Url 형식 : ? 로 쿼리임을 명시적으로 적어주고 그 뒤에 바로 변수명=값 형태로 url에 적어준다.
  • 예시로 다음의 쿼리는 id를 "abc" 가 PathVariable이 되겠다.
    http://localhost:8080/foos?id=abc


method의 parameter로 받아오는 코드
쿼리에 날라오는 id와 변수의 이름을 똑같이 만들어주면 String 타입 파라미터 id에 쿼리로 보낸 값이 할당 된다. (id = "abc")

@GetMapping("/foos")
public String getFooByIdUsingQueryParam(@RequestParam String id) {
    return id;
}

Optional, defaultValue 설정, 여러 개의 값 받기

  • required 옵션으로 Optional Parameter로 사용하기
    @RequestParam(value = "searchKeyWord1", required = false) final String searchKeyWord1
  • 초기값 (defaultValue) 설정하기
    @RequestParam(value = "writer", defaultValue = "MangKyu")
  • 여러 개의 값 받기
    • 여러 값을 받을 땐 값들 사이에 &&를 사용한다.
http://localhost:8080/foos?id=abc&&name=Lim

@RequestBody

@RequestBody는 클라이언트가 전송하는 Json(application/json) 형태의 HTTP Body 내용을 Java Object로 변환시켜주는 역할을 한다.
그렇기 때문에 요청시 Body 없이 @RequestBody를  사용하는 메소드를 활용하려고 한다면 에러가 발생하게 된다.
주로 POST, PUT method를 받을 때 사용한다.

  • 한 줄로 읽는 내부 동작 탐구
  • @RequestBody로 받는 데이터는 Spring에서 관리하는 MessageConverter들 중 하나인 MappingJackson2HttpMessageConverte를 통해 Java 객체로 변환된다. Spring은 메세지를 변환되는 과정에서 객체의 기본 생성자를 통해 객체를 생성하고, Reflection을 사용해 값을 할당한다.
@PostMapping("/foo") 
public ResponseEntity<Board> requestBody(@RequestBody final Board board) {
  return ResponseEntity.ok(boardService.add(board));
}
  • 여기서 Board는 requestDto에 해당한다.
@Getter
@Setter 
public class Board { 
    private int index; // 게시물의 번호를 저장하는 변수 
    private String writer; // 작성자의 이름을 저장하는 변수 
    private String contents; // 작성 내용을 저장하는 변수 }
}

  • 이 때 형식에 맞는 요청은 아래와 같다. 데이터 타입에는 XML, JSON, Multi From 등이 사용될 수 있다. 제일 많이 사용하는 타입인 Json으로 예시를 들겠다.
    • Url: http://localhost:8080/foo
    • Content-Type: application/json
    • Body:
      {
        index : 1
        writer: "Lim"
        contents : "어쩌고저쩌고"
      } 

@RequestHeader

  • RequestBody는 Request의 Body 부분에 적어주는 것이라면 Header 부분에 적어주는 것이다.
  • 요청 먼저 보자
Content-Type: application/json accept-language: kor MyPin: limser


method의 parameter로 받는 코드
위의 요청을 받는 다면 language는 kor이 되겠다.

@GetMapping("/greeting")
public ResponseEntity<String> greeting(@RequestHeader("accept-language") String language) {
    // code that uses the language variable
    return new ResponseEntity<String>(greeting, HttpStatus.OK);
}

@RequestHeader를 받아오는 여러가지 타입


@RequestHeader로 받는 방식 몇 가지를 더 소개하고자 한다. 편한 방법을 사용하면 된다.

  1. Annotation 파라미터로 header 이름 적어주기

@RequestHeader("accept-language") String language

  1. Map 사용
  • MultiValueMap을 사용하기도 한다.
    @RequestHeader Map<String, String> headers
  1. HttpHeaders Object로 받기

@RequestHeader HttpHeaders headers



Controller에 값을 받아오고 싶은 당신도 API의 동작 설계를 고려해 용도에 맞게 사용하기를 바란다.


Reference

아이템 9: Try with resources를 사용하라

  • Java 8 이상 환경에서 실행 가능한 예제입니다.
  • 다루는 내용:
    • Try with resources 용법과 그 장점
    • 두 개 이상 자원을 관리하는 Try with resources를 사용할 때, 두 개 이상의 예외를 표기해주는 Throwable의 getSuppressed

Try finally: 개선이 필요한 형태

  • Try finally는 주로 명시적으로 연결을 close 해줘야 하는 경우 사용한다.
  • 예를 들면, InputStream이나 OutputStream처럼 닫히지 않고 연결이 유지되고 있다면 컴퓨팅 자원이 계속 소모되는 경우가 그렇다.
  • 심지어 아래의 예제처럼 두 개 이상의 자원이 사용되는 경우 try-finally 구문을 중첩(Nested)하여 사용하게 된다.
  • Try-finally는 개발자가 연결의 close를 선언해줘야할 뿐만 아니라 중첩된 경우 가독성도 상당히 떨어진다.
public class Copy {
    private static final int BUFFER_SIZE = 8 * 1024;

    static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        try {
            OutputStream out = new FileOutputStream(dst);
            try {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                while ((n = in.read(buf)) >= 0)
                    out.write(buf, 0, n);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }

Try with resources

  • try 절에 try (resources) 형태로 AutoCloseable을 구현한 클래스 인스턴스를 선언하게 되면 변수가 미치는 단락이 종료되는 시점에 AutoCloseable 인터페이스의 close() 메서드를 자동으로 호출한다.
  • 그렇기 때문에 이전처럼 finally 구문에서 별도로 자원에 대한 해제를 하지 않아도 된다.

위의 예시코드를 try-with-resources 구문으로 바꿔보았다. 위의 코드보다는 한층 깔끔해진다.


public class Copy {
    private static final int BUFFER_SIZE = 8 * 1024;

    static void copy(String src, String dst) throws IOException {
        try (InputStream in = new FileInputStream(src); // try-with-resources를 사용하는 부분!
             OutputStream out = new FileOutputStream(dst)) {
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
    }

장점

try-with-resources의 장점

  1. 자원 관리가 수월해진다.
    • AutoCloseable의 close() 메서드를 자동으로 호출해 별도로 자원 해제를 하지 않아도 된다. (finally를 생략 가능하다)
  2. 2개 이상의 자원을 사용할 시에 가독성이 향상된다.

 

AutoCloseable

  • try-with-resources 구문이 추가 되었고, AutoCloseable 인터페이스가 추가되었다. 이 인터페이스가 나오고부터 Java의 close를 지원하는 클래스에서 AutoCloseable을 구현하도록 변경되었다.
  • AutoCloseable 인터페이스를 implement하게 되면 Exception을 던지는 close() 메서드를 필수적으로 구현해야 한다.
package java.lang;

public interface AutoCloseable {
    void close() throws Exception;
}
  • AutoCloseable을 구현한 예제이다.
package EffectiveJava.item9;


public class CustomAutoCloseable implements AutoCloseable {

    public void doSomething(){
        System.out.println("Do something ...");
    }

    @Override
    public void close() throws Exception {
        System.out.println("CustomResource Closed!");
    }

    public static void main(String[] args) {
        try (CustomAutoCloseable customResource = new CustomAutoCloseable()){
            customResource.doSomething();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

// try-with-resources 구문이 끝나면서 close 메소드가 실행된다.
// Do something ...
// CustomResource Closed!

Exception의 getSuppressed 사용하기

  • Try-catch-with-resources 구문에서 2가지 이상의 자원을 try(/* 자원 선언부 */){} 자원 선언부 자리에서 사용하여 서로 다른 2가지 이상의 에러를 일으킬 가능성이 있을 경우에 사용한다.
  • 중심이 되는 Exception 안에 Suppressed 된 다른 종류의 예외를 담고 있는 형태로 이루어져 있다.

Suppressed Exception

  • 두 예외가 동시에 발생할 수는 없기 때문에 close()에서 발생되는 예외는 억제된(Suppressed) 예외로 처리되어 실제 발생한 예외(try-catch문에서 발생한 예외)에 저장된다.
  • Throwable에는 다음과 같이 억제된 예외와 관련된 메서드가 정의되어 있다.
void addSuppressed(Throwwable exception) 
Throwable[] getSuppressed()
     addSuppressed() // 억제된 예외를 추가
     getSuppressed() // 억제된 예외(배열)를 반환
  • 다음으로 Suppressed된 raiseCustomMultiException을 일으키는 예제이다.

/**
 * 숨겨진 에러를 만들 수 있는 suppressed를 사용하여 CustomMultipleException을 만들어보자
 * 두 가지 이상 자원에 try-catch-with-resources를 사용할 때 숨겨진 에러를 찾을 수 있다.
 */
public class ThrowableGetSuppressedExample {

    public static void main(String[] args) {
        try {
            raiseCustomMultiException();
        } catch (Exception e) {
            e.printStackTrace();
            Throwable[] suppExe = e.getSuppressed(); // getSuppressed로 숨겨진 예외를 가져온다.

            // print element of suppExe
            for (int i = 0; i < suppExe.length; i++) {

                System.out.println("Suppressed Exceptions:");
                System.out.println(suppExe[i]);
            }
        }
    }


    /**
     * @throws CustomMultiException
     * IOException 속의 숨겨진(suppressed) NumberFormatException이 존재하는 형태
     */
    public static void raiseCustomMultiException() throws Exception {

        // creating a suppressed Exception
        Exception suppressed = new NumberFormatException();

        // creating a IOException object
        final IOException ioe = new IOException();

        // adding suppressed Exception
        ioe.addSuppressed(suppressed); // 숨겨진 형태로 예외 추가

        // throwing IOException
        throw ioe;
    }
}
  • 실행된 결과
java.io.IOException
    at EffectiveJava.item9.try_catch_with_resources.raiseCustomMultiException(try_catch_with_resources.java:33)
    at EffectiveJava.item9.try_catch_with_resources.main(try_catch_with_resources.java:13)
    Suppressed: java.lang.NumberFormatException
        at EffectiveJava.item9.try_catch_with_resources.raiseCustomMultiException(try_catch_with_resources.java:30)
        ... 1 more
Suppressed Exceptions:
java.lang.NumberFormatException

Process finished with exit code 0

Reference

Gradle 빌드 환경에서 Junit 5 Test

최근에 Next-Step 자바지기님의 TDD, clean code 과정을 해나가면서 Spring 없이 Gradle 환경에서 Junit 5 테스트를 수행 해야 하는 일이 잦아졌다.

 

Gradle을 빌드하다보면 Junit 5을 dependencies에 추가했음에도 Test 메소드가 Gradle의 :test task (junit tests)를 타고 수행되지 못해 아래와 같은 에러가 뜨곤 한다.

 

에러의 이유는 다양하지만 그 중 junit-jupiter-engine이 테스트 코드를 인식하지 못해 에러의 원인이 되기도 한다.

Process 'Gradle Test Executor 1' finished with non-zero exit value 1

 


에러를 예방한다는 의미로, 제대로 Gradle 빌드 환경에서 Junit 5 test를 수행하는 방법을 알아보자

  • 이 글에서 다루고 있는 내용: Junit 5을 사용하여 테스트를 수행할 수 있도록 Gradle을 셋팅하는 법
  • 이 글에서 다루지 않는 내용: Gradle의 모든 셋팅법, Gradle의 명령어

 

1. 먼저 Gradle부터 준비하자

version 4.6 or higher of the build tool is installed since that is the earliest version that works with JUnit 5.

 

  • Junit 5을 빌드하려면 4.6 버전 이상의 Gradle이 필요하다. 설치는 이 링크
  • Gradle 버전은 프로젝트 위치에서 연 bash나 shell에서 gradle -v 을 통해 확인할 수 있다.
$> gradle -v
------------------------------------------------------------
Gradle 7.0.6
------------------------------------------------------------

 

2. build.gradle 파일에 Gradle을 구성

build.gradle 파일에 Test task를 추가하면 Gradle에서 Test Task를 수행할 수 있다.
우리는 Junit 플랫폼을 사용할 것임으로 아래와 같은 코드를 build.gradle 파일에 추가해준다.

test {
    useJUnitPlatform()
}

 

3. 이제 JUnit Dependency를 추가해야 한다. 단 Junit 5는 그 전과 다르다.

build.gradle 파일에 JUnit Dependency를 추가해준다.

  • 여기서 JUnit 5이 달라진 점은 JUnit의 API 부분과 실행가능하도록 만들어 주는 런타임 플랫폼이 구분되었다는 점이다.
  • 이 구분된 런타임 플랫폼은 JUnit 5 Official User Guide1.1 What is JUnit 5 에 잘 설명 되어 있다.

본문은 아래와 같다. JUnit 5을 사용하는 것이 목적이라면 Dependencies 코드 부분까지 넘어가는 것을 추천한다.

1.1 What is JUnit 5?

Unlike previous versions of JUnit, JUnit 5 is composed of several different modules 
from three different sub-projects.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. 
It also defines the TestEngine API for developing a testing framework that runs 
on the platform. Furthermore, the platform provides a Console Launcher 
to launch the platform from the command line and a JUnit 4 based Runner 
for running any TestEngine on the platform in a JUnit 4 based environment.

JUnit Jupiter is the combination of the new programming model and extension model 
for writing tests and extensions in JUnit 5. The Jupiter sub-project provides 
a TestEngine for running Jupiter based tests on the platform.

JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests 
on the platform.
  • 보자면 JUnit 5은 그 전과 달리 3개의 서브젝트로 구성되어 있다는 내용이다.
  • 3개의 서브 프로젝트들은 JUnit Platform, JUnit Jupiter, JUnit Vintage이다.
    • JUnit Platform은 JVM에서 JUnit이 동작할 수 있도록 프레임워크를 인스턴스화 해주는 기반이다.
    • JUnit Jupiter는 JUnit 플랫폼 위에서 테스트를 동작하는 엔진이다. 개발자가 테스트 코드를 집중 할 수 있게 하고 다른 API를 사용해 테스트를 확장하는 것을 가능케 해준다.
    • JUnit Vintage는 이전 버전들 JUnit 3, JUnit 4의 테스트 엔진이다.

 


결국, 우리는 JUnit의 클래스와 메소드들을 사용하기 위한 JUnit API와 Runtime Platform인 Jupiter API, 두 개의 dependency를 추가해야한다.

  • Jupiter-API는 테스트 수행을 위해 testImplementation으로 추가해준다.
  • Jupiter-engine은 RuntimeOnly로 추가해준다.

Build.gradle 파일에 아래와 같이 dependencies를 추가해준다. 

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
}

 

4. 끝, 이제 실전 테스트다.

  • TestExample.java를 만들어 아래 코드로 테스트 해보자.
  • 다음과 같은 명령어를 활용해 테스트 해볼 수 있다. $ gradle clean test
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class TestExample {
    @Test
    public void testAdd() {
        assertEquals(42, Integer.sum(19, 23));
    }

    // assertThrows 는 Throws Exception 테스트의 이전 스타일을 대체하는 JUnit5의 새로운 기능이다.
    @Test
    public void testDivide() {
        assertThrows(ArithmeticException.class, () -> {
            Integer.divideUnsigned(42, 0);
        });
    }
}

 

성공!! Test의 성공은 언제나 기분이 좋다!

 

 

 


추가) JUnit 3이나 JUnit 4를 같이 사용해야하는 경우

build.gradle 파일의 Dependencies 부분에 아래 코드를 추가해준다.

testCompileOnly 'junit:junit:4.12' 
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.3.1'

 

참고

java.lang.String 클래스에는 비어 있는 문자열인지 체크하는 두 개의 메소드가 있습니다.

isEmpty() / isBlank() 두 메소드는 무엇이 다를까? 그 차이점을 알아보겠습니다.

1. isEmpty()

  • JAVA 6 이후에 추가된 메소드 입니다.
  • 문자열의 길이가 0인 경우에, true를 리턴합니다.
  • 내부적으로 구현은 String의 length가 0인지 체크하는 방식으로 되어 있습니다.
public boolean isEmpty() {
    return value.length == 0;
}

 

2. isBlank()

  • JAVA 11 이후의 버전에서 사용 가능합니다.
  • 문자열이 비어 있거나, 빈 공백으로만 이루어져 있으면, true를 리턴합니다.
  • 내부적으로는 WhiteSpace 아닌 첫 번째 인덱스와 전체 길이가 같은지 체크합니다.
public boolean isBlank() {
    return indexOfNonWhitespace() == length();
}

 

테스트 코드 구현

  • Junit 5을 활용하여 Java 11 버전에서 테스트를 수행하였습니다.
  • Test 환경이 없으신 분은 System.out.println 메소드를 사용해서 테스트 하시면 됩니다. System.out.println("Hello".isEmpty()); 
// isEmpty와 isBlank 메소드 둘 다 성공한 테스트입니다.
@Test
void isEmpty(){
    assertEquals("Hello".isEmpty(), false);
    assertEquals("Hello ".isEmpty(), false);

    assertEquals("".isEmpty(), true);
    assertEquals(" ".isEmpty(), false);
    assertEquals("\n\t".isEmpty(), false);
}

@Test
void isBlank(){
    assertEquals("Hello".isBlank(), false);
    assertEquals("Hello ".isBlank(), false);

    assertEquals("".isBlank(), true);
    assertEquals(" ".isBlank(), true);
    assertEquals("\n\t".isBlank(), true);
}

정리

위의 테스트 코드를 보면, 나머지 케이스는 isEmpty()와 isBlank()가 같지만,
마지막에 " ", "\n", "\t"와 같은 공백이 있는 문자열을 체크하는 경우에는 두 메소드의 결과가 다릅니다.

 

isEmpty()는 문자열의 길이를 체크하여, 문자열의 길이가 0인 경우에만 true를 리턴하기 때문에 빈 공백이 들어있는 문자열은 false를 리턴합니다.

 

isBlank()는 문자열이 비어 있는 경우와 공백(white space)를 포함하고 있는 경우 모두 true를 리턴합니다.

 

끝으로, isBlank()는 Java 11 버전에 추가되었기 때문에 Java 11 버전과 그 이후 버전에서만 사용 가능합니다.

 

참고

'Java' 카테고리의 다른 글

Gradle 로 Junit 5 테스트 할 수 있도록 빌드 하기  (0) 2021.10.26
Java에서 큰 수 다루기 (BigInteger)  (0) 2021.08.05
Java Packages & API 사용하기  (0) 2021.07.06
Java tutorial  (0) 2021.07.02

+ Recent posts