여러 스타트업에서 CTO로 활동하며 기술팀의 역량 성장을 이끌어왔습니다.
코딩만큼이나 기술 지식과 경험을 나누는 일에서도 큰 즐거움을 느낍니다.
講義
受講レビュー
- Spring Boot TDD - 入門から実践まで正確に
- Spring Boot TDD - 入門から実践まで正確に
- Spring Boot TDD - 入門から実践まで正確に
- Spring Boot TDD - 入門から実践まで正確に
投稿
Q&A
69.테스트격리 / 과도한 테스트 격리의 문제 중 '부적절한 설계 왜곡'
백엔드해야지님 안녕하세요. 우선 강의 수강해주셔서 감사드립니다!말씀하신 부분의 제 의도를 정확히 이해하신 것으로 생각됩니다. 🙂1번에 대해서 조금 부가 설명을 드려보면, A 클래스에 코드가 늘어나서 일부 코드를 B 클래스로 분리하면 어떤 분들은 B 클래스에 대한 인터페이스를 만들어서 B 클래스를 주입하도록 하기도 합니다. 그러면 B 클래스는 A 클래스의 변형된 구현 설계임에도 public으로 노출되고 A 클래스의 관계가 복잡해 집니다.
- 1
- 2
- 34
Q&A
"오해: 단위 테스트와 통합 테스트를 잘 분리해야한다." 에 대한 질문
유상원님 안녕하세요. 강의 수강해주시고 좋은 질문 남겨주셔서 고맙습니다. 🙂질문에 대해서 제 생각을 말씀드리겠습니다.저는 테스트를 작성할 때 이런 것들을 중요하게 생각합니다.요구사항을 false negative 없이 충분히 검증하는가?엔지니어링 비용 지출이 효과에 비해서 큰가?그리고 제가 설계할 때 많이 떠올리는 원칙 중 하나는 "Consistency is overrated." 입니다.마지막으로 "현재 우리가 처한 상황에서 가장 실용적인 선택(장기적 관점까지 고려한)은 무엇인가"를 그 어떤 것보다 가장 중요하게 고민합니다. 잠깐 제 개인 사례를 말씀드리면, 제가 최근 맡은 업무 중 하나는 제어할 수 없는 외부 의존성에 너무 강하게 결합되어 국소적인 클래스 수준으로 밖에 테스트를 작성할 수 없는 시스템의 아키텍처를 개선하는 중장기 계획을 수립하는 것입니다. 이런 상황에서 통합 테스트가 좋은지 나쁜지는 고민할 거리가 아닙니다. 제가 익혀온 이론과 마주한 현실 사이의 차이를 점진적으로 줄일 명확하고 단계적인 전략이 필요합니다. 다시 돌아가서 제 동료가 상원님과 같은 의견을 저에게 제시했다면 저는 이런 질문을 드릴 것 같습니다.'많이 좋지 않은 테스트 환경'을 개선할 수는 없을까? 그것이 먼저 고려해야 할 근본적인 원인은 아닐까?많은 시간이 소비될 가능성이 크다고 했는데 가능성은 어느 정도이며 늘어나는 시간은 어느 정도인가?많은 시간이 소비되는 것이 모든 곳에서 일관되게(consistently) 발현될 문제인가?단위 테스트 분류가 반드시 필요하다고 주장하는 사람들 혹은 mockist들은 클래스 범위를 벗어나면 모두 외부라고 지칭한다. 우리가 정의하는 외부는 무엇인가? 그 외부라는 것들은 모두 동일한 특징을 갖는가?(테스트를 동일한 시간으로 느리게 만드는가?) 느려서 문제가 되는 테스트가 있다면 그 테스트를 고치는 것과 프로젝트의 모든 영역에 적용되는 단 한가지 일관된(consistent) 규칙을 적용하는 것(최악의 상황을 상정한다면, 그럼으로써 프로젝트 모든 곳에 일관된 false negative 위험성을 퍼뜨리는 것) 중 어떤 것이 더 실용적이고 엔지니어링 정의에 부합하는가?단위 테스트를 작성하고 false negative 위험성을 보완하기 위해 통합 테스트를 별도로 작성하는 것이 괴롭지는 않은가?(실제로 단위 테스트와 통합 테스트를 구분하던 많은 분들이 저에게 왜 같은 논리에 대한 테스트를 두 번 작성하는지 자괴감이 들지만 또 불안해서 생략하지는 못하겠다며 조언을 물었습니다.)이런 질문들에 대해 관련된 구성원들의 합의가 도출된다면 그 합의가 무엇이든 신뢰하고 추진할 것 같습니다. 하지만 특정 맥락이 주어지지 않은 상황에서 일반적인 원칙으로 "단위 테스트와 통합 테스트를 잘 분리해야한다."라고 말하는 것은 바람직하지 않습니다. 그렇지 않은 경우가 너무 많기 때문입니다. 대신 "우리 상황은 이러이러하기 때문에 이러이러한 장단점을 고려해서 단위 테스트와 통합 테스트를 잘 분리했다." 라고 말한다면 충분히 존중받을 것입니다. 아주 좋은 질문 남겨주셔서 다시 한 번 감사드리고요, 혹시 답변이 부족했다면 말씀해주세요!
- 1
- 2
- 482
Q&A
아키텍처 개선
제로콜라님 안녕하세요. 강의 수강해주시고 좋은 질문도 남겨주셔서 감사드립니다. 👍주신 질문들에 대해서 제 생각을 말씀드려볼게요. 질문 1. TDD를 사용해 만들어진 먹구름 아키텍처를 어느 시점에서 개선 해야 할까요?팀마다 상황이 매우 다양한 이유로 아키텍처를 개선할 수 있습니다.새로운 요구사항을 만족시키는 데에 코드베이스 이해 등에 소요되는 비용이 너무 크다고 느껴지는 경우.새로 합류한 팀원 입장에서 코드베이스 온보딩이 쉽지 않은 경우.팀원의 누군가가 먹구름 아키텍처에 과한 반감을 갖는 경우. 지식과 경험에 따라서 거시적 아키텍처 보다 작은 코드 설계에 관심이 많고 비즈니스 가치와 생산성에 큰 문제가 없더라도 특정 설계를 선호하거나 비선호할 수 있습니다. 제품 개발 생산성에는 가끔 구성원의 작은 동기부여가 영향을 줄 수도 있으니, 아키텍처 변경 비용이 그리 크지 않다면(TDD를 사용해 왔다면 그럴 가능성이 좀 더 높아지겠죠) 개선하는 것도 고려할 수 있습니다. 질문 2. 어떤 영역을 대상으로 테스트를 작성해야할까?API 계층의 동작이 가볍다 또는 무겁다의 판단 기준저는 팀의 프로그래머들이 테스트를 실행할 때 느끼는 감정을 가장 먼저 고려합니다. 절대적인 기준이 있는 것은 아니고 프로그래머들이 테스트 실행에 대해서 주로 느리다(무겁다)고 느끼면 개선하고 불편함을 느끼지 않는다면 굳이 개선하지 않습니다.제가 실무에서 사용하는 장비(m4 맥북)에서는 강의 촬영 환경보다 테스트 실행 성능이 높은데요, 강의 예제 프로젝트의 124개 테스트 실행에 1.7초 정도 걸립니다. 저는 보통 1,000개 테스트 실행에 30초 이하가 걸리면 느리다는 느낌을 별로 받지 않습니다. 물론 앞으로 제 감정은 달라질 수 있죠.서비스 대상으로 테스트를 많이 실행하고, API 는 간단한 성공 경우에 테스트를 한다.저는 사례로 말씀해주신 것처럼 할 것 같습니다. 🙂 혹시 답변이 부족했다면 말씀해주세요!
- 1
- 1
- 34
Q&A
병렬 처리 시 질문
HAHA님 안녕하세요. 강의 수강해 주셔서 감사합니다.말씀하신 것처럼 테스트 간에 서로 영향을 주는 상황이라면 '독립적인 환경'이 아니라고 판단할 수 있습니다.따라서 병렬 실행 시 다양한 문제들이 발생할 수 있으니 독립적이지 못한 환경에서 실행되는 테스트들은 병렬로 실행하지 않아야 합니다.답변이 부족하다면 말씀해주세요. :)
- 0
- 2
- 40
Q&A
assertThat 상태 코드 비교
진찬님 안녕하세요. 강의 수강해주셔서 고맙습니다!말씀하신 내용에 대해서는 별다른 의도가 있지는 않습니다. 코드 값보다 열거형 사용이 더 명확하다고 생각하신다면 그렇게 해도 좋다고 생각합니다. 🙂
- 1
- 1
- 48
Q&A
테스트 격리에서 테스트 랜덤 실패 이유
안녕하세요. 강의 수강해주셔서 고맙습니다.69번 수업이면 많이 진행하셨네요. 꼭 끝까지 완료하세요. :) 해당 수업에서 실패하는 테스트가 달라지는 정확한 이유는 저도 모르겠습니다. 그래서 프로그래밍이 어렵다고 말씀드린 것인데요.분명한 것은 README.md 파일 내용에 따라서 JUnit이 테스트를 실행하는 순서가 달라진다는 점입니다. 그래서 서로 맥락을 공유하는 테스트 중 먼저 실행되는 테스트는 성공하고 나중에 실행되는 테스트는 실패하는데, 이 순서가 테스트와 무관한 파일의 내용에 영향을 받다 보니 우리 입장에서는 말씀하신 것처럼 '랜덤으로' 실패하는 것처럼 느껴지는 것입니다. 혹시 설명이 부족하다면 말씀해주세요!
- 0
- 2
- 54
Q&A
테스트 코드 작성 범위 고민
폭식님 안녕하세요. 좋은 평가에 질문까지 남겨주셔서 정말 고맙습니다. 우리가 시스템을 개발할 때 테스트 대상이 될 수 있는 코드는 많습니다. 그리고 그 중에서도 실제로 어떤 영역을 대상으로 테스트를 작성해야 하는지는 여러가지 상황에 따라 달라집니다. 여기서는 말씀하신 구조를 가정하고 제 의견을 말씀 드리겠습니다. +--------+ +-----+ +---------+ +------------+ | Client | ---> | API | ---> | Service | ---> | Repository | +--------+ +-----+ +---------+ +------------+ 한가지 규칙만 있다면 참 쉬울 것 같은데요, 안타깝게도 그렇지는 않고 여러가지 상황 변수에 따라서 결정해야 합니다. 그래서 몇 가지 상황에 대해서 제가 선택할 수 있는 방법을 소개해드리겠습니다. 1. API 계층의 동작이 가벼워서 테스트 실행 성능에 큰 문제가 되지 않는 경우우리가 응용프로그램을 개발하는 목적은 클라이언트의 요구사항을 만족하는 것입니다. 이 목적이 달성되는지 확신할 수 있다면 다른 작업들은 선택적입니다. 그래서 모든 요구사항을 클라이언트와 직접 만나는 API 대상으로만 테스트합니다.+--------+ +-----+ +---------+ +------------+ | Client | ---> | API | ---> | Service | ---> | Repository | +--------+ +-----+ +---------+ +------------+ ^ | Tests 2. API 계층 동작이 무거워서 테스트 실행 성능에 많은 영향을 미치는 경우API를 대상으로만 테스트를 작성해서 클라이언트의 모든(또는 대부분의) 요구사항을 확인할 수 있더라도 테스트 실행에 너무 긴 시간이 소비되면 프로그래머는 심리적으로 테스트를 덜 자주 실행하게 되고 작업 리듬에 부정적인 영향을 받게 됩니다.이 때는 Service 대상으로 테스트를 많이 작성해서 테스트 실행 성능을 관리할 수 있습니다.Service 대상으로 최대한 많은 요구사항을 검증합니다.API가 Service를 사용하는지 확인할 수 있도록 가장 간단한 성공 경우에 대한 테스트 하나를 작성합니다.사용자 인증 등 API 계층의 책임인 기능을 검증하기 위해서 필요한 API 대상 테스트를 작성합니다.+--------+ +-----+ +---------+ +------------+ | Client | ---> | API | ---> | Service | ---> | Repository | +--------+ +-----+ +---------+ +------------+ ^ ^ | | Tests Tests 3. Service 계층 테스트에 Repository에 대한 테스트 대역(test double)을 사용하는 경우어떤 이유로 인해서 Service 개체를 테스트할 때 운영 환경과 동일한 Repository가 아니라 mock 등 테스트 대역을 사용하는 경우도 있습니다. 이런 경우 Service 개체를 대상으로 테스트를 충분히 작성해도 테스트에 의해서 Repository 코드가 실행되지는 않기 때문에 Repository가 운영 환경에서 정상적으로 동작하는지 확신할 수 없습니다. 그래서 이런 상황이라면 Repository에 대한 테스트를 별도로 작성합니다.+--------+ +-----+ +---------+ +------------+ | Client | ---> | API | ---> | Service | ---+------> | Repository | +--------+ +-----+ +---------+ | +------------+ ^ ^ | ^ | | | | Tests Tests | Tests | | +------------------------+ +--> | Repository Test Double | +------------------------+ 이 강의 실습에서는 1번 방식을 사용하고 저는 실무에서도 자주 사용합니다. 그리고 2번의 방법도 실무에서 많이 사용하고요, 테스트 대역을 잘 사용하지 않기 때문에 3번 방법은 가끔씩만 사용합니다. 클라이언트의 요구사항을 검증하는 것이 테스트를 작성하는 가장 큰 목적이고, 엔지니어링 측면에서 문제가 있을 때 이를 해결하기 위한 기법을 추가 사용한다고 제 기준을 정리하겠습니다. 더 설명이 필요하다면 말씀해주세요!
- 1
- 3
- 140
Q&A
질문드립니다.
오개발님 안녕하세요. 강의 수강해주셔서 감사드립니다. :) 첫번째 질문부터 답변 드리겠습니다.우선 테스트 코드의 @Transactional 사용 여부에 대해서는 강의에서 언급한 기억이 없고 자료를 찾아봐도 발견되지 않는데요, 제 기억이 잘 못 되었을 수도 있으니 제가 어떤 수업에서 말씀드렸는지 알려주시면 더 설명드리겠습니다.디스코드 대화에서 이런 말씀을 드린 적이 있기는 합니다."또 하나는 예전에 데이터베이스가 완전히 초기화된 상태에서 테스트가 거짓 음성을 일으킨 일이 있었습니다. 다른 데이터들이 섞여 있었다면 실패할 논리가 운영코드에 들어갔는데 딱 테스트에 필요한 데이터만 있어서 테스트가 성공한 적이 있었습니다."다만 테스트 코드에서 데이터베이스 롤백을 사용한 적이 없는 것은 맞고 이 내용은 강의에서 한두번 언급됩니다. :)CRUD는 유일한 방식은 아니며 설계 패턴 중 한가지고 저는 CRUD 패턴을 잘 사용하지 않기는 합니다만, 데이터베이스가 사용되는 테스트를 지칭하신다면 테스트에 운영 환경과 격리된 데이터베이스를 사용하는 것은 맞습니다. 강의 실습에서는 이런 내용에 대해서 다루지 않았지만 실무에서는 운영 환경과 테스트 환경은 염격하게 격리해야 합니다. 이것은 데이터베이스 롤백 사용 여부와 무관하게 안정적인 서비스 운영을 위해 반드시 지켜야 합니다. 그럼 두번째 질문에 대해서 말씀드릴게요.TDD는 테스트 기법이 아니라 테스트를 사용해서 소프트웨어를 개발하는 기법입니다. 그러니까 이미 만들어진 소프트웨어를 테스트 한다면 TDD가 필요하지는 않습니다. 그리고 이런 사례는 드물지 않게 발생하고 어색하거나 문제가 있는 상황은 아닙니다. :) 강의의 남은 수업들도 오개발님에게 도움이 되시길 바라겠습니다!
- 2
- 1
- 63
Q&A
거짓 양성, 거짓 음성 질문
태영님 안녕하세요. 강의 수강해주셔서 고맙습니다. :) 강의 내용 중 테스트 양성과 음성이 어떤 의미인지 혼동되시는군요. 종종 발생하는 일이라 오래전에 이 주제로 글을 쓰기도 했으니까 한 번 참고해 보시고요,소프트웨어 테스팅의 False Positive | 프로그래머 이규원의 웹사이트 도움이 되시도록 좀 더 설명을 드려 보겠습니다. 우선 코로나 바이러스 테스트 얘기부터 해보면요, 자가 코로가 바이러스 테스트를 아마 해보셨을 것 같은데, 양성 반응이 나오면 코로나 바이러스에 감염된 것(또는 감염되었을 가능성이 매우 높은 것)입니다. 그렇죠?식품의약품안전처 자료 참고6. 검사결과가 시험선(T)과 대조선(C)이두 줄로 나타나면 양성으로 선별진료소 등을 방문, PCR 검사를 받아야 합니다. 검사 후엔 자택으로 이동, 결과확인 전까지 자가격리를 해야합니다.여기서 코로나 바이러스 감염을 양성으로 보는 것은 테스트의 귀무가설(null hypothesis)을 '우리 몸이 코로나 바이러스에 감염되지 않았다'로 설정한 것입니다. 그래서 테스트를 통해서 뭔가 특별한 근거를 발견하지 못했다면 귀무가설을 채택하게 되고 이것을 음성이라고 합니다.그리고 실제로는 귀무가설을 채택해야 하는데 기각하게 되는 상황(즉, 코로나 바이러스에 감염되지 않았다는 가설을 채택하지 않음)을 일종 오류(type I error), 또는 거짓 양성(false positive)라고 합니다. 그리고 다른 표현이 또 있는데 거짓 경보(false alarm)입니다.그러니까귀무가설 기각 = 양성 = 경보가 됩니다. 이제 우리 몸을 시스템으로, 코로나 바이러스를 버그로 치환해보면 귀무가설은 '시스템에 버그가 없다'가 됩니다. 그래서 양성(경보)은 시스템에 버그가 있다는 뜻이고 테스트가 실패해서 프로그래머에게 알려주기를 기대합니다. 그런데 양성이 발생했는데 실제로는 버그가 없다면 테스트가 우리에게 잘못된 경보를 준 것이고 이 양성은 거짓 양성이 됩니다. 반대로 실제로는 버그가 있지만 이 버그를 감지해야 할 테스트가 실패하지 않고 음성 반응을 보이면 이것을 거짓 음성이라고 합니다. 질문을 보면 테스트 통과를 양성이라고 혼동하고 계신 것 같습니다. 테스트 시나리오 문구는 귀무가설입니다. 코로나 바이러스 검사 테스트 시나리오를 작성한다면 '우리 몸이 코로나 바이러스에 감염되지 않았다'가 되겠죠. 그리고 테스트가 성공하면 귀무가설을 채택하고 테스트가 실패하면 귀무가설을 기각합니다. 학문적으로 전자를 음성(negative)이라고 하고 후자를 양성(positive)이라고 합니다. 혹시 아직도 이해가 잘 안되시면 더 말씀해 주시거나 "what are false positive and false negative in software testing"으로 구글링을 해보시면 도움될 자료들을 많이 보실 수 있을거에요.
- 2
- 2
- 76
Q&A
cqrs 명령 아키텍처 개선 질문
규영님 안녕하세요. 강의 수강해주셔서 고맙습니다. :) 실습 코드를 정확히 이해하기 위해서 열심히 노력하고 계신 것 같습니다. 👍 도움이 되도록 이해하기 어려우신 내용에 대해 설명을 드려 보겠습니다. Product 엔터티는 JPA를 사용하는데요, JPA를 사용할 때 JpaRepository를 제공하는 Spring Data JPA를 반드시 사용해야 하는 것은 아닙니다. JPA를 지원하는 다른 도구들도 많습니다. 그래서 RegisterProductCommandExecutor를 만들 때 EntityManager 등 다른 도구를 사용하고 싶을 수 있습니다.EntityManager em = ... var executor = new RegisterProductCommandExecutor(p -> { em.getTransaction().begin(); em.persist(p); em.getTransaction().commit(); });이 때 어떤 도구가 사용되는지는 RegisterProductCommandExecutor는 알지 못하고 알 필요도 없습니다. 그래서 RegisterProductCommandExecutor는 JpaRepository 또는 Spring Data JPA라는 특정 기반구조 도구로부터 독립적인 설계가 됩니다. 재사용성이 조금 더 올라가는 거죠. 만약 RegisterProductCommandExecutor 클래스가 ProductRepository를 주입받으면 Spring Data JPA가 있어야 동작하는 특정 기반구조에 의존적인 코드가 되죠. 명령 모델과 API가 별도의 모듈로 분리된다면 의미가 더 명확해 집니다.+--------------------------------+ | Command Model | +---------+ | RegisterProductCommandExecutor |------->| JPA | +--------------------------------+ +---------+ ^ ^ | | | | +--------------------------+ +----------------------+ | API | | Spring Data JPA | | SellerProductsController |---->| JpaRepository | +--------------------------+ +----------------------+ 다만 이런 독립적인 설계가 유용한지 아닌지는 각 프로젝트마다 상황이 다릅니다. 따라서 규영님이 말씀하신 2번 설계를 사용하는 것이 더 좋을 수도 있습니다. 엔지니어는 한 가지 설계 방식만 아는 것 보다는 다양한 설계 방식을 이해하고 실무에서 주어진 환경에 따라 가장 적절한 방식을 사용하는 것이 더 바람직 하다는 관점에서, 의존성이 극히 낮은 설계 방식도 경험해 본다고 이해하시면 좋을 것 같습니다. :) 혹시 이해하시기에 설명이 부족하다면 말씀해주세요!
- 1
- 2
- 92