블로그
전체 62025. 03. 30.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 4주차 발자국
강의 요약섹션 7. Mock을 마주하는 자세Test DoubleDummy: 아무 것도 하지 않는 깡통 객체Fake: 단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체 (ex. FakeRepository)Stub: 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체 그 외에는 응답하지 않습니다. (상태 검증)Spy: Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있습니다.Mock: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체 (행위 검증) 순수 Mockito로 검증해보기 @Mock private MailSendClient mailSendClient; @Mock private MailSendHistoryRepository mailSendHistoryRepository; @InjectMocks private MailService mailService; @DisplayName("메일 전송 테스트") @Test void sendMail() { // given when(mailSendClient.sendMail(anyString(), anyString(), anyString(), anyString())) .thenReturn(true); // when boolean result = mailService.sendMail("", "", "", ""); // then assertThat(result).isTrue(); verify(mailSendHistoryRepository, times(1)).save(any(MailSendHistory.class)); } BDDMockito@Mock private MailSendClient mailSendClient; @Mock private MailSendHistoryRepository mailSendHistoryRepository; @InjectMocks private MailService mailService; @DisplayName("메일 전송 테스트") @Test void sendMail() { // given BDDMockito.given(mailSendClient.sendMail(anyString(), anyString(), anyString(), anyString())) .willReturn(true); // when boolean result = mailService.sendMail("", "", "", ""); // then assertThat(result).isTrue(); verify(mailSendHistoryRepository, times(1)).save(any(MailSendHistory.class)); } Classicist장점:실제 객체를 사용하므로 테스트가 자연스럽고 직관적입니다.설계 변경 시 테스트가 깨질 가능성이 적어 리팩토링이 용이합니다.도메인 모델 중심으로 설계가 진행되어 UI와 비즈니스 로직이 분리됩니다.단점:복잡한 협력자와의 통합 테스트에서 추가 작업이 필요할 수 있습니다.상태 검증만으로는 일부 동작 관련 오류를 놓칠 가능성이 있습니다. Mockis장점:모든 협력자를 Mock으로 대체하여 세부적인 동작을 명확히 검증 가능합니다.복잡한 협력자와의 상호작용을 쉽게 테스트할 수 있습니다.단점:구현 세부사항에 강하게 의존하여 리팩토링 시 테스트가 깨질 가능성이 높습니다.지나치게 세분화된 테스트는 통합 오류를 놓칠 수 있습니다. 섹션 8. 더 나은 테스트를 작성하기 위한 구체적 조언한 문단에 한 주제! 테스트 하나 당 목적은 하나여야 합니다.if, for 사용 지양 완벽하게 제어하기LocalDateTime.now() 사용 지양 테스트 환경의 독립성을 보장하자공유 변수와 테스트 목적이 아닌 메소드 사용 지양 한 눈에 들어오는 Test Fixture 구성하기Test Fixture테스트를 위해 원하는 상태로 고정시킨 일련의 객체 즉, given절에서 생성했던 모든 객체들을 의미BeforeEach, BeforeAll, AfterEach, AfterAll각 테스트가 픽스처의 내부 구현을 몰라도 테스트 내용을 이해하는 데 문제가 없을 경우에 사용해야 합니다.Given 절에서 SQL 사용 지양SQL로 Given 객체를 생성하면 테스트 코드의 가독성이 떨어지고, 무엇을 테스트하려는지 명확하지 않을 수 있습니다. 이는 테스트 목적을 파편화시키는 결과를 초래합니다. Test Fixture 클렌징 deleteAll()모든 엔티티를 하나씩 순차적으로 삭제합니다.내부적으로 for 루프를 돌며 각각의 엔티티에 대해 개별 DELETE 쿼리를 실행합니다.대량의 데이터를 삭제할 경우 성능이 저하됩니다. (100만 개의 데이터를 삭제하면 100만 번의 쿼리가 실행됩니다.) deleteAllInBatch()삭제할 모든 엔티티를 하나의 SQL DELETE 쿼리로 처리합니다.DELETE FROM table_name과 같은 단일 쿼리를 실행하여 성능이 훨씬 뛰어납니다.대량 데이터 삭제 시에도 단일 쿼리로 처리되므로 성능이 우수합니다. (동일한 데이터 삭제 시 단 한 번의 쿼리만 실행됩니다)@ParameterizedTest미리 정의된 값(ProductType)을 사용해 반복적으로 테스트를 실행합니다.@DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.") @CsvSource({"HANDMADE,false", "BOTTLE,true", "BAKERY,true"}) @ParameterizedTest void containsStockType(ProductType productType, boolean expected) { // given-when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isEqualTo(expected); }private static Stream provideProductTypesForCheckingStockType() { return Stream.of( Arguments.of(ProductType.HANDMADE, false), Arguments.of(ProductType.BOTTLE, true), Arguments.of(ProductType.BAKERY, true) ); } @DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.") @MethodSource("provideProductTypesForCheckingStockType") @ParameterizedTest void containsStockType(ProductType productType, boolean expected) { // given-when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isEqualTo(expected); } @DynamicTest런타임에 동적으로 테스트 케이스를 생성하여 실행합니다.@DisplayName("재고 차감 시나리오") @TestFactory Collection stockDeductionDynamicTest() { // given Stock stock = Stock.create("001", 1); return List.of( DynamicTest.dynamicTest("재고를 주어진 개수 만큼 차감할 수 있다.", () -> { // given int quantity = 1; // when stock.deductQuantity(quantity); // then assertThat(stock.getQuantity()).isZero(); }), DynamicTest.dynamicTest("재고보다 많은 수의 수량으로 차감 시도하는 경우 예외가 발생한다.", () -> { // given int quantity = 1; // when-then assertThatThrownBy(() -> stock.deductQuantity(quantity)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("차감할 재고 수량이 없습니다."); }) ); } 수행 환경 통합하기@ActiveProfiles("test"),@SpringBootTest 어노테이션을 사용하는 추상 클래스를 생성하여 상속받는 구조로 구현을 해야 실행 횟수가 줄어듭니다. private method test작성하지 않지는 않고, 객체를 분리할 시점인지를 확인해야 합니다. 테스트에서만 필요한 코드만들어도 되지만, 보수적으로 접근해야 합니다. 섹션 9. Appendix학습 테스트잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위해 작성하는 테스트여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 보다 구체적인 동작과 기능을 학습할 수 있습니다.관련 문서만 읽는 것보다 훨씬 재미있게 학습할 수 있습니다. Spring REST Docs테스트 코드를 통한 API 문서 자동화 도구API 명세를 문서로 만들고 외부에 제공함으로써 협업을 원활하게 합니다.기본적으로 AsciiDoc을 사용하여 문서를 작성합니다.REST Docs장점:테스트를 통과해야 문서가 만들어집니다. (신뢰도가 높다.)프로덕션 코드에 비침투적입니다.단점:코드 양이 많습니다.설정이 어렵습니다.Swagger장점:적용이 쉽습니다.문서에서 바로 API 호출을 수행해볼 수 있습니다.단점:프로덕션 코드에 침투적입니다.테스트와 무관하기 때문에 신뢰도가 떨어질 수 있습니다. 미션 (DAY16)Layered Architecture의 특징 및 테스트 작성결과: https://inf.run/JT9zG미션 (DAY18)@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이 및 @BeforeEach, given절, when절 적절한 배치 결과:https://inf.run/HxEwT발작국 4주차 회고이번 주는 테스트 코드 작성과 관련된 다양한 어노테이션의 차이를 이해하고 이를 실무에 어떻게 적용할 수 있을지 고민하는 시간이었습니다. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks의 차이점을 명확히 이해하는 데 집중했으며 각각의 어노테이션이 어떤 상황에서 적합한지 학습할 수 있었습니다. 더불어 테스트 코드를 작성하며 효율성을 높이는 방법에 대해서도 많은 것을 배웠습니다. 특히 Mock 객체를 활용해 데이터베이스나 네트워크 호출과 같은 외부 시스템 의존성을 줄이는 것이 얼마나 중요한지 체감할 수 있었으며 테스트 코드를 효과적으로 작성하는 방법에 대해 다시 한번 깊이 생각하게 된 의미 있는 시간이었습니다.
백엔드
・
워밍업클럽
2025. 03. 27.
0
워밍업 클럽 3기 BE 클린코드&테스트 - Day18 미션
@Mock목적: 객체를 단순히 Mocking하여 테스트에서 사용할 수 있도록 합니다.사용 범위: Spring 컨텍스트와 무관한 일반적인 단위 테스트에 사용됩니다.특징:실제 구현체를 대체하는 Mock 객체를 생성합니다.Spring 컨텍스트를 로드하지 않으며 독립적인 테스트 환경에서 사용됩니다.사용 시점: 단일 클래스와 메서드의 동작을 격리하여 테스트할 때 적합합니다예시:@Mock private Dependency dependency; @InjectMocks private SomeService someService; @MockBean목적: Spring 컨텍스트 내의 Bean을 Mock으로 대체하거나 새로 추가합니다.사용 범위: 통합 테스트에서 사용되며 Spring 컨텍스트를 로드하는 경우에 적합합니다.특징:Spring 애플리케이션 컨텍스트에 Mock 객체를 추가하거나 기존 Bean을 대체합니다.Spring Boot의 @SpringBootTest와 함께 사용됩니다.사용 시점: Spring 컨텍스트가 필요한 통합 테스트에서 특정 Bean을 Mock으로 대체할 때 적합합니다예시:@SpringBootTest public class SomeServiceIntegrationTest { @MockBean private Dependency dependency; @Autowired private SomeService someService; } @Spy목적: 객체를 Spy로 생성하여 일부 메서드는 실제 구현을 호출하고, 일부는 Mocking할 수 있도록 합니다.사용 범위: 일반적인 단위 테스트에서 사용됩니다.특징:객체의 실제 구현을 유지하면서 특정 메서드만 Mocking하거나 검증할 수 있습니다.독립적인 테스트 환경에서 사용됩니다.사용 시점: 객체의 일부 동작은 실제로 실행하며 특정 메서드만 Mocking해야 할 때 적합합니다.예시:// 서비스 로직 public boolean sendMail(String fromEmail, String toEmail, String subject, String contents) { boolean result = mailSendClient.sendMail(fromEmail, toEmail, subject, contents); if (result) { mailSendHistoryRepository.save(MailSendHistory.builder() .fromEmail(fromEmail) .toEmail(toEmail) .subject(subject) .contents(contents) .build() ); mailSendClient.a(); mailSendClient.b(); mailSendClient.c(); return true; } return false; } // 테스트 코드 @Spy private MailSendClient mailSendClient; @DisplayName("메일 전송 테스트") @Test void sendMail() { // given (mailSendClient.sendMail의 sendMail만 Mocking) doReturn(true) .when(mailSendClient) .sendMail(anyString(), anyString(), anyString(), anyString()); } @SpyBean목적: Spring 컨텍스트 내의 Bean을 Spy로 대체하거나 추가합니다.사용 범위: 통합 테스트에서 사용되며 Spring 컨텍스트를 로드하는 경우에 적합합니다.특징:Spring 애플리케이션 컨텍스트 내의 기존 Bean을 Spy로 감싸거나 새로 추가합니다.SpyBean은 모든 메서드가 기본적으로 실제 구현을 호출하지만 특정 메서드는 Mocking할 수 있습니다.사용 시점: Spring 컨텍스트 내에서 Bean의 동작을 검증하거나 일부 메서드를 변경해야 할 때 적합합니다.예시:@SpringBootTest public class MyServiceTest { @SpyBean private MyDependency myDependency; @Autowired private MyService myService; } @InjectMocks목적: @Mock 또는 @Spy로 생성된 Mock 객체를 테스트 대상 클래스에 주입합니다.사용 범위: 단위 테스트에서 사용됩니다.특징:생성자 주입, 세터 주입, 필드 주입 방식으로 Mock 객체를 자동으로 주입합니다.테스트 대상 클래스 인스턴스를 생성하고 필요한 의존성을 주입합니다.사용 시점: 여러 의존성을 가진 클래스를 테스트할 때, 해당 의존성을 자동으로 주입해야 할 경우 적합합니다.예시:@Mock private Dependency dependency; @InjectMocks private SomeService someService; // dependency가 자동으로 someService에 주입됩니다.@BeforeEach, given절, when절 적절한 배치@BeforeEach void setUp() { 1-1, 2-1. 사용자 생성에 필요한 내용 준비 1-2, 2-2. 사용자 생성 1-3, 2-3. 게시물 생성에 필요한 내용 준비 1-4, 2-4. 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-1. 사용자1 생성에 필요한 내용 준비 3-2. 사용자1 생성 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }
백엔드
・
워밍업클럽
2025. 03. 25.
0
워밍업 클럽 3기 BE 클린코드&테스트 - Day16 미션
Layered ArchitectureLayered Architecture는 소프트웨어의 기능을 계층별로 분리하여 설계하는 방식으로 모듈화와 관심사의 분리를 통해 유지보수성과 확장성을 높이는 데 중점을 둡니다. 각 레이어는 특정 역할을 수행하며 상위 및 하위 레이어와 제한적으로 상호작용합니다. 아래는 주요 레이어의 특징과 테스트 작성법을 정리한 내용입니다. Presentation Layer특징사용자 인터페이스(UI)와 사용자 상호작용을 담당합니다.RESTful API를 통해 클라이언트와 데이터를 주고받으며 요청을 처리하고 응답을 반환합니다.비즈니스 로직이나 데이터 저장소에 대한 세부사항은 알 필요가 없습니다. 테스트REST 컨트롤러가 올바르게 요청을 처리하고 응답을 반환하는지 검증합니다.@WebMvcTest, @MockitoBean, MockMvc를 사용하여 HTTP 요청/응답 시뮬레이션이 가능합니다.Presentation Layer와 Business Layer 간의 상호작용이 예상대로 이루어지는지 확인합니다.실제 API 호출을 통해 전체 흐름을 검증할 수 있습니다.(Postman 활용)전체 애플리케이션이 사용자 관점에서 올바르게 동작하는지 확인합니다. (API 흐름 검증) Business Layer특징애플리케이션의 핵심 비즈니스 로직과 규칙을 처리합니다.데이터 변환, 계산, 유효성 검증 등을 수행하며 Persistence Layer와 상호작용합니다.외부 API 호출이나 복잡한 비즈니스 프로세스를 관리합니다. 테스트비즈니스 로직이 올바르게 작동하는지 검증합니다.(예: 주문 상태 변경, 합계 계산 등)Persistence Layer와 상호작용하며 데이터 흐름이 올바르게 이루어지는지 확인합니다.Spring Boot Test를 사용해 실제 환경과 유사한 통합 테스트를 수행합니다. Persistence Layer특징데이터 저장 및 검색을 담당하며 데이터베이스와 직접 상호작용합니다.DAO(Data Access Object) 또는 Repository 패턴을 사용하여 데이터 접근 로직을 캡슐화합니다.비즈니스 로직에서 데이터베이스 세부사항을 숨기고 추상화된 인터페이스를 제공합니다. 테스트DAO/Repository 클래스의 메서드가 올바르게 동작하는지 검증합니다.H2와 같은 In-Memory 데이터베이스를 사용하여 실제 DB 연결 없이 테스트를 수행합니다.@DataJpaTest 어노테이션 활용
백엔드
・
워밍업클럽
2025. 03. 23.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국
강의 요약섹션 6. Spring & JPA 기반 테스트Presentation Layer외부 세계의 요청을 가장 먼저 받는 계층파라미터에 대한 최소한의 검증을 수행한다.Business Layer비즈니스 로직을 구현하는 역할Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)을 통해 비즈니스 로직을 전개시킨다.트랜잭션을 보장해야 한다.Persistence LayerData Access의 역할비즈니스 가공 로직이 포함되어서는 안 된다. Data에 대한 CRUD에만 집중한 레이어 미션 (DAY11)스터디카페 단위 테스트 작성결과:https://github.com/iPhone-design/readable-code/pull/2 과정InputHandler: 테스트 코드 작성, SCANNER 필드 StudyCafeIOHandler 클래스로 이동LockerPassFileReader: 테스트 코드 작성SeatPassFileReader: 테스트 코드 작성StudyCafeSeatPass, StudyCafeSeatPasses, StudyCafeLockerPass, StudyCafeLockerPasses: equals, hashCode 메소드 추가 아쉬운점로직이 간단한 부분은 테스트 코드를 쉽게 작성할 수 있었지만, 로직이 복잡한 부분에서는 테스트 코드 작성이 어려웠습니다. 특히 테스트를 위해 public이나 private 접근제어자를 수정해야 하는지 또는 기존 서비스 로직도 변경을 해야 하는지에 대해 고민이 많았습니다. 추후 코치님의 코드 리뷰를 통해 새롭게 배워 보완을 해야 할 것 같습니다.발작국 3주차 회고이번 강의를 통해 다양한 테스트 코드를 작성하는 습관을 길러야겠다는 생각이 들었습니다. 앞으로는 복잡한 로직을 작은 단위로 나누어 테스트하기 쉽게 만들고 처음부터 테스트 가능성을 고려한 설계를 할 수 있도록 노력해야겠다고 다짐했습니다. 또한 정상적인 경우뿐만 아니라 예외 상황이나 엣지 케이스까지 포함한 테스트 코드를 작성해 코드의 안정성을 높이는 데 집중해야겠다고 느꼈습니다. 아직 부족한 점이 많지만 이번 테스트 코드 작성 경험을 바탕으로 더 나은 테스트 코드 작성 방법과 설계를 고민하며 발전해 나가고 싶습니다.
백엔드
・
워밍업클럽
2025. 03. 16.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 2주차 발자국
Reable Code 강의 요약섹션 6. 코드 다듬기주석의 양면성주석이 많다는 것은, 그만큼 비즈니스 요구사항을 코드에 잘 못 녹였다는 이야기.코드를 설명하는 주석을 쓰면, 코드가 아니라 주석에 의존한다. 주석에 의존하여 코드를 작성하면, 적절하지 않은 추상화 레벨을 갖게 되어 낮은 품질의 코드가 만들어진다.우리가 가진 모든 표현 방법을 총동원해 코드에 의도를 녹여내고, 그럼에도 불구하고 전달해야 할 정보가 남았을 때 사용하는 주석변수와 메서드의 나열 순서변수는 사용하는 순서대로 나열한다.공개 메서드를 상단에 배치하는 것을 선호하는 편.공개 메서드끼리도 기준을 가지고 배치하는 것이 좋다.상태 변경 >> 판별 >= 조회 메서드비공개 메서드는, 공개 메서드에서 언급된 순서대로 배치한다.공통으로 사용하는 메서드라면, (가장 하단과 같은) 적당한 곳에 배치한다.패키지 나누기패키지는, 문맥으로써의 정보를 제공할 수 있다.패키지를 쪼개지 않으면 관리가 어려워진다.패키지를 너무 잘게 쪼개도 마찬가지로 관리가 어려워진다.처음 만들 때부터 잘 고민해서 패키지를 나눠놓는 것이 제일 좋다.IDE의 도움 받기코드 포맷 정렬 (Ctrl + Alt + L)코드 품질 (Sonarlint)포맷 규칙 (.editorconfig) 섹션 7. 리팩토링 연습 스터디 카페 이용권 선택 시스템리팩토링 포인트추상화 레벨객체로 묶어볼만한 것은 없는지객체지향 패러다임에 맞게 객체들이 상호 협력하고 있는지SRP : 책임에 따라 응집도 있게 객체가 잘 나뉘어져 있는지DIP : 의존관계 역전을 적용할만한 곳은 없는지일급 컬렉션리팩토링 한 단계마다, 그 이유를 설명할 수 있어야 한다. 섹션 8. 기억하면 좋은 조언들능동적 읽기복잡하거나 엉망인 코드를 읽고 이해하려 할 때, 리팩토링하면서 읽기공백으로 단락 구분하기메서드와 객체로 추상화 해보기주석으로 이해한 내용 표기하며 읽기핵심 목표는 우리의 도메인 지식을 늘리는 것. 그리고 이전 작성자의 의도를 파악하는 것.오버 엔지니어링필요한 적정 수준보다 더 높은 수준의 엔지니어링은탄환은 없다클린 코드도 은탄환이 아니다.모든 기술과 방법론은 적정 기술의 범위 내에서 사용되어야 한다. 미션 (Day7)지금까지 배운 내용을 기반으로 리팩토링 진행결과: https://github.com/iPhone-design/readable-code/pull/1 과정추상화 레벨 리팩토링Early return 리팩토링DIP 리팩토링 아쉬운점새로운 도메인을 도출하는 과정에서 많은 어려움을 겪었습니다. 도메인 모델을 명확히 정의하고 적절한 개념을 추출하는 작업이 예상보다 복잡하고 까다로워 리팩토링에 많은 시간이 소요되었습니다. 또한, 리팩토링 과정에서 새로운 버그가 유입되거나 기존 기능이 손상되는 등의 부작용도 발생하여 기대만큼 만족스러운 결과를 얻지 못한 점이 아쉬웠습니다. 특히, 도메인을 명확히 정의하고 코드의 책임을 명확히 분리하는 클린 코드의 기본 원칙과 사고방식을 더욱 깊이 이해하고 습관화할 필요가 있다고 생각합니다. 앞으로는 작은 단위로 점진적으로 리팩토링을 진행하고, 자동화된 테스트를 적극적으로 활용하여 코드의 품질과 안정성을 높이도록 노력해야겠다 느꼈습니다.Pratical Testing 강의 요약섹션 2. 테스트는 왜 필요할까?우리가 얻고자 하는 것빠른 피드백자동화안정감테스트 코드를 작성하지 않는다면변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야 한다.빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.테스트 코드가 병목이 된다면프로덕션 코드의 안정성을 제공하기 힘들어진다.테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.잘못된 검증이 이루어질 가능성이 생긴다.올바른 테스트 코드는자동화 테스트로 비교적 빠른 시간 안에 버그를 발견할 수 있고, 수동 테스트에 드는 비용을 크게 절약할 수 있다.소프트웨어의 빠른 변화를 지원한다.팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.가까이 보면 느리지만, 멀리 보면 가장 빠르다.테스트는 귀찮다. 귀찮지만 해야 한다. 섹션 3. 단위 테스트단위 테스트작은 코드 단위를 독립적으로 검증하는 테스트검증 속도가 빠르고, 안정적이다.JUnit 5단위 테스트를 위한 테스트 프레임워크AssertJ테스트 코드 작성을 원활하게 돕는 테스트 라이브러리풍부한 API, 메서드 체이닝 지원테스트 케이스 세분화하기암묵적이거나 아직 드러나지 않은 요구사항이 있는가?테스트 케이스 세분화하기 (해피 케이스, 예외 케이스)테스트하기 어려운 영역을 구분하고 분리하기관측할 때마다 다른 값에 의존하는 코드현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등외부 세계에 영향을 주는 코드표준 출력, 메시지 발송, 데이터베이스에 기록하기 등 섹션 4. TDD: Test Driven DevelopmentTest Driven Development프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론실패하는 테스트 작성 > 테스트 통과 최소한의 코딩 > 구현 코드 개선 테스트 통과 유선 기능 구현, 후 테스트 작성테스트 자체의 누락 가능성특정 테스트 케이스만 검증할 가능성잘못된 구현을 다소 늦게 발견할 가능성선 테스트 작성, 후 기능 구현복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다.쉽게 발견하기 어려운 엣지(Edge) 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백을 받을 수 있다.과감한 리팩토링이 가능해진다. 섹션 5. 테스트는 문서다.문서프로덕션 기능을 설명하는 테스트 코드 문서다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.DisplayName을 섬세하게명사의 나열보다 문장으로테스트 행위에 대한 결과까지 기술하기도메인 용어를 사용하여 한층 추상화된 내용을 담기테스트의 현상을 중점으로 기술하지 말 것BDD(Behavior Driven Development) 스타일로 작성하기TDD에서 파생된 개발 방법함수 단위의 테스트에 집중하기보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중하여 테스트한다.개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준(레벨)을 권장Given / when / then 발작국 2주차 회고이론으로만 접하고 있었던 부분을 실제 리팩토링 실습을 통해 더욱 피부로 와닿게 느낄 수 있었습니다. 특히, 직접 코드를 개선하는 과정에서 이론과 실무 간의 차이를 명확히 이해할 수 있었고, 클린 코드와 리팩토링의 중요성을 더욱 깊이 깨닫게 되었습니다. 또한 이번 주는 다른 개발자분들의 코드를 함께 리뷰하고 피드백을 주고받는 시간을 통해 다양한 접근법과 사고방식을 경험할 수 있었던 의미 있는 시간이었습니다. 혼자서는 생각하지 못했던 다양한 관점과 해결책을 알게 되었고, 협업을 통한 코드 리뷰의 중요성도 다시금 느끼게 되었습니다. 더불어 두 번째 강의에서 다루었던 테스트 코드의 중요성 또한 다시 한번 상기하게 되었습니다. 리팩토링 과정에서 테스트 코드가 얼마나 중요한 역할을 하는지 실질적으로 체감할 수 있었으며, 앞으로 테스트 코드 작성 능력을 향상시키기 위해 더욱 노력해야겠다는 생각이 들었습니다.
백엔드
・
워밍업클럽
2025. 03. 09.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 1주차 발자국
강의 요약 섹션 2. 추상 (抽象)우리가 클린 코드를 추구하는 이유가독성글이 잘 읽힌다.이해가 잘 된다.유지보수 하기가 수월하다.우리의 시간과 자원이 절약된다.추상이란?사물을 정확하게 이해하기 위해서 사물이 지니고 있는 여러 가지 측면 가운데서 특정한 측면만을 가려내어 포착하는 것이다. 어떤 일면만을 추상하는 것은 다른 측면을 버린다는 것과 같다.추상화를 하는 이유?적절한 추상화는 복잡한 데이터와 복잡한 로직을 단순화하여 이해하기 쉽도록 돕는다. 즉 읽기가 좋다.추상화의 가장 대표적인 행위이름 짓기메서드와 추상화메서드 선언부추상화 레벨매직 넘버, 매직 스트링 섹션 3. 논리, 사고의 흐름뇌 메모리 적게 쓰기Early returnelse의 사용을 지양사고의 depth 줄이기중첩 분기문, 중첩 반복문사용할 변수는 가깝게 선언하기공백 라인을 대하는 자세공백 라인도 의미를 가진다. (복잡한 로직의 의미 단위를 나누어 보여줌으로써 읽는 사람에게 추가적인 정보를 전달할 수 있다.)부정어를 대하는 자세부정어구를 쓰지 않아도 되는 상황인지 체크하기부정의 의미를 담은 다른 단어가 존재하는지 고민하기 or 부정어구로 메서드명 구성해피 케이스와 예외 처리예외가 발생할 가능성 낮추기어떤 값의 검증이 필요한 부분은 주로 외부 세계와의 접점의도한 예외와 예상하지 못한 예외를 구분하기Null을 대하는 자세항상 NullPointException을 방지하는 방향으로 경각심 가지기메서드 설계 시 return null을 자제한다 섹션 4 객체 지향 패러다임단일 책임 원칙 (Single Responsibility Principle)하나의 클래스는 단 한 가지의 변경 이유만을 가져야 한다.객체가 가진 공개 메서드, 필드, 상수 등은 해당 객체의 단일 책임에 의해서만 변경 되는가?관심사의 분리높은 응집도, 낮은 결합도개방 폐쇄 원칙 (Open-Closed Principle)확장에는 열려 있고, 수정에는 닫혀 있어야 한다추상화와 다형성을 활용해서 OCP를 지킬 수 있다.리스코프 치환 원칙 (Liskov Substitution Principle)상속 구조에서, 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야 한다.LSP를 위반하면, 상속 클래스를 사용할 때 오동작, 예상 밖의 예외가 발생하거나, 이를 방지하기 위한 불필요한 타입 체크가 동반될 수 있다.인터페이스 분리 원칙 (Interface Segregation Principle)클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다.ISP를 위반하면, 불필요한 의존성으로 인해 결합도가 높아지고, 특정 기능의 변경이 여러 클래스에 영향을 미칠 수 있다.의존 역전 원칙 (Dependency Inversion Principle)상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.의존성의 순방향 : 고수준 모듈이 저수준 모듈을 참조하는 것의존성의 역방향 : 고수준, 저수준 모듈이 모두 추상화에 의존하는 것 저수준 모듈이 변경되어도, 고수준 모듈에는 영향이 가지 않는다. 섹션 5 객체 지향 적용하기상속과 조합상속보다 조합을 사용하자!상속은 시멘트처럼 굳어지는 구조다. 수정이 어렵다.조합과 인터페이스를 활용하는 것이 유연한 구조Value Object도메인의 어떤 개념을 추상화하여 표현한 값 객체값으로 취급하기 위해서, 불변성, 동등성, 유효성 검증 등을 보장해야 한다.일급 컬렉션컬렉션을 포장하면서, 컬렉션만을 유일하게 필드로 가지는 객체컬렉션을 추상화하며 의미를 담을 수 있고, 가공 로직의 보금자리가 생긴다.만약 getter로 컬렉션을 반환할 일이 생긴다면, 외부 조작을 피하기 위해 꼭 새로운 컬렉션으로 만들어서 반환해주자.Enum의 특성과 활용Enum은 상수의 집합이며, 상수와 관련된 로직을 담을 수 있는 공간이다.특정 도메인 개념에 대해 그 종류와 기능을 명시적으로 표현해줄 수 있다.만약 변경이 정말 잦은 개념은, Enum 보다 DB로 관리하는 것이 나을 수 있다.다형성 활용하기변하는 것과 변하지 않는 것을 분리하여 추상화하고, OCP를 지키는 구조추상화와 다형성 활용하여 반복되는 if문 제거. OCP 지키기숨겨져 있는 도메인 개념 도출하기도메인 지식은 만드는 것이 아니라 발견하는 것객체 지향은 현실을 100% 반영하는 도구가 아니라, 흉내내는 것이다.설계할 때는 근시적, 거시적 관점에서 최대한 미래를 예측하고, 시간이 지나 만약 틀렸다는 것을 인지하면 언제든 돌아올 수 있도록 코드를 만들어야 한다.발작국 1주차 회고클린 코드와 객체 지향 설계에 대한 학습을 통해 코드의 가독성과 유지 보수성을 높이는 방법에 대해 많은 것을 배웠습니다. 이론으로만 알고 있던 부분들을 실제 코드를 수정해 가면서 배우니 훨씬 더 이해가 잘 되었던 것 같습니다. 빠르게 제 것으로 만들어 현업 프로젝트에 적용해 보고 싶습니다.
백엔드
・
워밍업클럽