블로그

송준환

인프런 워밍업 클럽 2기 - BE 클린코드, 테스트코드 스터디 4주차 발자국

일주일 간 학습 내용 요약BDD와 TDD를 활용한 테스트 작성 원칙테스트는 문서다테스트 코드는 프로덕션 기능을 설명하는 문서 역할을 한다.다양한 테스트 케이스를 통해 프로덕션 코드를 이해하고, 팀 차원의 자산으로 공유할 수 있다. DisplayName을 섬세하게 작성테스트 이름은 명사 나열보다 문장 형식으로 작성해야 한다.테스트 행위의 결과까지 명확히 기술하며, 도메인 용어를 사용해 추상화된 내용을 담아야 한다.메서드 관점이 아닌 도메인 정책 관점에서 기술하는 것이 중요하다. BDD(Behavior Driven Development) BDD는 TDD에서 파생된 개발 방법론으로, 함수 단위 테스트보다는 시나리오에 기반한 테스트 케이스에 집중한다.개발자가 아닌 사람도 이해할 수 있는 수준의 추상화를 권장하며, “Given-When-Then” 구조로 작성된다.Given: 시나리오 진행을 위한 준비 과정.When: 시나리오 행동이 진행되는 단계.Then: 결과를 명시하고 검증하는 단계.  테스트 작성 팁현상을 중점으로 기술하기보다는 도메인 정책에 맞게 테스트를 작성한다.테스트는 단순한 검증이 아니라 프로덕션 코드의 발전을 돕는 중요한 문서로써 팀의 자산이 된다.Mocking의 원리Mock을 마주하는 자세Mocking은 테스트를 할 때 실제 객체 대신에 가짜 객체(Test Double)를 사용하여 객체 간의 상호작용을 검증하는 방법이다.특히, 외부 시스템과의 상호작용을 모방하여 시스템의 동작을 시뮬레이션한다. Test Double의 종류Dummy: 아무런 기능을 하지 않는 객체.Fake: 실제로는 사용하지 않지만, 간단한 기능을 제공하는 객체(ex. FakeRepository).Stub: 미리 준비된 결과를 제공하는 객체.Spy: Stub처럼 동작하나, 호출된 내용을 기록하여 검증할 수 있는 객체.Mock: 특정 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체. 상태 검증 대신 행위 검증(Behavior Verification)을 중심으로 한다. Classicist vs. MockistClassicist: 상태 검증을 중시하는 전통적인 테스트 방식.Mockist: 행위 검증을 중시하는 방식으로, 객체 간의 상호작용을 검증하는 데 집중한다. Spring에서의 Mocking@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks: Spring에서 사용되는 다양한 Mocking 어노테이션과 그 용도.BDDMockito: BDD 스타일로 Mocking을 지원하는 라이브러리. Layered Architecture에서의 MockingPersistence Layer, Business Layer, Presentation Layer 각각에 대한 Mocking을 적용할 수 있으며, 통합 테스트보다 더 작은 단위의 테스트를 위한 도구로 사용된다.더 나은 테스트 작성 방법테스트 작성 원칙테스트 하나 당 목적은 하나: 테스트는 명확한 목표를 가지고 하나의 기능만을 검증해야 한다.완벽한 제어: 테스트에서 모든 상황을 완벽히 제어하고, 외부 의존성 없이 독립적으로 실행될 수 있어야 한다.테스트 환경의 독립성: 테스트 환경은 독립적으로 설정되어야 하며, 다른 테스트에 영향을 미치지 않아야 한다.테스트 간 독립성: 각 테스트는 서로 영향을 주지 않도록 독립성을 보장해야 한다. Test FixtureTest Fixture란 테스트를 위해 준비된 고정된 상태의 객체 집합을 의미하며, 테스트가 실행될 때 필요한 환경을 설정해 준다.Fixture 클렌징: 테스트 후 Test Fixture를 적절하게 정리하고 초기화해야 한다. 테스트 수행과 비용테스트를 수행하는 것도 비용이므로 가능한 한 테스트 환경을 통합하고 효율적으로 구성해야 한다. ParameterizedTest와 DynamicTest@ParameterizedTest: 여러 입력값에 대해 동일한 테스트를 반복할 때 사용한다.@DynamicTest: 동적으로 테스트 케이스를 생성하여 실행할 수 있다.효과적인 테스트 기반 API 문서화학습 테스트새로운 기능, 라이브러리, 프레임워크 등을 학습하기 위해 작성하는 테스트.여러 테스트 케이스를 정의하고 검증하는 과정을 통해 해당 기술의 동작과 기능을 더 깊이 이해할 수 있다.관련 문서를 읽는 것보다 재미있고, 실질적인 학습 방법으로 사용된다. Spring REST DocsAPI 명세를 자동으로 문서화해주는 도구로, 테스트 코드를 기반으로 API 문서를 생성한다.문서화된 API 명세는 협업을 원활하게 하며, 테스트가 통과해야 문서가 생성되므로 신뢰도가 높다.기본적으로 AsciiDoc을 사용하여 문서를 작성한다. REST Docs vs. SwaggerSpring REST Docs테스트를 통과해야 문서가 생성되기 때문에 신뢰도가 높다.프로덕션 코드에 영향을 주지 않지만 설정이 복잡하고 코드 양이 많다는 단점이 있다.Swagger설정이 쉽고, 문서에서 바로 API를 호출할 수 있는 기능을 제공한다.그러나 프로덕션 코드에 영향을 미치고, 테스트와 무관하게 문서가 생성되기 때문에 신뢰도가 떨어질 수 있다.회고• 칭찬하고 싶은 점: 시간이 많이 없었지만 미션과 발자국 모두 기한안에 낼 수 있어서 좋았다.• 아쉬웠던 점: 미션 제출 기한에 쫓겨 강의를 서둘러 듣고 제출한 점은 살짝 아쉽다.• 보완하고 싶은 점: 배운 내용들을 다시한번 복습해봐야겠다.드디어 마지막주까지 학습을 마치고 스터디가 끝났다. 4주동안 정말 많은 도움이 됐다. 클린코드와 테스트코드 모두 어느정도 틀과 방향성을 잡았으니 이제 시작이라고 생각한다. 항상 배운 내용들을 생각하고 돌려보며 코드를 작성하는 습관을 들여야겠다.Day 18 미션https://wnsghks.notion.site/Day-18-12a172cf6ea18016bb2bd5742b720bfc?pvs=4참고강의박우빈 - Readable Code: 읽기 좋은 코드를 작성하는 사고법

인프런워밍업클럽클린코드스터디테스트코드스터디

송준환

인프런 워밍업 클럽 2기 - BE 클린코드, 테스트코드 스터디 1주차 발자국

일주일 간 학습 내용 요약추상화 레벨을 맞추자.읽는사람이 한번 더 생각하지 않도록 항상 코드를 추상화 레벨에 맞춰서 작성을 하는것이 중요한것 같다.추상화 레벨에 맞게 구체화된 코드를 메서드로 만들어 추상화시키고 그에 맞춰서 이름을 지어주자.사실 이름짓기가 제일 어려운것 같다.읽는 사람으로 하여금 뇌의 메모리를 적게 쓰게 하자.평상시에 사용하는 if - else 문은 코드가 길어질수록 생각을 많이 해야한다.else 문에서 그 위에있던 if문의 조건들을 다 기억하고 제외해야하기 때문이다.이럴때는 Early return을 사용하여 뇌의 메모리를 비워주도록 해보자.공백라인을 사용해서 코드의 가독성을 높히자.단순히 공백라인을 사용하는것이 아니라 특정 단위로 끊어서 의미있게 사용을 해보자.객체를 설계할때 getter/setter 사용을 자제하자객체를 만들때 getter와 setter를 무조건 만들곤 했는데, 매우 폭력적이란 말씀에 어떤식으로 설계해야 되는지 확 와닿았다.객체를 객체답게 대우를 해주어야 할것 같다.SOLID:Single Responsibility Principle (SRP) - 단일 책임 원칙• 클래스는 단 하나의 책임만 가져야 한다. 즉, 하나의 클래스가 하나의 기능만 담당해야 한다.Open/Closed Principle (OCP) - 개방/폐쇄 원칙• 클래스는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.Liskov Substitution Principle (LSP) - 리스코프 치환 원칙• 서브타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.Interface Segregation Principle (ISP) - 인터페이스 분리 원칙• 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.Dependency Inversion Principle (DIP) - 의존성 역전 원칙• 고수준 모듈은 저수준 모듈에 의존해서는 안 된다. 둘 다 추상화된 것에 의존해야 한다.회고지난 일주일 동안 몇가지의 읽기 좋은 코드를 작성하는 방법을 학습을 했는데 일단 내가 이 스터디를 신청한 이유는아직 이 분야에 대해 배운지는 얼마 되지 않았지만 처음부터 보기 좋은 코드를 짜는 습관을 들여야 된다고 생각해서 신청하게 되었다.추상과 구체를 배우게 됐는데 원래 개념적으로 알고는 있었지만 직접 코드를 작성하면서 배우니 이렇게까지추상,구체화할수 있겠구나 라는 생각이 들었다. 코드가 작동되도록 작성하는것도 중요하겠지만 나중에 코드를 읽어볼때잘 읽히는 코드가 좋은 코드라고 생각이 되었다. 내가 저번주에 작성한 코드도 지금보면 읽기 힘들때가 많다.코드를 작성한 순간 레거시 코드가 된다는것에 크게 공감했다.• 칭찬하고 싶은 점: 일단 미션과 발자국을 기한안에 제출했고, 정해진 일정에 맞춰 스터디를 진행한점은 만족한다.• 아쉬웠던 점: 점점 갈수록 코드가 복잡해져서 완벽히 이해 안가는 부분을 넘어갔는데 하나둘씩 쌓이면서 처음과는 달리 여러번 돌려봐야 이해되는 부분이 생겼다.• 보완하고 싶은 점: 시간을 조금더 투자해서 확실히 이해하고 내것으로 만들자.다음 주 학습 목표• 다음주에는 내용이 더 딥해지겠지만 내것으로 만들고 잘 따라가는 것이 목표다.미션 해결 과정 [Day 2 미션]헬스장에서 운동을 하는 과정을 구체화시켜보았다.헬스장에서 운동을 한다를 한단계 구체화해서운동준비, 웨이트 트레이닝, 유산소운동, 마무리 로 구체화시켰고거기서 각각 한단계 더 구체화 시켜보았다.추상 : 헬스장에서 운동을 한다.구체 : [운동 준비]헬스장에 도착하면 먼저 운동복으로 갈아입는다.스트레칭을 통해 근육을 풀어준다.운동 계획을 확인하면서 오늘 할 운동을 상기시킨다.[웨이트 트레이닝]벤치 프레스를 할 경우, 먼저 벤치에 누워 발을 단단히 바닥에 고정한다.바벨을 양손으로 어깨 너비보다 조금 넓게 잡고, 안정된 자세를 잡는다.숨을 들이쉬면서 바벨을 가슴까지 천천히 내린다.가슴에 닿기 직전에 멈추고, 숨을 내쉬면서 힘을 주어 바벨을 다시 위로 밀어 올린다.팔을 완전히 펴지 않고, 긴장감을 유지한 채 반복한다.세트가 끝나면 잠시 휴식을 취하며 호흡을 정돈한다.[유산소 운동]러닝머신을 이용할 경우, 먼저 속도를 천천히 올리며 워밍업을 한다.몸이 충분히 풀리면 적당한 속도로 달리기 시작한다.시간이 지나면서 속도를 조금씩 올리며, 목표로 한 시간 혹은 거리만큼 달린다.마지막 5분은 속도를 낮춰 워밍다운을 통해 심박수를 천천히 안정시킨다.[마무리]모든 운동을 끝낸 후에는 다시 스트레칭을 통해 근육을 풀어준다.샤워를 하고, 단백질 쉐이크를 먹어준다.[Day 4 미션]아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.AS-ISpublic boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; }  AS-IS의 코드를 보면 불필요한 else문이 많고, If문의 조건식에 해당하는 코드를 보면 생각을 한번 거쳐야 이해되는 코드이다. [섹션 3. 논리, 사고의 흐름] 에서 배웠던 내용을 적용해보면 Early return으로 else문을 적지않고 다음 조건으로 넘어가기, 조건문 메서드로 뽑아내어 추상화 시키기, 그리고 부정어 사용을 지양했더니 훨씬 읽기 좋고 뇌의 메모리가 적게쓰인다는 것을 느꼈다.TO-BEvalidateOrder 메서드 public boolean validateOrder(Order order) { if (order.hasNoItems()) { log.info("주문 항목이 없습니다."); return false; } if (order.isTotalPriceInvalid()) { log.info("올바르지 않은 총 가격입니다."); return false; } if (order.hasNoCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } return true; } Order.javapackage cleancode.mission.day4.tobe; import java.util.Map; public class Order { Map<Item,Integer> items; String userid; public int calculateTotalPrice() { return items.entrySet().stream() .mapToInt(entry -> (int) (entry.getKey().getPrice() * entry.getValue())) .sum(); } public boolean hasNoItems() { return this.items.isEmpty(); } public boolean isTotalPriceInvalid() { int totalPrice = calculateTotalPrice(); return totalPrice < 0; } public boolean hasNoCustomerInfo() { return userid == null || userid.trim().isEmpty(); } } Item.javapackage cleancode.mission.day4.tobe; public class Item { String itemName; int price; public int getPrice() { return this.price; } } AS-IS의 코드를 보면 불필요한 else문이 많고 If문의 조건식에 해당하는 코드를 보면 생각을 한번 거쳐야 이해되는 코드들인데 [섹션 3. 논리, 사고의 흐름] 에서 배웠던 내용을 적용해보면 Early return으로 else문을 적지않고 다음 조건으로 넘어가기, 조건문 메서드로 뽑아내어 추상화 시키기, 그리고 부정어 사용을 지양했더니 훨씬 읽기 좋고 뇌의 메모리가 적게쓰인다는 것을 느꼈다.SOLID에 대하여 자기만의 언어로 정리해 봅시다.SOLIDSOLID는 코드의 유지보수성과 확장성을 높이는데 도움을 주기위해 만들어진 원칙이다.처음 자바공부를 할때 배우곤 당장의 코드짜기에 급급해 되짚어보지 않았지만 코드가 점점 복잡해 질수록 원칙의 필요성이 느껴진다. 단순히 개념을 배우기보다는 직접 코드를 작성하면서 깊게 생각해보고 이해도를 높여야겠다.SRP(Single Responsibility Principle) : 단일 책임 원칙하나의 객체에는 하나의 책임을 가져야 한다. → 높은 응집도와 낮은 결합도를 갖기위해 사람마다 역할과 책임의 경계가 모호하기 때문에 최대한 작성자의 의도를 알수있고, 일관성 있게 작성하여야 한다.만약 객체가 여러 역할을 담당하면 기능이 변경될 때마다 그 객체도 계속 수정되어야 할 것이다. ⇒ 객체의 수정 이유가 여러가지 이다.또한 어떤 기능을 수정하려고 하는데 그 기능이 어떤 객체에 들어가 있는지, 어느 부분에 있는지 내가 원하는 부분을 찾기 힘들 것이다. 하나의 객체에 하나의 책임을 가지게 되면 수정하고자 하는 부분을 찾기 수월해지고 객체를 수정하는 이유와 의도를 명확히 할수 있다. 이로써 유지보수성을 높일 수 있게 된다.OCP(Open-Closed Principle) : 개방-폐쇄 원칙확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다. 새로운 요구사항이 발생하더라도 기존 코드를 변경하지않고 새 기능을 추가할수 있어야 한다.만약 기존 코드를 변경해야 한다면, 그로 인해 새로운 버그가 발생할 가능성이 높아지고 다른 모듈이나 클래스에 의존성이 있는 경우 문제가 확산될 수 있다.상속이나 인터페이스 등을 활용해 기능을 확장하면서도 기존 코드를 손대지 않는 구조를 만들면 코드의 안정성이 높아진다.LSP(Liskov Substitution Principle) : 리스코프 치환 원칙하위 클래스는 상위 클래스의 역할을 대신할 수 있어야 한다. 즉, 상위 클래스의 인스턴스가 필요한 자리에 하위 클래스의 인스턴스를 대입해도 프로그램이 정상적으로 작동해야 한다.내가 이해한 바로는 부모클래스의 모든 기능을 자식클래스가 수행할수 있어야 한다는 것이다. 부모클래스의 기능에 추가적인 기능을 부여하면서 점점 구체화 시키는것이 자식클래스 인데 부모클래스의 기능을 수행할수 없다면 그것은 구체화 했다고 할 수 없고 정체성을 잃어버리기 때문이다. 이렇게되면 코드가 의도하지 않은 방식으로 동작할 수 있다.ISP(Interface Segregation Principle) : 인터페이스 분리 원칙클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다. 하나의 큰 인터페이스보다 각 클라이언트가 필요한 메서드가 가지는 작은 인터페이스로 분리하는 것이 좋다. 큰 인터페이스를 구현하게 되면, 그 인터페이스의 메서드를 모두 구현해야 하는 부담이 생기며, 사용하지 않는 기능에 대한 구현도 강요받게 된다.DIP(Dependency Inversion Principle) : 의존성 역전 원칙상위 모듈은 하위 모듈에 의존해서는 안 되며, 둘다 추상화 된 것에 의존해야 한다. ⇒ 구체적인 구현이 아닌 추상적인 인터페이스에 의존해야 한다.만약 상위 모듈이 하위 모듈에 직접 의존하면, 하위 모듈의 변화가 상위 모듈에 큰 영향을 미치게 된다. 따라서 둘 다 추상화된 인터페이스나 추상 클래스에 의존하게 하여, 결합도를 낮추는 것이다.처음에는 OCP 와 비슷한것 아닌가? 라는 생각이 들었다. 둘다 객체지향 설계의 중요한 원칙이지만, 그 초점과 목적이 다르다. OCP는 확장과 수정의 관리에 초점을 맞추고 DIP는 의존성 관리에 초점을 맞춘 원칙이다.참고강의박우빈 - Readable Code: 읽기 좋은 코드를 작성하는 사고법

클린코드스터디

채널톡 아이콘