Chapter. 1 TDD
 
[챕터 목표]

  • 테스트 가능한 코드(Testable Code)의 의미를 명확히 이해하고, 다양한 종류의 테스트를 작성하며, TDD 기반의 요구사항 기능 개발을 학습합니다.
  • TDD(Test-Driven Development)의 개념과 프로세스(Red-Green-Refactor)를 학습하고, 실제 실무에서 적용할 수 있도록 연습합니다.
  • 상황에 따라 적절한 테스트를 작성하는 전략을 학습합니다.
  • 단순히 테스트를 작성하는 것을 넘어, 왜 테스트가 필요한지 근본적인 목적과 중요성을 이해합니다.
  • 주어진 과제를 분석하고, TDD 방식을 이용해 직접 기능을 구현하는 경험을 쌓습니다.

 
챕터 목표를 보니 유지보수성있고 견고한 코드를 짜기 위해, 실패하는 케이스를 비롯한..  여러가지 테스트 케이스를 남기기 위해 TDD를 한다.
 
TDD는 소프트웨어의 규모가 커지고 사용자 수가 많아짐에 따라 예측하기 어려운 장애가 자주 발생한다. 그래서 전통적인 개발 방식으로는 이러한 문제를 안정적으로 해결하기 어렵고, 지속적인 품질 유지와 빠른 변화 대응을 위해 자동화된 테스트와 TDD의 중요성이 더욱 강조되고 있다.
 
QA를 대신하여 빠른 결함 발견 및 문제 해결, 팀 내 코드 품질 및 협업 효율 향상, DEVOPS CI/CD 자동화를 통한 빠른 배포 사이클 확보를 목표로 한다.
 

결함률을 낮추고, 코드품질을 높여 협업에 용이하게 하고 자동화 배포에도 용이하게 하는 과정을 일련으로 하게 한다.
 
 
자 그러면 TDD 모키토를 암에 있어서 상세하게 알아야 할게 MockStub 이다.

 
Mock 예시 코드 (Mockito) 를 보면 객체의 행동을 검증하는데 제대로 수행했는지 검증한다.
registerUser 즉 유저 등록 과정(행동)이 정확히 1번 수행 되었는지 verify를 검증한다.

@Test
void userRepository_save_호출검증() {
    // given
    UserRepository mockRepository = mock(UserRepository.class);
    UserService userService = new UserService(mockRepository);
    User user = new User("test@example.com", "password123");

    // when
    userService.registerUser(user.getEmail(), user.getPassword());

    // then (저장 메서드가 정확히 1회 호출됐는지 검증)
    verify(mockRepository, times(1)).save(any(User.class));
}

Stub 예시 코드 (Mockito)
Stub은 객체의 상태를 반환하여 맞는지 본다. assertThat..

@Test
void userRepository_stub_응답제공() {
    // given
    UserRepository stubRepository = mock(UserRepository.class);
    UserService userService = new UserService(stubRepository);
    User user = new User("test@example.com", "password123");

    when(stubRepository.save(any(User.class))).thenReturn(new User(1L, user.getEmail(), user.getPassword()));

    // when
    long id = userService.registerUser(user.getEmail(), user.getPassword());

    // then
    assertThat(id).isEqualTo(1L);
}

 
TDD를 어설프게 배워서 그런지,, Mock과 Stub의 개념을 이번에 처음 명확히 구분해서 알았다. 대부분 Stub으로만 TDD하려했고, Mock의 형태로 하려 해본적이 없다.
 
왜냐하면... 기본적으로 정확히 동작했는지... 본다는 것은 사실 에러가 안났으면 정확히 동작했다고 당연히 간주해왔다... 이걸 궂이 TDD에서 궂이 확인하려면 정확하게 Mock 으로 verify 해서 확인할수 있다는 것을 알았다.
 
그리고 사실 객체의 행동보다 원하는 상태로 잘 변환 했는지 확인하는게 늘 더중요하게 봤다.
거기는 대부분 비즈니스로직이고 그렇기 때문에 위에서 when 과 thenReturn 이후에 일어나는 부분들이
assertThat 되었을 때 기본적으로 일단 작동하는 프로세스가 완성되는 것이다.
 

그러나 여기서 validation 체크를 해서 여러가지를 검증 하고 , 더나아가 동시성 상황에 대한 가정과 여러가지 부분에 대해서 생각하는 것은 재밌는 부분이다. 이게 바로 TDD를 통해서 할 수 있는 부분이다.

 
이렇게 정리하고 나니까 목표, 방향이 확 보이는데...  정리가 덜된 상태에서 어영부영하고 PR도 주중에 허둥지둥 내서 그런지 아쉬움이 남는다.
 
 
 
아래와 같이 확실히 결제와 같은 중요하고 핵심적인 부분은 그래도 확실히 Mock테스트를 통해 안전적인 호출을 확인하고 Stub로 기대하는 상태 결과값 반환을 기대하는것이 필요하다는 생각이 든다.

  • Test Double 활용과 강결합 해소 전략
    • 테스트 더블을 사용하여 외부 의존성을 제거하면 독립적이고 빠른 테스트가 가능합니다.
    • 외부 의존성이 강한 코드에서는 외부 환경에 따라 테스트 결과가 불안정하고 예측하기 어렵습니다.
    • 느슨한 결합을 통한 장점: 외부 시스템과의 결합도를 낮추면 코드가 유연해지고, 테스트가 빠르고 안정적이며 유지보수가 용이합니다.

 
아래 Mock과 Stub으로 하는 것을 보니 TDD가 좀더 재밌게 느껴진다. 호출 검증하고, 기대하는 결과값 보고 하는 것 말이다. 확실히 이런 부분은 TDD를 해놓으면 유지보수 이후까지 생각해 놨을 때 효율적이고 효과적이다.
 

/**
 * PaymentService는 PaymentGateway(외부 결제 서비스)에 직접적으로 의존합니다.
 * 이렇게 구현하면 실제 외부 시스템 상태(네트워크 문제, 서버 장애 등)에 따라 테스트가 영향을 받기 때문에,
 * 테스트가 불안정해지고 실행 시간이 길어질 수 있습니다.
 */
@Service
public class PaymentService {
    private final PaymentGateway paymentGateway;

    public PaymentService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public boolean processPayment(PaymentRequest request) {
        return paymentGateway.charge(request); // 실제 외부 시스템 호출 (강결합)
    }
}

/**
 * Mock을 사용한 예시입니다.
 *
 * Mock을 활용하여 외부 시스템 호출 여부(행동 검증)를 명확하게 확인할 수 있습니다.
 * 실제 외부 결제 서비스(PaymentGateway)에 접근하지 않고도 PaymentService가 
 * PaymentGateway의 charge 메서드를 정확히 1회 호출했는지 검증합니다.
 * 이를 통해 외부 시스템 상태에 의존하지 않는 독립적인 테스트가 가능합니다.
 */
@Test
void paymentService_mock을활용한결제요청호출검증() {
    // given: Mock 객체 생성 및 설정
    PaymentGateway mockGateway = mock(PaymentGateway.class);
    PaymentService service = new PaymentService(mockGateway);
    PaymentRequest request = new PaymentRequest(1000);

    // 외부 시스템(PaymentGateway)의 charge 메서드 호출 시 무조건 true를 반환하도록 설정
    when(mockGateway.charge(request)).thenReturn(true);

    // when: 서비스의 결제 처리 메서드 실행
    boolean result = service.processPayment(request);

    // then: 외부 시스템의 메서드 호출 여부와 결과 값 검증
    verify(mockGateway, times(1)).charge(request);  // 호출 횟수(행동) 검증
}

/**
 * Stub을 사용한 예시입니다.
 *
 * Stub은 특정 입력 값에 대한 반환 값을 미리 설정하여, 외부 시스템의 상태를 완벽하게 통제합니다.
 * 이 예시에서는 PaymentGateway가 '결제 실패(false)' 상태를 반환하도록 명시하여,
 * 결제가 실패하는 상황에서도 PaymentService가 올바르게 동작하는지 확인합니다.
 * 실제 PaymentGateway를 사용하지 않고도 원하는 상태 조건 하에서의 동작을 정확히 테스트할 수 있습니다.
 */
@Test
void paymentService_stub을활용한반환값검증() {
    // given: Stub 객체 생성 및 상태(반환값) 설정
    PaymentGateway stubGateway = mock(PaymentGateway.class);
    PaymentService service = new PaymentService(stubGateway);
    PaymentRequest request = new PaymentRequest(1000);

    // Stub 설정: PaymentGateway의 charge 메서드가 특정 요청에 대해 false(실패)를 반환하도록 명시
    when(stubGateway.charge(request)).thenReturn(false);  // 결제 실패 상황 가정

    // when: 서비스의 결제 처리 메서드 실행
    boolean result = service.processPayment(request);

    // then: 반환된 결과가 미리 정의한 상태와 일치하는지 확인
    assertThat(result).isFalse();  // 명시적으로 설정한 반환 값(상태) 검증
}

 
자 TDD 검증을 받아본다.
 
https://github.com/MyoungSoo7/hhplus-tdd-jvm/pull/1#issuecomment-2906452783

 

1. 서비스로 TDD 4개 기능 구현 및  테스트 by MyoungSoo7 · Pull Request #1 · MyoungSoo7/hhplus-tdd-jvm

서비스로 TDD 4개 기능 구현 및 테스트 c6562a3 b69f995 775d8b9 332d4a4 TDD로 완성된 서비스 로직 구현은 적절한지? TDD로 만든 테스트 케이스가 TDD 방식이 맞는건지? 프로젝트 구조가 도메인 식으로 되어

github.com

 
역시 그 냥 TDD 통과하게 해놓고 Service단 만들었더니..
사실상 Mock 테스트와 기본적인 Stub 테스트를 한것이다.
그러나 Stub에서 좀더 다양한 테스트가 가능한듯 하다~!
 
자... 일단 기본적인 뼈대만 이어서 만들어놓고, 기능을 명확하게 정의하고 검증 가능한 코드로 구현하는 개발 사고방식을 좀더 만들기 위해서  유저포인트 조회, 유저포인트 충전, 유저포인트 사용, 유저포인트 히스토리 기본적인 4개 사항을 보는 것이다.
 여기에 대해서 서비스만 만들고 기본적으로 이었다...그리고 TDD형식만 만들었는데...

  •  

 
이렇게 피드백이 왔다.
 

 

  • 이번 챕터에서 내가 작성한 테스트 중, 명확하거나 부족했던 부분은 무엇이었나요?

TDD 질문중의 하나이다. 일단 이것에 대해서 잘한 사람의 것을 살펴보았다.
 

import static io.hhplus.tdd.point.TransactionType.CHARGE;
import static io.hhplus.tdd.point.TransactionType.USE;
import static org.junit.jupiter.api.Assertions.*;

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.List;

import io.hhplus.tdd.service.PointService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(controllers = PointController.class)
class PointControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private PointService pointService;

    @Test
    @DisplayName("유저 포인트 조회 API")
    void getUserPointTest() throws Exception {

        //given
        long userId = 1L;
        long amount = 1000L;

        UserPoint userPoint = new UserPoint(userId, amount, System.currentTimeMillis());
        given(pointService.selectUserPoint(userId)).willReturn(userPoint);

        //when & then
        mockMvc.perform(get("/point/{id}", userId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("id").value(userPoint.id()))
                .andExpect(jsonPath("point").value(userPoint.point()))
                .andExpect(jsonPath("updateMillis").value(userPoint.updateMillis()));

    }

    @Test
    @DisplayName("유저의 포인트 충전/이용 내역 조회 API")
    void getHistoriesTest() throws Exception {
        //given
        long userId = 1L;
        long amount1 = 5000L;
        long amount2 = 3000L;

        PointHistory pointHistory1 = new PointHistory(1L, userId, amount1, CHARGE, System.currentTimeMillis());
        PointHistory pointHistory2 = new PointHistory(2L, userId, amount2, USE, System.currentTimeMillis());

        List<PointHistory> pointHistoryList = List.of(pointHistory1, pointHistory2);
        given(pointService.selectUserPointHistory(userId)).willReturn(pointHistoryList);

        // when & then
        mockMvc.perform(get("/point/{id}/histories", userId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.length()").value(pointHistoryList.size()))
                .andExpect(jsonPath("$[0].id").value(pointHistory1.id()))
                .andExpect(jsonPath("$[0].userId").value(pointHistory1.userId()))
                .andExpect(jsonPath("$[0].amount").value(pointHistory1.amount()))
                .andExpect(jsonPath("$[0].type").value(pointHistory1.type().name()))
                .andExpect(jsonPath("$[0].updateMillis").value(pointHistory1.updateMillis()))
                .andExpect(jsonPath("$[1].id").value(pointHistory2.id()))
                .andExpect(jsonPath("$[1].userId").value(pointHistory2.userId()))
                .andExpect(jsonPath("$[1].amount").value(pointHistory2.amount()))
                .andExpect(jsonPath("$[1].type").value(pointHistory2.type().name()))
                .andExpect(jsonPath("$[1].updateMillis").value(pointHistory2.updateMillis()));

    }

    @Test
    @DisplayName("포인트 충전 API")
    void chargeTest() throws Exception {
        // given
        long userId = 1L;
        long amount = 1000L;
        String s_amount = "1000";

        UserPoint chargedUserPoint = new UserPoint(userId, amount, System.currentTimeMillis());
        given(pointService.chargeUserPoint(userId, amount)).willReturn(chargedUserPoint);

        // when & then
        mockMvc.perform(
                        patch("/point/{id}/charge", userId)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(s_amount)
                )
                .andExpect(status().isOk())
                .andExpect(jsonPath("id").value(chargedUserPoint.id()))
                .andExpect(jsonPath("point").value(chargedUserPoint.point()));
    }

    @Test
    @DisplayName("포인트 사용 API")
    void useTest() throws Exception {
        // given
        long userId = 1L;
        long amount = 5000L;
        long usePoint = 1000L;
        String s_usePoint = "1000";

        UserPoint result = new UserPoint(userId, amount - usePoint, System.currentTimeMillis());

        given(pointService.useUserPoint(userId, usePoint)).willReturn(result);

        // when & then
        mockMvc.perform(
                        patch("/point/{id}/use", userId)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(s_usePoint)
                )
                .andExpect(status().isOk())
                .andExpect(jsonPath("id").value(result.id()))
                .andExpect(jsonPath("point").value(result.point()));

    }

}

 
나는 사실 TDD를 체계적으로 배우기도, 실무적으로 많이 써보지 않아서... 사실 구현하면서 직접 시나리오를 디버깅하면서 테스트하는 편이다.
 
그래서 perform메서드를 모른다...  사실상 여기에 TDD를 구현하려는 시도는 나에게 새로운 어노테이션과 새로운 체계를 배우는 느낌이다. 그래서 낫설다. kotlin으로 코드를 짜라 이런 느낌과 비슷하다. TDD로 구현하는 것도 사실상 직관적으로 given 에서 이어지는 willReturn , perform 에서 이어지는 andExpect 이런 것들을 거의 본적이 없어서, 약간 자바의 신기능들 .. 예를 들면 Record 이런거 처음 생겼을 때 보는 그런 느낌이다.
 
위에서 말한것처럼 밸리데이션, 유효성 체크를 하는 것은 기본적으로 되어 있다.
첫 시작단추에서 이런 구도가 안나오니 아주 기본적인 것만 하고 그냥 내버려둔것 같다.
 
그렇다고 구글아저씨나 AI에게 일일이 물어보며 삽질을 궂이 경험하는 것보다.. 이렇게 우수작품을 벤치마킹하는게 더 효율적인 것 같다.
 

 
서비스 단에서 구현해 보는 저기의 anyLong() 같은 것도... 사실 잘모른다... 흠... 이런거 깔끔하게 정리된거 없나...
간단히  소스 구조상으로는 id가 들어가서 UserPoint가 나오지만...
 
테스트 해야 할 것은 그게 아니라. 포인트 조회에 있어서 정상적으로 작동하는지 테스트하는것이다.
그래서  assertThat으로 result와 대조해 본다.
 

  • 처음부터 실패하는 테스트를 먼저 작성했나요? 그 이유는 무엇이었나요?,

테스트를 하면서 실패하는 테스트를 생각하는데 있어서 조회는 너무 싶다. 입력값을 다르게 하거나 아이에 다른것과 매치시키면 되기 때문이다. 단순한 CRUD의 경우에 보통 이렇다. 좀더 복잡한 lock 기능 구현에 있어서 생각해보면 좋을 것 같다.

  • 기능 구현보다 테스트 작성이 더 어려웠던 경험이 있다면 어떤 점이었나요?,

확실히 기능구현은 하면되는데 테스트 작성은 anyLong, perform , willReturn , when , given, containsExactlyInAnyOrder 이런 식으로 코드를 구조화 해본적이 없으니 여전히 어렵다. 코틀린 쓰나 이 TDD작성하나.. Java에서 신기능 쓰나 모르는 메서드를 알아내는 부분은 TDD 전모를 찾아봐야 되는데, 이것에 관해 문서 찾아보러 갈 정도의 여정이 없어서 어렵나보다.

  • Mock과 Stub을 각각 어떤 상황에서 사용했고, 그 판단 기준은 무엇이었나요?,

Mock은 실행되었나이기 때문에 실제로 약간의 고난이도 아니면 필요없고, Stub은 난이도 보다 상태변화이기 때문에 그것에 맞추어서 하면 된다고 생각한다. 난이도 vs 상태변화

  • 테스트를 먼저 작성했더니 구현이 더 쉬워졌거나 설계가 명확해진 경험이 있었나요?

없다... 테스트를 작성하는 것은 인텔리제이를 처음 썼을 때 만큼이나 용기가 필요한 것 같다. 인텔리제이에 익숙해진 지금 다시 이클립스로 못달아갈 정도로 편하지만.. 지금 테스트 작성을 하면서.. .보통 맨땅에 해딩하는 식으로 부딪히고.. 에러나면 디버깅하고... 디버깅하면서 다각도로 보면서 개발해온 방식 때문인지... 기능에 대해 명확해 진다는것은 아래와 같이 포인트 히스토리를 작성했을때.. 거시적인 관점을 가질수 있다는 것이다.

 

  • 학습하며 "이건 나중에 실무에서 꼭 써먹고 싶다" 느낀 포인트는?

간단한 기능을 만들어놓고 로직을 작성하면서, 
[1] 포인트 충전,사용정책 추가
[2] 동시성 제어 추가
 
이런 부분을 추가하고 나서 테스트를 통과하나 이렇게 기록을 남기는 것이다.
 
그렇다면 추후 유지보수시에 기본적인 포인트충전, 사용정책은 넘어갈거고,
동시성 제어에서 문제가 나타나면 그 부분에서 어떤 부분을 테스트 했고,
어떤 부분을 더 테스트하지 않아서 문제가 나타났을까 로그를 보면서 상황파악을 해볼수 있을것이다.
 
마지막으로, 통합테스트까지 깔끔하게 하니 기분이좋다~!
 

 
 
 

LIST

버전을 설명하기 전에 자바의 JVM 간단 설명

[JVM 구성요소 : 클래스로더시스템 , 메모리, 실행엔진]

1. 클래스 로더는 말 그대로, 클래스를 읽어오는 시스템 구조인데, 로딩 -> 링크, 초기화순으로 진행된다.

2. 메모리는 스택, 레지스터, 힙, 메소드, 네이티브메서드스택으로 구성된다.

 (1) 힙은 인스턴스화된 모든 클래스 인스턴스와 배열을 저장, 객체를 저장하게 되는데, 모든 JVM 스레드에 공유되는 공유자원이다.

 (2) 메소드영역에서는 런타임 상수풀, 필드와 메소드 데이터 내용 ,즉 클래스 수준의 정보를 저장하게 된다. (논리적으로는 메소드는 힙의 일부다.)

 (3) PC 레지스터는 현재 실행중인 메서드가 네이티브가 아니면, 현재 실행중인 JVM명령어 위치에 저장되고, 네이티브이면 PC레지스터에 저장되는 값은 저장되지 않는다. CPU의 레지스터와 다르게, 연산을 위해 필요한 피연산자를 임시로 저장하기 위한 용도로 사용한다.

 (4) 스택(JVM 스택이라고도 불린다.) 은 LIFO동작으로, 쓰레드 마다 런타임 스택을 만들고 , JVM스택에는 프레임에 저장된다. 메서드 하나가 호출될 때마다 새 프레임이 생성되어 스택에 쌓이고, 메서드 호출이 정상 완료되거나 예외가 던져지면 프레임은 스택에서 빠지면서 소멸된다. 또한 쓰레드가 종료되면 스택도 제거된다.

 (5) 네이티브 메소드 스택 : 네이티브 메서드 스택은 JVM의 스택이 아니라 C스택을 가르킨다. 자바가 아닌 다른 언어로 작성된 메서드를 지원하기 위해 사용되는 스택( JVM스택과 마찬가지로 스레드 단위의 자료구조)

[하기 참고]

https://homoefficio.github.io/2019/01/31/Back-to-the-Essence-Java-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%EA%B9%8C%EC%A7%80-2/

 

Back to the Essence - Java 컴파일에서 실행까지 - (2)

Back to the Essence - Java 컴파일에서 실행까지 - (2)Java 11 JVM 스펙을 기준으로 Java 소스 코드가 어떻게 컴파일되고 실행되는지 살짝 깊게 알아보자. 이번엔 2탄 실행 편이다. 1탄 컴파일 편은 여기에..

homoefficio.github.io

3. 실행엔진은 말 그대로 클래스 로딩 과정을 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행하게 된다. 이제 이 과정을 수행하고 기계가 읽을 수 있는 코드로 변경을 해주는데 이 때 사용되는게 인터프리터와 JIT컴파일러이다.

 

[이펙티브 자바 참고]

=>네이티브 메서드는 신중하게 사용하라

https://itstory.tk/entry/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EA%B7%9C%EC%B9%9954-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EB%A9%94%EC%84%9C%EB%93%9C%EB%8A%94-%EC%8B%A0%EC%A4%91%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC

 

[이펙티브 자바] 규칙54. 네이티브 메서드는 신중하게 사용하라

이펙티브 자바 Effective Java 2/E국내도서저자 : 조슈아 블로크(Joshua Bloch) / 이병준역출판 : 인사이트 2014.09.01상세보기 자바의 네이티브 인터페이스(Java native interface, JNI)는 C나 C++ 등의 native programming

itstory.tk

 

 

자바 8 : 람다, 스트림, 인터페이스 디펄트 메소드, 옵셔널, LocalDateTime

(연장지원은 2023년 9월 종료 - 32비트 지원하는 마지막 버전, 이후는 서드파티에서만 지원)

 

자바9 : Project Jigsaw 기반으로 런타임이 모듈화된 것이 가장 큰 특징

본래는 2016년 발표 예정이었으나 2번이나 연기되어 2017년 7월 27일 발표 예정, 그나마도 한번 더 연기되어 9월 21일에 발표되었다. 가장 큰 원인은 역시 Project Jigsaw의 개발 난이도였다. 런타임의 모듈화는 하위 호환성을 어느 정도 포기하고 성능을 추구한 것이기에 아직 현장에서는 Java 9로 넘어가는 것을 꺼리는 분위기다. (2018년 3월에 종료)

 모듈이란 이름(name), 어떤것을 제공(export)하고 어떤것들이 필요한가(require)를 선언하는 소프트웨어적 단위

JDK의 모듈화는 사용하려는 Java Runtime의 모듈을 따로 지정할 수 있음을 의미한다. 이제는 java.desktop 또는 java.corba를 사용하지 않는 경우, Swing 또는 Corba 모듈이 포함된 환경에서 애플리케이션을 실행할 필요가 없다.(애플리케이션에서 사용하는 모듈만 모아  Runtime이미지를 만들수 있다.)

https://infoscis.github.io/2017/03/24/First-steps-with-java9-and-jigsaw-part-1/

 

Java 9 와 Project Jigsaw 소개 1

의역, 오역, 직역이 있을 수 있음을 알려드립니다.이 포스트는 원저자의 동의를 얻어 한글로 번역한 내용입니다. This post is a translation of this original article [https://blog.codecentric.de/en/2015/11/first-steps-wi

infoscis.github.io

 

자바10 : var 키워드, 자바소스파일 컴파일 전 실행가능

( Java 기반의 JIT 컴파일러가 추가되었고, 이전 버전에서 Deprecated 처리된 API는 Java SE 10에서 모두 삭제되었다.)

=> JIT(Just In Time) : 컴파일시 바이트코드를 저장소 저장하는 개념 ( 인터프리터가 안읽어도 된다.)

 

 

자바11 : 오라클, 오픈 JDK 통합 (2018년 9월 25일 발표 , 일반지원은 2023년 9월 , 연장지원은 2026년 9월에 종료될 예정)

이클립스 재단으로 넘어간 java EE가 JDK에서 삭제되고(스프링에 주도권을 빼앗겨 오라클은 자바EE수익화 실패- 자바EE8이 마지막 버), JavaFx도 JDK에서 분리되어 별도의 모듈로 제공된다.

( Java Fx는 경량 사용자 인터페이스 API를 사용하여 리치 인터넷 어플리케이션을 만들때 사용된다. JavaFx 어플리케이션은 하드웨어 수준에서 가속기능을 사용할 수 있는  그래픽과 미디어 엔진을 갖추고 있어 보다 클라이언트의 성능에 신경을 써야하는 분야에서 사용하면 좋다. JavaFx어플리케이션 또한 자바 EE플랫폼 서비스의 클라이언트 역할들 담당한다.)

Gloun 이라는 업체가 JavaFX를 유지보수 중 : https://gluonhq.com/products/javafx/  

* Oracle JDK가 구독형 유료 모델로 전환된다는 점 * => Azul Systems개발 https://www.azul.com/downloads/#zulu

Zulu JDK는 오라클의 TCK인증을 받은 구현체로 개인과 기업 모두 무료로 사용할 수있다.(기술지원만 유료)

 

자바 12: 스위치 문 확장, 가비지 컬렉터 개선 (2019년 3월 19일 공개)

 

자바 13 : 스위치문에 yield 예약어 추가 ( 2019년 9월 17일 공개)

 

자바14 : 스위치 표현시 표준화, instanceof 패턴 매칭, record 데이터 오브젝트 선언 기능 추가 (2020년 3월 18일 공개)

 

자바 15 (2020년 9월 15일 공개)

EdDSA암호화 알고리즘 추가 (자바는 JCA[Java Cryptography Architecture]와 , JCE [Java Cryptography Extension] 를 기반으로 오래전부터 보안 관련기능을 제공해왔다.  )  : JCA는 JDK1.1부터 제공된 암호화 기능을 담은 보안 관련 핵심 프레임워크다. 전자서명, 메시지를 해시하는 메시지 다이제스트, 인증서와 인증서 유효성 검사, 암호화 및 복호화, 키 생성과 관리 API를 포함한다.

공개 키 암호화에서 Edwards-curve EDDSA (Digital Signature Algorithm)는 트위스트 에드워드 곡선(Twisted Edwards curve)을 기반으로하는 Schnorr 서명의 변형을 사용하는 디지털 서명 체계이다

https://perfectmoment.tistory.com/1231

 

SSH의 공개키 암호화에는 'RSA', 'DSA', 'ECDSA', 'EdDSA' 중 어떤 것을 사용하면 좋을까?

원격으로 컴퓨터에 액세스하기 위한 프로토콜인 SSH는 컴퓨터의 인증을 위해 공개키 암호화를 사용하고 있습니다. 공개키 암호화 방식인 RSA, DSA, ECDSA, EdDSA의 각각의 구조와 'SSH에 적합한 방식'에

perfectmoment.tistory.com

 

패턴 매칭( 2차 미리보기) , 스케일링 가능한 낮은 지연의 가비지 컬렉터 추가

Solaris(썬마이크로 시스템 -os) 및 SPARC플랫폼(썬마이크로 시스템 - 확장형 프로세서 아키텍처) 지원제거 , 외부메모리 접근 API , 레코드(2차 미리보기) , 클래스 봉인 - 상속가능한 클래스 지원

public sealed class Animal
       Dog,
	   wiki.namu.example.species.india.Monkey,
	   wiki.namu.example.species.sabana.Leopard
{
    // ...
}

다중 테스트 블록 : 자바에서도 여러줄의 문자열을 손쉽게 작성

 

자바16(2021년 3월 16일 출시) 

자바에서도 자동 병렬 프로세싱을 지원하는 자동 벡터 API가 추가

OpenJDK의 버전 관리가 Mercurial 이었으나 이제 Git으로 바뀐다. 

OpenJDK 소스를 GitHub에서 볼 수 있다.

자바11부터 시작했으며 15부터 도입한 ZGC기능이 향상 ( ZGC는 메타데이터를 객체의 메모리 주소에 표시한다. ZGC는 64비트만 지원하는데, 메모리의 주소 파트로 42비트(4TB)를 사용하고 다른 4비트를 GC metadata( finalizable, remap, mark1, mark0 ) 를 저장하는 용도로 사용한다.

https://www.blog-dreamus.com/post/zgc%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

 

ZGC에 대해서

Java ZGC의 동작 원리와 특징에 대해서 상세히 알아보고, 이를 통해 현업에 ZGC 도입시 주의할 점도 함께 살펴봅니다.

www.blog-dreamus.com

유닉스 도메인 소켓이 지원

Alpine Linux 지원 추가

자바8부터 제거된 악명 높은 PermGen 대신 Metaspace방식을 지원  (자바의 Classlader가 로드한 class들의 metadata가 저장되는 공간 -  Heap 사이즈모니터링 뿐만 아니라 Metaspace에 대한 모니터링이 필요할 수도 있다)

JNI(Java Native Interfac)를 대신할 외부 링크방식의 인터페이스를 인큐베이팅을 통해 시작한다.  

값유형의 클래스를 동기화에 사용 시 경고 메세지가 개선되었다.

public class JEP390 {

    public static void main(String[] args) {

        Double d = 20.0;
        synchronized (d) {} // 컴파일 시 해당 줄 내용과 함께한 경고 메시지가 친절히 표시된다.
    }
}

jpackage 명령어를 통해 각 운영체제별 자바 프로그램을 설치 패키지(pkg, deb,msi 등) 로 생성하는 기능이 정식으로 추가되어, 자바프로그램을 손쉽게 배포하는 기능이 추가된다.

자바 15의 외부메모리 접근 인큐베이팅 2차

자바14의 패턴 매칭이 정식 기능으로 추가

자바 9부터 추가되어 자바 내부 API 접근에 대한 경고 무시 기능이 강화되어 내부 API접근 시도시 기본적으로 오류와 함께 자바 프로그램이 종료될 수 있도록 강화

자바 15에 추가된 봉인 클래스의 2차 미리보기

 

 

자바 17( 자바8,11을 이을 세번째 LTS버전이 한국시각 2021년 9월 15일에 출시 되었다.)

패턴매칭은 여전히 Preview단계이다.

외부 함수/메모리 API 및 신규 벡터 API는 인큐베이팅 단계이다.

애플릿이 완전히 제거될 예정으로 Deprecated 처리.

애플 M1 및 이후 프로세서 탑재 컴퓨터 제품군에 대한 정식 지원

macOS 그래픽 렌더링 베이스를 OpenGl에서 Metal로 교체

의사난수 생성기를 통해 예측하기 어려운 난수를 생성하는 API정식 추가

컨텍스트 기반의 역직렬화 필터링 

봉인클래스가 정식 추가되었다.

자바18 (2022년 3월 22일에 출시)

자바 API의 기본 Charset이 UTF-8로 지정되었다.

정적 파일을 서빙하는 기능만 있는 심플한 웹서버 제공(커맨드라인툴)

Java API Doc에 @snippet 태그 추가

리플렉션 기능 리팩터링(메소드 핸들을 이용해 다시 구현)

벡터 API ( 세번째 인큐베이터 단계)

Internet-Address Resolution SPI

외부 함수 & 메모리 API (두번째 인큐베이터 단계)

switch 문 패턴 매칭 ( 두 번째 프리뷰)

try문의 finally 기능 deprecate (아직 제거 되진 않았지만 사용을 권장하지 않는다. try -with-resources를 이용하자)

 

자바19 (2022년 9월에 출시)

레코드 패턴매칭(프리뷰)

Linux/RISC- V port

외부 함수 & 메모리 API (프리뷰)

가상 쓰레드 (프리뷰)

벡터 API (네번째 인큐베이터 단계)

switch 문의 패턴 매칭( 세번째 프리뷰)

멀티쓰레드 프로그래밍을 단순화하는 Structed Concurrency API(인큐베이터 단계)

 

자바20 (2023년 3월에 출시)

Scoped Values(인큐베이터 단계)

레코드 패턴 매칭(두번째 프리뷰)

switch 문 패텅 매칭(네 번째 프리뷰)

외부함수 & 메모리 API (두번째 프리뷰)

가상 쓰레드( 두 번째 프리뷰)

Structed Concurrency( 두번째 인큐베이터 단계)

 

자바21(2023년 9월에 출시예정 , 자바8,11,17에 이은 네번째 LTS)

 

 


 

결론적으로 자바8, 자바11, 자바17 이 세가지 LTS버전을 기준으로 생각할 때

JavaEE가 제거된 자바11, 보안이 강화된 자바17을 생각하면

어짜피 스프링부터 시작한 개발자는 자바 17을 사용해서 개발하는게 낫다는 결론이 이른다.

 

자바21도 올해 9월 4번째 LTS출시예정이라고 한다...

참 버전 나오는 속도가 빠르다.

어쨌거나 정리하고 나니 자바 17을 써서 공부해야겠다.

 

https://marrrang.tistory.com/16

 

Java 각 버전의 특징들 (~JAVA18)

Java 1.8이 나온지는 벌써 6년정도 되었습니다. 그동안 java 1.8을 꾸준히 사용해오고 jdk 11뿐만 아니라 최근에는 jdk 17까지 나온 상황입니다. 이 시점에서 다시 한번 정리하면 좋을 것 같아서 Java 1.8

marrrang.tistory.com

 

LIST

1. 자바의 객체지향은 클래스라는 설계도

2. 프로그래밍의 3대요소를 쓰시오? -> 변수가 만들어지면 변수를 관리하는 Symbol 테이블에 저장

변수, 자료형(변수의 크기와 어떤 종류의 데이터를 저장할 것인지를 결정 ) , 할당

 3. 자바의 기본자료형(PDT) 8개를 작성하시오.  -> 사용자자료형 UDDT

-> boolean(1 byte) , char(2 byte) ,  byte(2byte) , short (2byte) , int (4byte), long ( 8 byte  ) , float(4 byte) , double(8 byte)

4. 진수 진법 이해

10진수 -Decimal , 2진수 - Binary , 8진수 -  octal , 16진수 - Hexa

[참고 깃헙]

https://github.com/bitcocom/fcjava

 

 

 

 

 

LIST

+ Recent posts