2025-06 Testable Code 1

최초 업로드 2025-06-25 / 마지막 수정 2025-06-25

해당 글이 2, 3 버전이 언젠간 나오길 바라며 최근 내가 느끼는 테스터블 코드에 대한 생각을 간략하게 적어보고자한다.

디써클에서 벌써 1년 2개월 정도 근무하면서 많은 것들을 느꼈고 여러 개발 효율성에서의 문제점들을 해결해나가고 있다.

근무 했던 다른 환경들과의 차별점은, 한 번 만들어진 플랫폼을 v1, v2, v3 이런식으로 몇 년에 한 번씩 뒤엎는 것이 아니라

매우 애자일하게 거의 주 단위로 계속 요구사항이 바뀌는 것이다.

이전에 개발했던 것들은 대부분 폭포수 느낌이었고 스크럼과 스프린트는 했지만 그렇게 빡빡하지는 않았다.

이번 회사는 많이 달랐다. 주 단위로 기획이 바뀌거나 결정이 번복되고, 신규 기능이 추가되는 속도가 너무 빨랐다. (개발 인원에 비해)

특히나 숙련도가 따라오지 못하는 개발자가 팀에 속해있어서 앱의 퀄리티는 계속 떨어지고 버그는 너무나도 많아졌다. (안타깝게도 이것은 아직도 완벽하게 해결되지 못했다.)

그래서 이러한 환경 속에서 어떻게하면 계속해서 바뀌는 요구사항을 충족하면서 기존 만들었던 기능들을 깨지지 않게하고 리팩토링도 편하게 할 수 있을지 고민했다.

답은 하나로 귀결되었다: 테스팅을 해보자


멋모르고 E2E 부터 냅다 만들기

(작년 10월 ~ 12월 즈음이었던 것 같은데) 처음에는 무조건 프론트 - 백엔드를 전체 테스팅해서 깨지는 곳들을 보고자 했다.

당시 1주 들여서 고작해낸 것이 매번 배포될 때 e2e가 돌면서 가입과 로그인 이 두 가지만 테스트해보는 것이었다.

이때의 배경:

  • 회사 초창기에는 잦은 리팩토링과 인프라 변경으로 로그인도 자주 깨졌었다.
  • 무료 gcp 크레딧을 사용하여 실 서버와 완전 동일하게 모든 환경을 구축한 e2e 복제 서버와 DB가 존재했다.

이때의 문제점:

  • 실 배포 서버와 완전 동일하다보니 무료였긴하지만 유료였다면 많은 비용이 청구됨.
  • 케이스 하나 추가하는게 너무 어려웠음. e2e db 초기화와 원하는 데이터 시딩을 해주는 백엔드 엔드포인트도 따로 파져있었음.

오픈소스에 기여하다가 효율적인 통합 테스팅 구조를 이해함

자기 개발의 일환으로 없는 시간을 쥐어짜내어 3월 경에 Sequelize에 기여할 수 있는 기회가 있었다.

아쉽게도 이후에 너무 바빠서 Sequelize 버전 7에 기여는 못했지만 단 한 번의 PR 머지로 대량의 지식을 습득할 수 있었다.

Sequelize의 테스팅 파이프라인을 처음 돌려보자마자 미친듯이 큰 깨달음을 얻었다.

Sequelize 소스 코드에서는 Docker을 통해 DB를 종류 별로 띄워놓고 직렬로 통합 테스트 케이스들을 쭉 테스팅했다.

속도는 조금 느리지만 수천개의 mysql - sequelize 통합 테스트를 몇 분 안에 로컬 맥에서 돌아가는 것을 확인했고 그 중 깨지는 테케들을 수정해나아갔다.

이 프로세스를 단 한 번 경험하고 우리 회사에 이 전체 파이프라인을 모두 가져올 수 있다는 점을 깨달았다.

그래서 4월에 개발한 SP3라는 크롤러 엔진에는 Sequelize의 폴더 구조를 벤치 마킹하여 Unit, Integration, E2E 이렇게 세 개의 테스트 폴더들을 놓고 개발했었다.

소스 코드를 먼저 LLM으로 짜게 한 뒤에 LLM으로 다시 한 번 테스트 케이스를 만드는 식으로 Structural Testing(이건 최근에야 Effective Software Testing을 읽으며 이해한 개념이며 당시에는 뭘하는지도 잘 몰랐음)을 했었다.

그렇게 했을 때 복잡한 html => md => json으로 변환되는 파이프라인을 매우 안정적으로 만들 수 있었고, 새로운 회귀 테스팅 케이스들이 생겨났을 때 안정적으로 대응할 수 있었다.


레이어드 아키텍처에서의 한계점

4월에 SP3에서의 개발이 마무리되고 다시 메인 플랫폼 백엔드로 투입이 되었다. 그때 메인 백엔드 플랫폼에서는 테스팅이 예전에 내가 만든 e2e 테스트 외에는 전무했다.

대신 대규모 플랫폼 개편에 힘입어 핵심 서비스 로직에 모두 테스트를 달 수 있었다. 4, 5, 6월에 거쳐서 계속해서 테스트 케이스들을 추가해 나갔다.

여기서 또 많은 깨달음을 얻은 것이, 기초적인 레이어드 아키텍처에서는 서비스 레이어를 효율적으로 Unit Test할 수가 없다는 점이다.

데이터는 DB에 바로 찍히기 때문에 비즈니스 로직을 효과적으로 테스팅하려면 DB를 오락가락하는 것들을 모두 테스트에 끼워넣어야했다.

결론적으로 현재 우리 백엔드에는 600~700개 가량의 테스트 케이스가 존재하며 그중 대부분은 E2E이거나 Integration이다.

그래서 병렬 처리가 불가능하며 (Docker에 DB 여러개 띄우면 가능하긴함.) 대략 5분 가까이 되는 시간이 걸린다.

테스트는 빨라야한다. - 그래야 유지보수하기 쉽다. - 그래서 어떻게 하면 빨라질 수 있는지 6월 내내 고민했다.


데이터가 메모리 위에 올라간다면?

나는 대학 시절에 자바로 OOP를 공부하며 학교에서 철저하게 테스트 케이스들을 실행, 분기 커버리지 100%로 작성하게 했어서

테스트에 대한 경험이 이미 있는 상태였다. 다만 웹 환경에서 하는 것이 익숙하지 않았을 뿐이었다.

예전 기억을 더듬다보니 왜 예전에는 JUnit을 그렇게 쉽게 했는데 이제는 안될까를 계속해서 고민했다.

고민의 결과는 DB였다. 어떻게든 서비스 단을 서비스만 있도록 잘 만들면 되지 않을까? 라는 고민을 계속했다.

헥사고널이라는 시스템이 계속 떠올랐다. 헥사고널이라는 시스템은 몇 달 전에 DDD 책에서 읽고 넘어간 기억이 있다.

뭔가 바깥에 어댑터를 두어서 안쪽은 매우 행복한 세상을 만드는듯한 아키텍처였는데, 백엔드를 진지하게 한지 얼마 되지 않은 나는

헥사고널이라는 것이 엄청나게 어려운 서비스나 비즈니스 로직이 있어야 해야되는 것 정도로 이해하고 있었다.

더 알기 위해 헥사고널에 대해 GPT와 계속 이야기하다보니 Onion Architecture을 알게되었다. 이후 Clean Architecture 또한 알게되었다.

그래서 Clean Architecture 책의 핵심 챕터들을 추려서 읽었다. (이 또한 GPT에게 추리게 한 후 읽음.)

다행히도 내가 찾고자 했던 보물은 Clean Architecture에 실존했다. (Onion, Hexagonal도 유사하지만 Clean 위주로 읽었음.)

Clean Architecture을 준수했을 때 엔티티가 메모리에 올라와 있기 때문에 DB를 거치지 않아도 여러 개의 서비스 로직을 순차적으로 테스팅할 수 있다.

이제 더 이상 수 많은 Mock에 시달릴 필요가 없다. 원래 레이어드 아키텍터에서는 Service 레이어를 유닛 테스팅하려면 Mock이 90%라 사실상 나는 의미가 많이 없다고 봤다.

하지만 Clean Architecture의 usecase를 application레이어에서 정의한 뒤에 db repository.save 연결 부분만

mock을 해준 뒤 usecase 함수 여러 개를 순차 실행해도 메모리에는 실제 엔티티가 남아서 마치 DB의 역할을 하며 남아있다.

Clean Architecture을 쓰면 병렬로 수많은 서비스들을 깔끔하게 테스팅할 수 있다.

당장 도입될지는 모르겠지만, 몇몇 도메인의 로직에 우리 회사 백엔드 플랫폼에도 도입할 예정이다. (제발 올해 안에 가능하기를)

실제로 적용해보고, 여러 고민을 거듭한 뒤에 다시 이 포스트에 돌아오고 싶다.

2025-06 Testable Code 1

최초 업로드 2025-06-25 / 마지막 수정 2025-06-25

해당 글이 2, 3 버전이 언젠간 나오길 바라며 최근 내가 느끼는 테스터블 코드에 대한 생각을 간략하게 적어보고자한다.

디써클에서 벌써 1년 2개월 정도 근무하면서 많은 것들을 느꼈고 여러 개발 효율성에서의 문제점들을 해결해나가고 있다.

근무 했던 다른 환경들과의 차별점은, 한 번 만들어진 플랫폼을 v1, v2, v3 이런식으로 몇 년에 한 번씩 뒤엎는 것이 아니라

매우 애자일하게 거의 주 단위로 계속 요구사항이 바뀌는 것이다.

이전에 개발했던 것들은 대부분 폭포수 느낌이었고 스크럼과 스프린트는 했지만 그렇게 빡빡하지는 않았다.

이번 회사는 많이 달랐다. 주 단위로 기획이 바뀌거나 결정이 번복되고, 신규 기능이 추가되는 속도가 너무 빨랐다. (개발 인원에 비해)

특히나 숙련도가 따라오지 못하는 개발자가 팀에 속해있어서 앱의 퀄리티는 계속 떨어지고 버그는 너무나도 많아졌다. (안타깝게도 이것은 아직도 완벽하게 해결되지 못했다.)

그래서 이러한 환경 속에서 어떻게하면 계속해서 바뀌는 요구사항을 충족하면서 기존 만들었던 기능들을 깨지지 않게하고 리팩토링도 편하게 할 수 있을지 고민했다.

답은 하나로 귀결되었다: 테스팅을 해보자


멋모르고 E2E 부터 냅다 만들기

(작년 10월 ~ 12월 즈음이었던 것 같은데) 처음에는 무조건 프론트 - 백엔드를 전체 테스팅해서 깨지는 곳들을 보고자 했다.

당시 1주 들여서 고작해낸 것이 매번 배포될 때 e2e가 돌면서 가입과 로그인 이 두 가지만 테스트해보는 것이었다.

이때의 배경:

  • 회사 초창기에는 잦은 리팩토링과 인프라 변경으로 로그인도 자주 깨졌었다.
  • 무료 gcp 크레딧을 사용하여 실 서버와 완전 동일하게 모든 환경을 구축한 e2e 복제 서버와 DB가 존재했다.

이때의 문제점:

  • 실 배포 서버와 완전 동일하다보니 무료였긴하지만 유료였다면 많은 비용이 청구됨.
  • 케이스 하나 추가하는게 너무 어려웠음. e2e db 초기화와 원하는 데이터 시딩을 해주는 백엔드 엔드포인트도 따로 파져있었음.

오픈소스에 기여하다가 효율적인 통합 테스팅 구조를 이해함

자기 개발의 일환으로 없는 시간을 쥐어짜내어 3월 경에 Sequelize에 기여할 수 있는 기회가 있었다.

아쉽게도 이후에 너무 바빠서 Sequelize 버전 7에 기여는 못했지만 단 한 번의 PR 머지로 대량의 지식을 습득할 수 있었다.

Sequelize의 테스팅 파이프라인을 처음 돌려보자마자 미친듯이 큰 깨달음을 얻었다.

Sequelize 소스 코드에서는 Docker을 통해 DB를 종류 별로 띄워놓고 직렬로 통합 테스트 케이스들을 쭉 테스팅했다.

속도는 조금 느리지만 수천개의 mysql - sequelize 통합 테스트를 몇 분 안에 로컬 맥에서 돌아가는 것을 확인했고 그 중 깨지는 테케들을 수정해나아갔다.

이 프로세스를 단 한 번 경험하고 우리 회사에 이 전체 파이프라인을 모두 가져올 수 있다는 점을 깨달았다.

그래서 4월에 개발한 SP3라는 크롤러 엔진에는 Sequelize의 폴더 구조를 벤치 마킹하여 Unit, Integration, E2E 이렇게 세 개의 테스트 폴더들을 놓고 개발했었다.

소스 코드를 먼저 LLM으로 짜게 한 뒤에 LLM으로 다시 한 번 테스트 케이스를 만드는 식으로 Structural Testing(이건 최근에야 Effective Software Testing을 읽으며 이해한 개념이며 당시에는 뭘하는지도 잘 몰랐음)을 했었다.

그렇게 했을 때 복잡한 html => md => json으로 변환되는 파이프라인을 매우 안정적으로 만들 수 있었고, 새로운 회귀 테스팅 케이스들이 생겨났을 때 안정적으로 대응할 수 있었다.


레이어드 아키텍처에서의 한계점

4월에 SP3에서의 개발이 마무리되고 다시 메인 플랫폼 백엔드로 투입이 되었다. 그때 메인 백엔드 플랫폼에서는 테스팅이 예전에 내가 만든 e2e 테스트 외에는 전무했다.

대신 대규모 플랫폼 개편에 힘입어 핵심 서비스 로직에 모두 테스트를 달 수 있었다. 4, 5, 6월에 거쳐서 계속해서 테스트 케이스들을 추가해 나갔다.

여기서 또 많은 깨달음을 얻은 것이, 기초적인 레이어드 아키텍처에서는 서비스 레이어를 효율적으로 Unit Test할 수가 없다는 점이다.

데이터는 DB에 바로 찍히기 때문에 비즈니스 로직을 효과적으로 테스팅하려면 DB를 오락가락하는 것들을 모두 테스트에 끼워넣어야했다.

결론적으로 현재 우리 백엔드에는 600~700개 가량의 테스트 케이스가 존재하며 그중 대부분은 E2E이거나 Integration이다.

그래서 병렬 처리가 불가능하며 (Docker에 DB 여러개 띄우면 가능하긴함.) 대략 5분 가까이 되는 시간이 걸린다.

테스트는 빨라야한다. - 그래야 유지보수하기 쉽다. - 그래서 어떻게 하면 빨라질 수 있는지 6월 내내 고민했다.


데이터가 메모리 위에 올라간다면?

나는 대학 시절에 자바로 OOP를 공부하며 학교에서 철저하게 테스트 케이스들을 실행, 분기 커버리지 100%로 작성하게 했어서

테스트에 대한 경험이 이미 있는 상태였다. 다만 웹 환경에서 하는 것이 익숙하지 않았을 뿐이었다.

예전 기억을 더듬다보니 왜 예전에는 JUnit을 그렇게 쉽게 했는데 이제는 안될까를 계속해서 고민했다.

고민의 결과는 DB였다. 어떻게든 서비스 단을 서비스만 있도록 잘 만들면 되지 않을까? 라는 고민을 계속했다.

헥사고널이라는 시스템이 계속 떠올랐다. 헥사고널이라는 시스템은 몇 달 전에 DDD 책에서 읽고 넘어간 기억이 있다.

뭔가 바깥에 어댑터를 두어서 안쪽은 매우 행복한 세상을 만드는듯한 아키텍처였는데, 백엔드를 진지하게 한지 얼마 되지 않은 나는

헥사고널이라는 것이 엄청나게 어려운 서비스나 비즈니스 로직이 있어야 해야되는 것 정도로 이해하고 있었다.

더 알기 위해 헥사고널에 대해 GPT와 계속 이야기하다보니 Onion Architecture을 알게되었다. 이후 Clean Architecture 또한 알게되었다.

그래서 Clean Architecture 책의 핵심 챕터들을 추려서 읽었다. (이 또한 GPT에게 추리게 한 후 읽음.)

다행히도 내가 찾고자 했던 보물은 Clean Architecture에 실존했다. (Onion, Hexagonal도 유사하지만 Clean 위주로 읽었음.)

Clean Architecture을 준수했을 때 엔티티가 메모리에 올라와 있기 때문에 DB를 거치지 않아도 여러 개의 서비스 로직을 순차적으로 테스팅할 수 있다.

이제 더 이상 수 많은 Mock에 시달릴 필요가 없다. 원래 레이어드 아키텍터에서는 Service 레이어를 유닛 테스팅하려면 Mock이 90%라 사실상 나는 의미가 많이 없다고 봤다.

하지만 Clean Architecture의 usecase를 application레이어에서 정의한 뒤에 db repository.save 연결 부분만

mock을 해준 뒤 usecase 함수 여러 개를 순차 실행해도 메모리에는 실제 엔티티가 남아서 마치 DB의 역할을 하며 남아있다.

Clean Architecture을 쓰면 병렬로 수많은 서비스들을 깔끔하게 테스팅할 수 있다.

당장 도입될지는 모르겠지만, 몇몇 도메인의 로직에 우리 회사 백엔드 플랫폼에도 도입할 예정이다. (제발 올해 안에 가능하기를)

실제로 적용해보고, 여러 고민을 거듭한 뒤에 다시 이 포스트에 돌아오고 싶다.

Copyright © 2023 Seho Lee All Rights Reserved.
</>
Latest Commit
3f69a33a-19c5-5803-af99-13a0eb505499
Seho Lee
2025-06-25T11:50:39Z