이 시리즈를 통해 회사에 입사하고 2년 반동안 겪은 다양한 경험들을 바탕으로 성장할 수 있는 방법들을 공유해보는 시간을 가져보고자 합니다.

 

저와 같은 주니어 개발자 혹은 취업을 준비하고 있는 취업 준비생 분들에게 조금이나마 도움이 됐으면 좋겠다는 제 생각을 공유하는 것이니 열린 마음으로 읽어주시면 감사하겠습니다 ㅎㅎ

1. 해보고 질문하자. 물어보자. 

최근 다양한 IT 매체에서 '주니어 개발자는 질문을 많이 해야된다'는 이야기를 간접적으로 많이 보고 들은 것 같습니다. 하지만 무엇을 질문하는가에 대한 이야기는 구체적으로 안해준다는 것이 굉장히 아이러니 했습니다.

 

아래 질문 예시를 볼까요?

A 사원:  팀장님. 프로젝트를 git에서 pull 받은 뒤 로컬 환경에서 실행하려 하는데 빌드에서 충돌이 나서 실행이 안됩니다. 혹시 어떻게 해결하는지 아시나요?

B 사원: 팀장님. 프로젝트를 git에서 pull 받은 뒤 로컬 환경에서 실행하고자 하는데 빌드간 충돌이 발생해 실행이 안되고 있습니다. 
확인해 봤을 땐 IDE 초기 설정이 문제인 것 같은데 혹시 해당 프로젝트 실행간 필요한 설정이 별도로 있는지 확인 가능하실까요?

C 사원: 팀장님. 프로젝트를 git에서 pull 받은 뒤 로컬 환경에서 실행하는 경우 정상 실행되지 않는 문제가 있는 것 같습니다.
혹시 팀 내에 이슈 관련 이력들이 공유되고 있을까요? 공유되고 있다면 어디서 확인할 수 있는지 알려주시면 감사하겠습니다. 

 

여러분이 팀장이라면 세 명의 사원들 중 어떤 사원의 질문이 가장 마음에 드시나요? 

 

더보기

저라면 답변을 가장 짧게 할 수 있는 질문을 한 사람이 가장 마음에 들 것 같습니다.

A에게 하는 답변: ~는 확인해 보셨나요?
B에게 하는 답변: IDE 화면 캡쳐해 공유 후, 혹시 이것과 차이점이 있으실까요?
C에게 하는 답변(이력이 없을 경우): 이력 찾아본 뒤 링크 공유. 
C에게 하는 답변(이력이 있을 경우): 이력은 따로 관리하지 않습니다. 혹시 ~는 확인해 보셨나요?

 

이력이 잘 관리되고 있는 팀에서는 C 사원의 답변이, 그렇지 않을 경우엔 혼자서 어느정도 원인을 파악해 확인해봐야 될 부분을 좁혀준 B 사원의 질문이 가장 마음에 들겠네요.

 

다시 주제로 돌아가서, 어떤 상황에서건 혼자서 해보고 질문하는 것은 매우 중요한 요소입니다. 이것은 질문하는 사람의 시간을 뺏는 것을 최소화하며 모르는 부분을 찾아 해결하는 능력을 향상시킬 수 있기 때문입니다.

 

'나 열심히 찾아봤는데 아직 잘 모르겠어... 이것까진 생각해봤는데 도와줘..'가 질문을 읽은 사람에게 느껴지는 질문이 가장 좋은 질문이라고 생각합니다. 

 

30분은 찾아보자

그렇다고 3~4시간, 하루 이틀씩 하나의 문제를 가지고 끙끙 앓을 필요는 없습니다. 중요한 것은 질문하기 전 고민을 해 봤는가이지 시간적인 노력이 아니기 때문입니다. 

 

따라서 일정 시간을 정하고, 그 시간이 넘어서도 잘 모르겠으면 질문을 해 문제를 해결하는 것을 권장드립니다. 

 

제가 정한 규칙은 30분입니다. 2년간의 축척된 삽질을 바탕으로 통계를 내 본 결과, 30분 정도 찾아보면 내가 해결할 수 있는 문제인지 파악할 수 있는 최소 시간이었음을 깨달았기 때문이죠.

 

물론 이 시간은 온전히 제 경험을 통한 저만의 의견입니다. 하지만, 질문을 하기전 혼자 고민해보는 시간의 마지노선을 정하는 것을 추천드립니다.

 

2. 하기 전에 물어보자

혼자서 개발하실 경우 큰 문제는 없겠지만 팀원들과 함께 업무를 할 경우에는 무조건 질문하시는 것을 추천드립니다. 

 

개발자라는 직업은 문제 해결을 하는 직업이기 때문에 하나의 논제에도 다양한 해결 방안이 나오기 마련입니다.  코드 리뷰 문화가 잘 자리잡은 팀에서도 작은 개발 단위인 PR(Pull Request)에서조차 여러 의견이 나올 수 있습니다.

 

아래 예시를 봐볼까요? 

A 사원: 파일 업/다운로드 기능 개선건 PR 올렸습니다.
B 대리: 네 확인해 볼게요~ (확인 중) A 사원님. 파일 확장자나 파일 다운로드 제한 기능 같은게 없는 것 같은데 이유를 알 수 있을까요? 그리고 지금까진 사내 OBS를 써왔는데 트래픽이 많아져서 클라우드 연동해서 쓰기로 어제 결정났거든요. 이런 것들 고려해서 다시 PR 올려주세요~ (PR Close)
A 사원:  ... (타닥타닥) 

오늘 결론냈던 내용이 고객 요청으로 인해 요구사항이 변경되었을 수 있고, 내가 고려하지 못한 부분까지 다른 팀원들이 생각할 수 있기 때문에 재작업을 하지 않기 위해선 코드를 구현하기 전 다시 한 번 구체적인 논의를 진행 후 작업을 진행하시는 것을 권장드립니다. 

 

이런 논의를 자주 하다보면 함께 일하는 팀원의 개발 스타일이나 사고를 알 수 있어 점차 긍정적인 방향으로  업무 처리를 진행할 수 있습니다. 주니어 개발자라면 대부분이 본인보다 높은 경력을 가지고 있을 것이기 때문에 이러한 논의에서 배울 수 있는 점이 매우 많으니 작업 이전에 'B 대리님. 코드 구현 이전에 고려한 내용을 정리해 봤습니다. 이런 방향으로 구현할 계획인데 보완할 점이나 고려해야할 부분이 추가로 있을까요?'라고 한 번 물어보세요.

 

3. 신기술. 그걸 도입해서 얻는 이점이 뭔데?

MSA, AI, DDD, 헥사고널 아키텍쳐 등 다양한 개념들이 쓰나미처럼 몰려오고 있습니다. 개발 실력 향상을 추구하는 주니어 개발자에게 있어 이러한 기술들은 굉장히 해보고 싶고, 구미가 당길법한 것들입니다. 이러한 것들을 도입해보자! 제안해본 분들도 있을 것이라 생각됩니다.

 

어떤 답변을 받았었나요? 부정적이거나 흐지부지 끝난 경우 많았었나요? 혹은, 다음과 같은 질문을 받아보셨나요?

 

'이걸 도입하면 얻는 이점이 뭐죠?'

 

신기술 도입에는 사실 숨겨진 이면이 있습니다. 공수라고 하는 괴물이지요. 신기술을 공부하고 기존 시스템에 도입하는 그 긴 시간동안 팀은 별도의 성과를 내야 합니다. 왜냐면 회사니까요.

 

회사 입장에서는 신기술을 도입하건 1990년대 java 6을 도입하건 시스템이 개발되어 수익을 얻는 것에 더 관심을 많이 가질 것입니다.

들인 노력에 비해 기대 효과가 크지 않은 것들에 대해 시간과 노력을 투자하는 것은 옳지 않다고 생각하겠지요. 

 

만약 도입하고 싶거나 해보고 싶은 것들이 있다면 팀에서 공수를 들여야 하는 합당한 이유들을 준비하거나 사이드 프로젝트로 공부하시는 것을 추천드립니다. 개발자이기 이전에 월급을 받는 회사원이라는 점 명심하세요.

 

 

4. 몰라도 자신있게 의견을 말하자. 고집은 부리지 말고..

사실 생각보다 팀에서 주니어에게 바라는 것은 크게 없습니다. 지금 당장 시스템을 개선한다던지 신규 프로젝트를 할거면 경력 있는 개발자를 채용하겠죠. 그럼 왜 신입, 주니어를 채용할까요? 

 

제가 생각하기엔 두 가지가 있습니다. 그것은 바로 안정성을 흔드는 열정과 아직 열려있는 성장 가능성입니다.

팀에 열정을 불어넣는 사람이 된다면 지금 당장 팀에 도움이 되지 않아도 팀원으로서 팀에 이바지 할 수 있지 않을까요?

 

따라서 최대한 업무에 적극적으로 참여하고 자신있게 의견을 제안하고 말해봅시다. 모르는게 있으면 '제가 잘 몰라서 그러는데 이건 뭔가요?' 자신있게 궁금해 하세요.

 

몰라도 괜찮습니다. 잠깐 부끄러운 대신 그 지식은 부끄러운 만큼 오래 갈테니까요.

 

 

오늘은 개발적인 것보다는 마음가짐에 대해 이야기 해봤습니다. 사실 이 모든 이야기는 제가 신입때 경험했던 것들을 바탕으로 글을 작성한 것이니 저보다 뛰어나신 분들은 '뭐 당연한 소리를 글로 쓰고 있어' 라는 생각을 하실 수도 있습니다. 

 

 

근데... 팀원들은 본인에 대해 어떻게 생각할지 모르잖아요?
항상 겸손해야 합니다

 

 

2부에서는 지금 쓴 내용을 토대로 2년 반동안 어떻게 성장하고 있는지 조금 더 구체적으로 작성해 보겠습니다. 아직 햇병아리지만 귀엽게 봐주셨으면 감사하겠습니다.

 

감사합니다.

이 글에서는 Junit4에서 Junit5으로 migration 하는 과정을 공유드립니다.

Junit5로 마이그레이션은 Junit4를 그대로 사용해도 되지만 Junit5에서는 어떤 기능들이 변경되었고 어떤 기능이 추가되었는지 확인하고자 Junit5 migrate 방법신규 기능 적용 내용들을 정리해 작성했습니다.

Migration 과정

1. Dependency 변경

Spring Boot는 2.2 부터 JUnit5가 기본으로 채택되었습니다.

 

pom.xml

  1. maven-surefire-plugin 2.22.0 버전 이상 plugin 추가
<plugin>
   <artifactId>maven-surefire-plugin</artifactId>
   <version>2.22.2</version>
</plugin>
  1. Spring Boot 2.2 이상 버전 spring-boot-starter-test dependency 추가
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
  1. 기존 junit4와 관련된 dependency 제거
<!-- 제거 해야합니다 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junit.version}</version> <!-- 4.12 -->
    <scope>test</scope>
</dependency>

 

주의사항 1:  ScriptEvaluationException

다음과 같이 진행할 경우 다음 오류가 발생할 수 있습니다.

Caused by: java.lang.ClassNotFoundException: org.junit.jupiter.api.extension.ScriptEvaluationException

위의 경우, 한 클래스 내에서 junit4와 junit5를 혼합해 사용하면 발생하는 이슈였습니다. 오류 발생시 남아있는 junit4 logic을 확인해보시길 바랍니다. (저 같은 경우 @RunWith를 @ExtendWith로 변경하지 않아 발생한 오류였습니다.)

주의사항 2:  maven-surefire-plugin version

말로는 2.22.0 버전 이상을 사용할 경우 이상이 없다고 하지만, 어플리케이션 테스트 관련 이슈가 존재해 plugin version을 2.22.2버전을 사용하였습니다. (ex) @ExtentionWith 실행시 오류 발생 등)

2. Migration

 

junit4와 junit5의 변경된 부분은 크게 두 가지 부분이 존재합니다.

  • annotation
  • dependency (org.junit.jupiter.api 사용)

2-1 annotation

annotation은 크게 다음과 같은 내용들이 변경되었습니다.

  1. @RunWith(SpringRunner.class) -> @ExtendWith(SpringExtension.class)
  2. @Rule -> @ExtendWith
  3. @Before, @After -> @BeforeEach, @AfterEach
  4. @BeforeClass, @AfterClass -> @BeforeAll, @AfterAll
  5. @Ignore -> @Disabled or @DisabledIf(조건문)

2-2 dependency

  1. junit5 는 org.junit.jupiter.api를 사용합니다.
  2. 기존에 사용하던 org.junit.Assert는 AssertJ, Hamcrest, Truth 등으로 변경합니다.

Junit5 기능 적용하기 (feat. 회사 프로젝트 남몰래 바꾸기)

migration 이후 개인적으로 어떤 부분이 junit 4에서 5로 기능을 수정하면 좋을까에 대한 고민을 해봤습니다.

가이드를 참고하더라도 실제로 적용해보지 않으니 어떤 기능이 개선된 것인지 감이 오지않아 Condition Checker에 junit5의 일부 기능을 도입해 보았습니다.

추가된 어노테이션들을 대상으로 리팩토링을 진행하였습니다.

 

1. @DisplayName (Junit5 User Guide(공식 문서) - 2.4. Display Names)

기존에는 테스트 코드 실행시 테스트 메소드 명칭이 나왔으나 junit5부터는 @DisplayName 어노테이션을 통해 통합 IDE에서 테스트 실행시 보여지는 메소드 명칭을 수정할 수 있습니다.

이를 통해 테스트 수행시 좀 더 해당 메소드가 수행하는 내용을 직관적으로 설명이 가능해집니다. 아래 테스트 내용을 통해 실행 결과가 어떻게 변경되었는지 보여드리겠습니다.

변경 전

변경 후

해당 코드를 작성하지 않은 사람이 테스트 코드를 수행할 경우 어떤 기능을 테스트하고 있는지 이전보다 더 직관적으로 보여주는 것을 확인할 수 있습니다.

 

2. @Disabled (Junit5 User Guide(공식 문서) - 2.8. Conditional Test Execution)

Junit4에서 사용되는 @Ignore가 Junit5에서는 @Disabled로 변경되었습니다.

이 뿐만 아니라, @DisabledIf, @EnabledIf, @EnabledOnOs 등 다양한 조건문 어노테이션을 통해 테스트를 조건부로 실행시킬 수 있습니다.

예시

@Disabled("서버마다 공통된 mount 정보가 없어 테스트 불가능.")
@Test
public void test_get_mountDetailInfo_FAILED_when_data_not_exist() throws Exception {
    // Given
    MountStatus mountStatus = new MountStatus(getMountPath());

    // When
    String result = mountStatus.getDetail();

    // Then
    assertEquals("Nas path : devfs\n" + "Mount local path : /dev/\n" + 
    "Total : 189\n" + "Used : 189\n" + "Usage percent : 100%\n", result);
}

 

3. @TestMethodOrder (Junit5 User Guide(공식 문서) - 2.10. Test Execution Order)

기존 junit4에서는 @FixedMethodOrderMethodSorters의 조합으로 테스트 순서를 정의할 수 있었습니다. 하지만 해당 어노테이션으로는 테스트 메소드 순서 정의에 있어 기능이 적어 일부 제한이 존재하였습니다.
(MethodSorters.DEFAULT, MethodSorters.JVM, MethodSorters.NAME_ASCENDING 세 개가 전부)

Junit5에서는 @TestMethodOrderTestClassOrder를 지원하며 @Order를 통해 원하는 테스트 클래스/메소드 실행 순서를 정의하여 실행이 가능해집니다.

 

예시

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {
 @Test
 @Order(1)
 void nullValues() {
 // perform assertions against null values
 }
 @Test
 @Order(2)
 void emptyValues() {
 // perform assertions against empty values
 }
 @Test
 @Order(3)
 void validValues() {
 // perform assertions against valid values
 }
}

 

해당 어노테이션은 ConditionInfoCheckerTest->ConditionCheckerTest 테스트 클래스에 적용되었으니 condition checker junit5 @TestMethodOrder 적용 건을 확인해보셔도 좋을 것 같습니다.

 

4. @Nested(Junit5 User Guide(공식 문서) - 2.12. Nested Tests)

기존 junit4에서는 inner class를 이용한 테스트 그룹핑이 불가능하였으나 junit5부터는 @Nested 어노테이션을 이용할 시 테스트간 그룹핑이 가능해졌습니다.

하나의 테스트 클래스에서 테스트를 수행하지만 수행하는 테스트의 기능이 여러가지일 경우 기능별로 묶어 수행 결과에 대해 좀 더 직관적으로 확인이 가능해집니다.

 

적용 대상 클래스를 확인해보니 3가지 그룹으로 그룹핑이 가능했고,  이들을 @Nested로 그룹화하여 inner class로 코드를 리팩토링하였습니다.

 

기존 결과(변경 전)

변경 후 결과

다음과 같이 변경된 이후 좋았던 것은 그룹핑 전엔 보이지 않았던 기능별 테스트 목록이 그룹핑 후 조금 더 직관적으로 보였다는 부분입니다. @DisplayName과 함께 사용하니 그룹별로 어떤 기능의 테스트가 미흡한지 보다 더 잘 알 수 있어 테스트 코드를 추가로 보완할 수 있었습니다.

 

5. @RepeatedTest (Junit5 User Guide(공식 문서) - 2.15. Repeated Tests)

반복 테스트로서 해당 어노테이션을 정의한 테스트는 원하는 반복 횟수를 정의하면 정의한 횟수만큼 테스트를 수행합니다.

@RepeatedTest를 테스트 메서드에 붙일 경우 다음과 같은 실행 결과를 얻을 수 있습니다.

 

RepeatedTest 예시

 

6. @ParameterizedTest(Junit5 User Guide(공식 문서) - 2.16. Parameterized Tests)

기존 junit4에서의 테스트 메소드는 파라미터를 받을 수 없는 구조로 구현되어 있었습니다. 따라서 테스트 코드 작성시 내부에서 테스트 입력 값은 메소드 내부에서 하드코딩해 생성하거나 @BeforeClass, @Before를 통해 값을 생성하였습니다.

Junit5 부터는 @ParameterizedTest를 통해 테스트 데이터를 어노테이션을 통해 파라미터값으로 입력받을 수 있습니다.

 

대표 어노테이션

  • @CsvSource: Csv 파일과 같은 값으로 테스트 파라미터를 받을 수 있음
  • @ValueSource: type을 지정해 해당 타입의 값을 파라미터로 정의할 수 있음
  • @EnumSource: Enum 클래스를 정의 또는 인용하여 테스트 가능
  • @MethodSource: 메소드 결과 값으로 반환된 결과를 사용할 수 있음(외부 메소드도 적용 가능)
    • 주의 사항: 반드시 인자의 stream을 만들어야 함

해당 어노테이션들은 테스트 수행시 명시적으로 또는 암시적으로 사용할 수 있는 기능이 존재합니다.

 

암시적 선언 예시

@ParameterizedTest
@ValueSource(strings = "42 Cats")
void testWithImplicitFallbackArgumentConversion(Book book) {
  assertEquals("42 Cats", book.getTitle());
}

명시적 선언 예시

@ParameterizedTest
@ValueSource(strings = "42 Cats")
void testWithImplicitFallbackArgumentConversion(String title) {
  assertEquals("42 Cats", title);
}

이제 NotificationServiceStatusTest를 통해 해당 테스트의 예시를 들어보겠습니다. 해당 코드 또는 아래 내용을 참고해주시면 감사하겠습니다.

기존 코드 (변경 전)

public class NotificationServiceStatusTest {


    @Test
    public void test_collector_status_false() {
        assertFalse(new NotificationServiceStatus(WRONG_DOMAIN, NotificationService.COLLECTOR).check());
    }

    @Test
    public void test_file_status_true() {
        assertTrue(new NotificationServiceStatus(conditionConfiguration.getStatsApiUrl(), NotificationService.FILE).check());
    }

    @Test
    public void test_file_status_false() {
        assertFalse(new NotificationServiceStatus(WRONG_DOMAIN, NotificationService.FILE).check());
    }

    @Test
    public void test_webHook_status_true() {
        assertTrue(new NotificationServiceStatus(conditionConfiguration.getStatsApiUrl(), NotificationService.WEBHOOK).check());
    }

    @Test
    public void test_webHook_status_false() {
        assertFalse(new NotificationServiceStatus(WRONG_DOMAIN, NotificationService.WEBHOOK).check());
    }

    @Test
    public void test_tag_status_true() {
        assertTrue(new NotificationServiceStatus(conditionConfiguration.getStatsApiUrl(), NotificationService.TAG).check());
    }

    @Test
    public void test_tag_status_false() {
        assertFalse(new NotificationServiceStatus(WRONG_DOMAIN, NotificationService.TAG).check());
    }

}

실행 결과 (변경 전)


변경 후

해당 코드는 모두 NotificationService enum 클래스의 값으로 테스트를 수행하므로 @EnumSource를 이용한 테스트로 통합하였습니다.

public class NotificationServiceStatusTest{

    @ParameterizedTest
    @EnumSource(NotificationService.class)
    @DisplayName("NotificationServiceStatus Status check using NotificationService - SUCCESS")
    public void test_notificationServiceStatus_true(NotificationService notificationService) {
        assertTrue(new NotificationServiceStatus(conditionConfiguration.getStatsApiUrl(), notificationService).check());
    }

    @ParameterizedTest
    @EnumSource(NotificationService.class)
    @DisplayName("NotificationServiceStatus Status check using NotificationService and Wrong Domain - FAIL")
    public void test_notificationServiceStatus_false(NotificationService notificationService) {
        assertFalse(new NotificationServiceStatus(WRONG_DOMAIN, notificationService).check());
    }
}

 

실행 결과(변경 후)

 

7. @MethodSource(Junit5 User Guide(공식 문서) - 2.16. Parameterized Tests)

@ParameterizedTest의 연장선상에 있는 기능입니다. @MethodSource을 사용할 경우 호출한 메소드를 테스트 메소드의 파라미터로 받을 수 있게 됩니다.

주의사항으로서는 무조건 Stream 객체로 반환해야 된다는 점이 있습니다.

 

예시

@ParameterizedTest
@MethodSource("statsStatusSuccessDomainList")
@DisplayName("NotificationServiceStatus Status check - SUCCESS(basic, add last slash, HTTP protocol)")
public void test_stats_status_true(NotificationServiceStatus notificationServiceStatus) {
    assertTrue(notificationServiceStatus.check());
}

static Stream<NotificationServiceStatus> statsStatusSuccessDomainList() {
    return Stream.of(new NotificationServiceStatus(properties.getApiUrl(), NotificationService.STATS),
            new NotificationServiceStatus(properties.getApiUrl() + "/", NotificationService.STATS),
            new NotificationServiceStatus("http://" + properties.getApiUrl(), NotificationService.STATS));
}

 

8. @NullAndEmptySource(Junit5 User Guide(공식 문서) - 2.16. Parameterized Tests)

@ParameterizedTest의 연장선상에 있는 어노테이션입니다. 해당 어노테이션을 추가할 경우 Null과 빈값을 파라미터로 받을 수 있습니다.

각각 호출하고 싶은 경우 @NullSource 및 @EmptySource를 사용하시면 될 것 같습니다.

참고 예제는 코드를 통해 확인해 주시면 감사하겠습니다.

주의사항

inner class내에서 @BeforeAll을 적용할 경우 정상적으로 테스트코드가 적용되지 않는 오류가 발생했습니다. 오류 내용은 다음과 같습니다.

org.junit.platform.commons.JUnitException: @BeforeAll method 
'public void com.test.nhn.condition.master.ConditionTest.beforeClass() throws java.io.IOException'
must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

오류의 마지막 문구에 @TestInstance(Lifecycle.PER_CLASS) 적용시 static을 적용하지 않아도 된다는 문구가 존재하여 @TestInstance(Lifecycle.PER_CLASS)를 사용해 테스트 코드를 작성하였습니다.

 

결론

현재 적용한 기능 말고도 여러 기능이 있지만 예시로 든 프로젝트에서는 테스트 코드에 큰 복잡성이 없어서 모든 기능을 다 사용해보진 못한 것 같습니다.

 

테스트 코드에 junit5를 적용하면서 느낀 것은 다음과 같습니다.

  • @DisplayName을 통해 해당 테스트가 무엇을 수행하는지 명시적으로 작성할 수 있습니다.
    • 예제에는 맨 마지막에 SUCCESS, FAIL을 붙였는데 접두사로 사용하면 좀 더 괜찮지 않을까.. 싶습니다.
  • JunitParams(@ValueSource 등)을 통해 어떤 값을 테스트 인자로 사용하는지를 직관적으로 알 수 있습니다.
  • @Nested를 통해 테스트 그룹핑이 용이해졌습니다.
  • @DisabledIf와 같은 조건문을 통해 환경별/상황별 테스트 실행 유무를 관리할 수 있습니다. 

제가 사용한 Junit5의 어노테이션들외에도 다양한 추가 기능들에 대해서는 별첨. 미사용 기능에 추가작성 하였으니 테스트 프레임워크에 대한 관심이 높으신 분들께서는  junit5 dynamic test Junit5 User Guide(공식 문서)를 참고해주시면 감사하겠습니다.

 

저 또한 미사용 기능들에 대해 한 번씩은 사용해보기 위해 좀 더 복잡한 로직을 찾아 코드를 작성해보고 이를 공유해보도록 하겠습니다. (회사 코드를 바꿔가면서 진행해보려 합니다.)

 

읽어주셔서 감사합니다.

별첨. 미사용 기능

  • @TestFactory
  • @TestTemplate
  • @TestInstance
  • @Tag
  • @DisplayNameGeneration
  • @RegisterExtension
  • @Timeout
  • @TempDir

Reference

'기타 > 테스트' 카테고리의 다른 글

3. Junit 5  (0) 2023.05.01
1. 테스트 코드는 왜 만들까?  (0) 2023.04.08
2. Test Double  (0) 2023.03.25

오늘은 Java의 대표적인 테스트 프레임워크인 Junit5에 대해 자세하게 알아보는 시간을 가져보겠습니다.

 

공식 문서를 참고하면 junit5는 다음과 같이 정의할 수 있습니다.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

그렇다면 세 가지 구성 요소에 대해 알아볼까요?

 

Junit5의 3요소

Junit Platform

  • JVM 위에서 테스트 프레임워크가 실행되도록 기초를 제공
  • 플랫폼에서 실행되는 테스트 프레임워크를 개발하기 위한 테스트 엔진 API를 정의
  • 플랫폼을 시작할 수 있는 콘솔 런처를 제공(command line 기반)

JUnit Jupiter

  • 테스트를 위한 프로그래밍 모델과 확장 모델의 조합
  • JUnit 5에서 테스트를 작성하고 확장할 수 있도록 하는 Jupiter 기반 테스트 엔진

JUnit Vintage

  • JUnit 3과 JUnit 4 를 지원하기 위한 테스트 엔진 제공

 

요약하자면 Junit5는 새로운 기능을 Junit Platform + JUnit Jupiter으로 제공하며 기존 junit3와 junit4를 호환시키는 JUnit Vintage를 포함한 Junit의 차세대 테스팅 프레임워크입니다.

 

 

Junit5 호환 버전

  • Junit4: JDK5 버전 이상
  • Junit5: JDK8 버전 이상

 

Junit5의 특징

1. JUnit Vintage가 junit3과 junit4를 호환합니다.

말 그대로 junit3와 junit4를 호환하기 때문에 기존에 junit을 이용하고 있던 사용자들도 부담없이 버전을 업그레이드 할 수 있습니다.

 

2. 좀 더 직관적인 annotation

테스트 코드 어노테이션이 보다 더 직관적으로 변경되었습니다.
기존 junit4에서 사용하던  몇몇 annotation의 의미에 대해 모호하게 느낄 수 있었는데 해당 부분이 개선되었습니다.

 

다음은 Junit4와 Junit5에서 제공하는 동일한 기능의 어노테이션을 비교한 자료입니다.

 

Feature JUnit4 vs Junit5

메소드 기능 설명 Junit4 Junit5
Declare a test method @Test @Test
Execute before all test methods in the current class @BeforeClass @BeforeAll
Execute after all test methods in the current class @AfterClass @AfterAll
Execute before each test method @Before @BeforeEach
Execute after each test method @After @AfterEach
Disable a test method/class @Ignore @Disabled
Test factory for dynamic tests N/A @TestFactory
Nested tests NA @Nested
Tagging and filtering @Category @Tag
Register custom extensions N/A @ExtendWith

해당 Feature와 어노테이션을 비교할 경우, 각각의 표현에 있어 Junit5가 보다 더 직관적임을 알 수 있습니다.

 

3. Reflection에 의존하지 않아도 됩니다.

Junit4에서는 plugin과 ide의 통합 지원이 없었기 때문에 reflection에 의존해야만 했습니다.
Junit5 부터는 JUnit Platform이라는 테스트코드 통합 지원 플랫폼이 제공되어 테스트 코드 실행이 해당 플랫폼 위에서 수행됩니다.

 

4. 다양한 기능의 추가

Junit4에서 있었던 기능들이 신규로 추가/개선되어 제공되고 있습니다. Junit5 도입시 보다 더 다양한 기능으로 테스트 코드를 작성할 수 있습니다. (다음 글에서 작성 예정)

 

5. private 메소드 테스트 허용

junit4에서는 모든 test method는 public 접근 제한자를 선언해야만 이용이 가능하였습니다. Junit5에서는 Reflection을 통해 자동으로 테스트 클래스를 검색하여 테스트 수행이 가능해집니다.

 


결론

개발자가 테스트 코드를 작성하기 위해 Junit을 도입할 경우 Junit5를 도입해야만 합니다. 이유는 다음과 같습니다.

 

  • junit3, junit4를 계속 사용하셔도 호환되기 때문에 개발에 크게 문제되지 않습니다.
  • 개발자는 기존 코드를 유지한 채로 신규 테스트코드에 한하여 junit5를 도입할 수 있습니다.

다음과 같은 이유로Junit5 도입에 있어 두려움을 크게 가질 필요가 없을 것이라고 생각됩니다. 여러분도 한 번 junit5 도입을 적극적으로 고려하는건 어떨까요? 다음 글에서는 junit5 기능에 대해 좀 더 상세히 다루는 시간을 가져보겠습니다.

 

감사합니다.

Reference

https://howtodoinjava.com/junit5/junit-5-vs-junit-4/

'기타 > 테스트' 카테고리의 다른 글

4. How to Migrate and Using Junit 5  (0) 2023.05.01
1. 테스트 코드는 왜 만들까?  (0) 2023.04.08
2. Test Double  (0) 2023.03.25

github trending으로 보는 최근 핫한 오픈 소스 프로젝트를 공유드립니다.

Hot Trend는 AI가 지배했지만 그 중에서 한 번 보시면 신기하고 흥미로울법한 기술들이 많아 한 번 정리해보았습니다.

github trend는 다음 링크를 참고하시길 바랍니다.

Auto-GPT

AFFiNE.PRO

  • Notion & Miro 대체 차세대 협업 툴
  • 무료 오픈소스

FerretDB

  • FerretDB는 PostgreSQL을 데이터베이스 엔진으로 사용하여 MongoDB 6.0+ 유선 프로토콜 쿼리를 SQL로 변환하는 프록시 오픈 소스

Magic Copy

Animated Drawings

  • 멈춰있는 그림에 애니메이션을 넣을 수 있는 오픈 소스

stable-diffusion-webui

https://github.com/AUTOMATIC1111/stable-diffusion-webui

  • stable diffusion 모델 사용을 쉽게 할 수 있는 브라우저 제공

LLAMA

이로 인해... ai 개발자들의 수많은 repo를 생성하기 시작하며 해당 모델에 대한 사용이 전세계적으로 뜨거운 것 같습니다.

최근 1달 hot trend

LLAMA 관련 프로젝트가 상위 5개 중 3개나 되네요..

'기타' 카테고리의 다른 글

주니어 개발자가 팀에서 성장하는 방법 - (1)  (0) 2023.06.23
resilence4j - 개념  (0) 2023.04.17
ChatGPT 제대로 사용하기  (0) 2023.03.30
8 Tips To Grow Your Skills as a Software Engineer  (0) 2023.03.28
개발자로 거듭나기  (0) 2023.03.28

resilience4j란?

함수형 프로그래밍을 위해 고안된 내결함성(fault tolerance) 라이브러리입니다. Resilience4j는 Circuit Breaker, Rate Limiter, Retry 또는 Bulkhead 등 서비스 장애 대응을 위한 다양한 기능을 제공하고 있습니다. resilience4j는 java8을 통해 기능 개발 됐으며  함수 인터페이스, 람다 식과 같은 고차 함수를 제공합니다.

지원 버전

1.x: Java 8 이상
2.x: Java 17 이상

핵심 모듈

  • resilience4j-circuitbreaker: 회로 차단 (Circuit breaking)
  • resilience4j-ratelimiter: 속도 제한 (Rate limiting)
  • resilience4j-bulkhead: 격벽 (Bulkheading)
  • resilience4j-retry: 자동 재시도(동기화 및 비동기화) (Automatic retrying (sync and async))
  • resilience4j-timelimiter: 시간 초과 처리 (Timeout handling)
  • resilience4j-cache: 결과 캐싱 (Result caching)

핵심 모듈 중 circuit breaker, ratelimiter, bulkhead에 대한 설명만 작성하겠습니다. 각 라이브러리에 대한 추가 설명이 궁금하신 분들은 resilence4j github을 참고해주시기 바랍니다.

resilience4j-circuitbreaker

Circuit Breaker란?

최근의 Web 및 App의 백엔드 서버 시스템은 여러 개의 서비스가 API나 RPC로 연결된 네트워크로 구성되어 있습니다. 만약 이 네트워크 중 하나가 갑자기 전혀 응답하지 않게 되는 상황이 발생하면 어떻게 될까요? 동작하지 않는 서비스 접속 시 타임아웃될 때까지 차단되어, 의존성이 있는 서비스까지 연쇄적으로 멈출 가능성이 있습니다. 만약 네트워크 전체를 파악하고 있는 사람이 아무도 없다면, 근본적인 원인이 어느 서비스에 있는지를 알아내기까지 시간이 걸리게 됩니다.

 

https://engineering.linecorp.com/en/blog/circuit-breakers-for-distributed-services

 

우리는 이러한 연쇄적인 장애 발생을 막아야 합니다. 적어도 가장 중요한 기능에 영향이 가지 않도록 해야 하는데, 그러기 위해서는 장애가 발생한 서비스에 대한 접속 차단이 필요합니다.

 

https://engineering.linecorp.com/en/blog/circuit-breakers-for-distributed-services

이 시스템을 자동화한 것이 바로 Circuit Breaker입니다.

참고: 마틴 파울러 Circuit Breaker 글

 

Circuit Breaker란, 원격 접속의 성공/실패를 카운트하여 에러율(failure rate)임계치를 넘어섰을 때 자동적으로 접속을 차단하는 시스템입니다. Circuit Breaker상태 머신(State Machine)으로 나타낼 수 있습니다. 접속 성공과 실패 이벤트가 발생할 때마다 내부 상태를 업데이트하여 자동적으로 장애를 검출하고 복구 여부를 판단합니다.

 

https://engineering.linecorp.com/en/blog/circuit-breakers-for-distributed-services

Circuit Breaker 상태 종류

CLOSED

초기 상태입니다. 모든 접속은 평소와 같이 실행됩니다.

OPEN

에러율이 임계치를 넘어서면 OPEN 상태가 됩니다. 모든 접속은 차단(fail fast)됩니다.

HALF_OPEN

OPEN 후 일정 시간이 지나면 HALF_OPEN 상태가 됩니다. 접속을 시도하여 성공하면 CLOSED, 실패하면 OPEN으로 되돌아갑니다.

resilience4j-ratelimiter

Rate Limit란?

서버는 제공할 수 있는 자원이 한정되어 있기 떄문에 특정 임계치까지만 클라이언트의 요청을 허용하는 정책을 의미합니다. 제한치를 넘어선 요청에 대한 요청을 거부하거나 Queue로 만들어 실행 기능 제공합니다.

 

https://etloveguitar.tistory.com/126

resilience4j-bulkhead

Bulkhead란?

동시 요청 수를 제한하는 기법입니다. 요청 수에 도달한 이후 요청에 대해서는 예외처리를 진행합니다.

Resilence4j bulkhead pattern

SemaphoreBulkhead

동시 요청 수를 제한을 두고 요청 수에 도달한 이후 요청에 대해서는 BulkheadFullException이 발생합니다.

FixedThreadPoolBulkhead

시스템 자원과 별도로 thread pool을 설정하고 설정된 thread pool은 서비스를 제공하기 위한 용도로만 사용합니다. 그리고 thread pool과 별도로 waiting queue를 설정할 수 있습니다. 만약 thread pool과 waiting queue 가 full 인 경우 BulkheadFullException이 발생합니다.

resilience4j-timelimiter

TimeLimiter는 future supplier의 time limit을 정하는 API입니다.

결론

Resilience4j는 서비스 장애 대응을 위해 만들어진 오픈 소스 라이브러리입니다. 해당 라이브러리에서는 다양한 기능을 제공합니다.

  • resilience4j-circuitbreaker: 회로 차단(Circuit breaking)
  • resilience4j-ratelimiter: 속도 제한(Rate limiting)
  • resilience4j-bulkhead: 격벽(Bulkheading)
  • resilience4j-retry: 자동 재시도(동기화 및 비동기화)(Automatic retrying (sync and async))
  • resilience4j-timelimiter: 시간 초과 처리(Timeout handling)
  • resilience4j-cache: 결과 캐싱(Result caching)

각 서비스 예시 코드는 다음 글에서 작성해보도록 하겠습니다. 감사합니다.

Reference

https://en.wikipedia.org/wiki/Circuit_breaker
https://github.com/resilience4j/resilience4j
https://cheese10yun.github.io/resilience4j-basic/
https://engineering.linecorp.com/en/blog/circuit-breakers-for-distributed-services

이 글은 https://yozm.wishket.com/magazine/detail/1964을 읽고 내용 및 생각을 정리한 글입니다.

 

테스트 코드는 왜 만들까? | 요즘IT

지금 돌이켜 생각하면 부끄러운 일이지만, 처음 테스트 코드를 마주했을 때 든 생각은 '왜 귀찮은 테스트 코드를 만들어야 하는 걸까?'였습니다. 물론 지금은 테스트 코드의 중요성을 깨달아 열

yozm.wishket.com

해당 본문이 테스트 코드에 대한 정리가 잘 되어 있어서 해당 글을 정독하시는걸 추천드립니다.

 

 

테스트 코드란?

  • 소프트웨어의 기능과 동작을 테스트하는데 사용되는 코드. 
  • 개발자가 작성한 코드를 실행하고 예상된 결과가 나오는지 확인하는데 사용
  • 테스트 코드의 종류로는 단위 테스트. 통합 테스트, 시스템 테스트, 사용자 인수 테스트 등이 존재

개발자가 주로 다루는 테스트 코드

단위 테스트 (Unit Test): 개별적인 코드 단위(ex) 함수,메서드)가 의도한대로 작동하는지 확인하는 과정

통합 테스트(Integration Test): 서로 다른 모듈들 간의 상호작용을 테스트하는 과정.

 

테스트 코드를 작성하는 이유

  • 테스트 코드를 작성하면 요구사항의 기능적인 항목들을 정리하는 경험을 가질 수 있다.
  • 테스트 코드를 통해 기능 수정간 발생할 수 있는 에러 상황에서 보호받을 수 있다.
  • 테스트 코드를 작성하면서 복잡한 의존성에 대한 고민을 하게 되고, 더 나은 코드를 작성할 수 있게 된다.

 

내가 생각하는 테스트 코드란 ?

테스트 케이스 작성간 발생할 수 있는 단점은 테스트 코드에 대한 이해도가 부족한 개발자가 작성한 테스트 코드 뿐이라고 생각합니다. 올바른 테스트 코드 작성은 서비스의 코드 품질을 높이며 유지보수에 큰 도움을 줄 수 있습니다. 그렇기에 테스트 코드의 올바른 이해가 작성 이전에 진행되어야 된다고 생각하고 저 또한 더 좋은 테스트 코드 작성을 위해 많은 공부를 하고 있습니다.

 

TDD(Test Driven Development) 방식도 존재하지만 이러한 방식에 대해서는 주니어 개발자의 입장에서 다소 회의적입니다. '숙련된 개발자일수록 TDD가 용이해 질 것이라 생각하지만 개발 순서의 차이일 뿐 동일한 결과물이 나오지 않을까...?' 또는 '오히려 시간이 더 소요되지 않을까..?' 하는 생각도 들고요.

주니어 개발자들은 개발을 진행하고 TC를 작성하며 더 좋은 코드를 향해 리팩토링하는 과정을 거치는 것이 보다 더 좋은 코드를 작성할 수 있는 방법이 아닐까 싶습니다. 어느 정도 코드 작성에 숙련되었다 생각된다면 TDD에 대한 고려도 필요하겠지만요.

 

위의 의견을 요약하면 내가 만드는 기능이 어떤 요구사항이 존재하는지, 어떤 케이스가 예외 사항이 될 수 있을지 정리하고 테스트 코드를 더 간결하게 짜기 위해 서비스 로직 구성을 고려해야 합니다. 그러다 보면 어느새 좋은 코드(clean code)를 작성하는 개발자가 되어 있을 것이라고 생각한다는 의견을 남기며 글을 마무리하겠습니다.

 

다음 글에서는 테스트 기법에 대해 다루는 시간을 가져보겠습니다.

읽어주셔서 감사합니다.

'기타 > 테스트' 카테고리의 다른 글

4. How to Migrate and Using Junit 5  (0) 2023.05.01
3. Junit 5  (0) 2023.05.01
2. Test Double  (0) 2023.03.25

+ Recent posts