누군가 나에게 "기회일지 손해일지 모르는 상황에서 너는 어떤 것을 선택하는 편이냐" 라고 묻는다면, 대개 전자를 택하는 편이다.
Me : If someone asks me like, "what would you choose between a chance or disadventage but you dont know what it is" then I mostly choose the former one.
ChatGPT : If someone were to ask me, "In a situation where you don't know if it will be an opportunity or a loss, which would you choose?" I would usually go for the former.
손해가 있을지언정, 기회가 될 수 있는 미래를 놓치고 싶지 않기 때문이다.
Me : Even if It will give me a disadventage, I dont want to miss the chance which could lead me a good future
ChatGPT : Even if it results in a loss, I don't want to miss out on potential opportunities in the future.
그 손해를 감수할 수 있도록, 그리고 기회를 많이 만들 수 있도록 평소에 준비를 많이 해둬야한다.
Me : I must be prepare in daily life to make chances more and overcome the disadventages.
ChatGPT : To take on such risks and create more opportunities, I need to be well-prepared in advance.
오늘은 많이 바쁘지 않았다.
Me : it wasnt busy today.
ChatGPT : Today was not very busy.
프로젝트도 잘 진행되고 있고, 저녁 모임도 잘 즐기고 올 수 있을 것 같다.
Me : The project is going well and I would enjoy a meeting this evening.
ChatGPT : The project is progressing well, and I think I can enjoy the dinner gathering tonight.
저녁 모임 이후에 코인노래방 가서 스트레스를 좀 풀어야겠어.
Me : Need to go to Coin Noraebang to release my stress.
ChatGPT : After the dinner, I should go to a Coin Noraebang to relieve some stress.
영어 공부를 구체적으로 어떻게 하면 좋을지에 대해 고민을 많이 했다.
Me: I've considered how to study Eng in detail.
ChatGPT : I've been thinking a lot about how to specifically improve my English.
영어는 내가 노력 한 만큼 실력이 상승 할 테지만, 외워야 할 것이 너무 많아.
Me: My Eng skill would be increased as I tried, but too many things to memorize.
ChatGPT : English skills will improve as much as I put in the effort, but there's so much to memorize.
하지만 내게 주어진 시간은 계산 가능한 범위 내에 있어.
Me : but the time I get is in the boundary which I can calculate.(??)
ChatGPT : However, the time I have is within a calculable range.
즉, 한정적이란 소리지.
Me : It means, my time has limit.
ChatGPT : In other words, it's limited.
그래서, 되도록 영어에 노출이 많이 되려고 한다.
Me : So, I will be exposed to Eng as much as possible.
ChatGPT : So, I'm trying to expose myself to English as much as possible.
영어 자체가 생활이 되면, 아기가 언어 배우듯이 자연스럽게 익혀지지 않을까 생각한다.
Me : I think i would learn Eng naturally as the babies learn if English has a big part of my life.
ChatGPT : I think if English becomes a part of my daily life, I'll pick it up naturally, like a baby learning a language.
영어 쉐도우 복싱 가능한 유튜브를 많이 보고, 많이 듣고, 또 직접 많이 말 해봐야겠다.
Me : should watch Eng shadowing video from youtube, listen a lot, and speak outside a lot.
ChatGPT : I need to watch a lot of Youtube videos for English shadowing, listen a lot, and speak a lot.
그 작업 중 하나가 이렇게 유튜브를 통해서 이야기 하는 것이다.
Me : the one of process is to speak out through youtube as this.
ChatGPT : One part of this effort is talking like this through Youtube.
얼른 유의미한 결과가 나왔으면 좋겠다.
Me : Hope to meet the meaningful result as much as possible.
Me : I'm gonna start to study English daily since today.
Gemini : I've decided to start studying English daily again.
작년에는 9월 글을 마지막으로 멈춰버렸는데, 이유는 바빠서가 아니고 그냥 힘들어서.
Me : I stopped this way to study Eng in Sep last year not because I was busy but It was very hard to keep doing it.
Gemini : I stopped studying English in September last year, not because I was busy, but because I found it difficult to continue
매일 글을 쓴다는 것 그리고 영어로 번역하고 틀린지 맞는지 확인하는 작업까지 한다는 것은,
보통 의지가 아니고선 계속 지속하기가 아주 힘든 것인 듯 하다.
Me : Literally it was very hard to me without strong will to keep writing something every day,
translating, and checking my English is correct or not.
Gemini : I realized that the process of writing daily, translating into English, and checking for spelling and grammar mistakes is something that is really difficult to do consistently without strong willpower. It can be quite challenging.
그럼에도 불구하고 다시 시작하는 이유는,
이렇게 힘든 과정을 거쳐야 억지로라도 영어 공부를 할 수 있을 것 같고
기억에도 더 오래 남을 것 같아서이다.
Me : The reasons why I start it again are to push myself to study English through this hard process,
and to memorize for a longer time.
Gemini : Despite this, I decided to start again because I think that going through this difficult process will allow me to continue studying English even if it's difficult, and that it will help me remember what I learn better.
이번에는 좀 글을 짧게 써보자.
Me : Let's write down shorter.
Gemini : I'll try to write shorter entries this time.
매일 내가 느낀 감정과 생각들을 일기처럼 쓸 것이고, 낭독도 할 것이고, 가능하다면 유튜브에도 올릴 것이다.
Me : Will write down what i've felt and thought today as a diary, speak out to record, and upload it on youtube.
Gemini : I'm going to write down my daily thoughts and feelings like a diary, record myself reading them aloud, and upload them to YouTube if possible.
내가 이것으로 기대하는 것은, 내가 주로 사용하는 표현들 위주의 영어 실력 향상과 내 매일의 기록,
그리고 나 자신을 돌보는 시간을 갖고 스트레스를 줄이는 것이다.
Me : What I expect from this works is to increase my English skills with some expressions which I use a lot, record my daily life, and release my stress by writing down my feeling so that I can check myself and do meditate.
Gemini : I hope that this will help me improve my English skills, especially in terms of the expressions I use most often, keep a record of my daily life, take some time for self-care, and reduce stress.
꾸준히 진행 한 이후, 기대하는 것들을 얼마나 이뤘는지 보자.
Me : Let's keep up this work and see how many things I will earn.
Gemini : I'll keep track of my progress and see how much I've achieved.
Gradle 은 application 의 빌드와 배포 문제를 해결하기 위한 framework 임 Java 의 경우, java class files, doc files, XML files, property files, 그 외 resources 등 하나의 프로젝트(application)에 굉장히 다양한 파일들을 담고 있음 게다가 각 프로젝트마다 각기 다른 구조 및 버전을 갖고 있어서 빌드하기가 굉장히 까다로움 Gradle 이 이러한 문제 해결에 도움을 줌
Gradle 이 하는 것 - 프로젝트 빌드하고 배포(jar 파일 만들어 줌) - 외부 lib 의존성 관리
Gradle 이 사용하는 build file 의 default 명은 "build.gradle"
Gradle 은 Domain-Specific Language (DSL) 을 정의함 (DSL : 어떤 도메인에서 특정 목적만을 달성하기 위해 사용되는 언어) 예를 들어 프로젝트는 Java 지만, build.gradle 은 Groovy 나 Kotlin 으로 쓰여질 수 있음
Gradle 이 진행하는 작업 단위 하나하나를 "task" 라고 부름 build.gradle 파일을 파싱해서 task 를 뽑아내고 작업을 진행함
task 들은 DAG 를 구성함. 따라서 모든 task 는 실행의 순서가 있으며 반복 실행되지 않음
먼저 실행된 task 의 output 이 나중에 실행된 task 의 input 이 되기도 함
Gradle 은 각 task 의 output 을 저장함 어떤 task 의 input 및 output 이 동일하다면, 다음에 동작할 때 해당 task 는 실행하지 않음으로써 리소스를 아낄 수 있음
Gradle 은 자기 자신을 자동으로 업데이트 할 수 있음 Gradle 은 의존성을 야기하는 lib 들 또한 자동으로 업데이트 할 수 있음
Gradle 의 핵심 구조는 다음과 같음
- Project : 소프트웨어 요소들의 능력(?) 이나 범위를 정의 - build script : "build.gradle" 이름을 갖는 스크립트 파일. build 하기 위한 지시사항들(task)을 갖추고 있음 - task : 실질적인 작업 사항
hello world 를 프린트하는 굉장히 간단한 build.gradle 예제 (Groovy)
위 내용을 갖는 build.gradle 을 만든 후, "gradle helloWorld" 명령어를 실행하면, gradle 이 실행되며 Hello World 가 실행됨
"gradle wrapper" 명령어를 실행하면, 기본적인 gradle 구조를 만들어 줌
- build.gradle - gradle - gradlew - gradlew.bat
build.gradle 은 task 들을 모아둔 명령 파일이고, gradlew 로 시작하는 것들은 실행 파일임
각 OS 마다 사용되는 gradle 실행 파일이 다름. - gradlew : unix(MacOS) 에서 사용 가능한 gradle 실행파일 - graldew.bat : Window 에서 사용 가능 - gradle/wrapper/gradle-wrapper.properties : gradle 버전 정보와 다운로드 url 정보 포함 - gradle/wrapper/gradle-wrapper.jar : gradle wrapper 실행에 필요한 jar 파일
이렇게 생성된 gradlew 를 갖게 되면 MacOS 에서 "./gradlew helloWorld" 같은 명령어 실행이 가능 해당 명령어로 위에서 만든 hello world 출력이 가능함
이렇게 만들어진 gradlew 를 사용하여 빌드하면 내 로컬 환경이 어떻게 구성되었는지 상관없이, 즉 내 컴퓨터에 java 나 gradle 이 설치되지 않아도 빌드가 가능함 다르게 말하면, gradlew 를 다른 컴퓨터나 서버로 옮긴 후에도 빌드가 가능함
참고로 gradlew tasks 명령어를 실행하면 현재 프로젝트에서 사용할 수 있는 gradle tasks 목록을 출력해 줌
Task 에는 여러 종류가 존재함
Ad hoc Task : 단 한 번만 커스텀하게 실행되는 태스크. doFirst, doLast 등의 액션으로 실행됨
task helloWorld { // 특별한 타입 없이 이렇게 선언 가능하다고 함. 아래 "type"을 갖는 태스크도 존재함 doLast { // Action 을 이렇게 선언 println "Hello World!" } }
실행 할 때는 gradle helloWorld 로 실행함
Type 을 선언하는 Task : copy 등의 타입을 선언한 태스크. 여기서 type 은, "이미 무엇을 할 지 정해져있는 작업모음 함수" 같은 것이라고 생각하자 예를 들어, copy type 은 "files 를 복사하고 dir 에 붙여넣는 작업"내용을 담고 있음 doLast 등의 actions 을 선언 할 필요가 없음. 왜냐하면 type 에서 이미 actions 정보를 갖고 있기 때문
task copyDocs(type: Copy) { from "src" into "build/docs" include "src/**/*md" // .md 파일만 골라서 copy 하도록 만듦. 여기서 ** 는 특정 dir 와 그 하위 dir 의 모든 파일을 포함하는 wildcard includeEmptyDirs = false // 빈 dir 는 제외하도록 설정 } task createZip(type: Zip) { from "build/docs" // build/docs 를 압축함 archiveFileName = "docs.zip" // 압축파일 이름 destinationDirectory = file("build/dist") // 생성된 압축파일이 저장되는 위치 dependsOn copyDocs // copyDocs 태스크가 실행된 이후에 createZip 이 실행되도록 의존 관계를 설정 }
실행 할 떄는 gradle copyDocs, gradle createZip 으로 실행함 gradle createZip 을 실행하면, 의존성이 걸려있는 copyDocs 가 같이 실행됨
--dry-run 옵션은 "gradle 실행시 어떤 tasks 를 실행할지 목록"을 보여줌 실제로 tasks 가 실행되지 않음 예를 들면 gradle createZip --dry-run
(혹은 gradle task tree 라는 community plugin 을 사용하여 실행 목록을 확인할 수 있음)
--all 은 gradle 을 실행할 때 동작을 확장하는 역할을 함 예를 들면 gradle tasks --all gradle dependencies --all // 모든 의존성 정보를 더 자세하게 출력함(터미널에)
gradle 이 실행(빌드가 실행 되면)되면, 내부적으로 아래 세 가지 스텝(life cycle)을 통해 실행됨.
1. Initialization phase : 빌드 대상이 되는 프로젝트의 정보를 담고 있는 settings gradle file 을 준비함
2. Configuration phase : build script 내 build 로직을 분석함 각 프로젝트마다 build script 를 갖을 수 있지만, 굳이 모든 프로젝트가 젝각각의 build script 를 갖고있을 필요는 없음. configuration phase 에서는 task 는 실행되지 않지만 (build.gradle 내에 있는) configuration code 는 실행됨 configuration codes 는 task 바깥에 있음. 아래 코드 예제 보면 바로 이해될 것임
// 여기 configuration code 가 존재하면 configuration phase 에서 실행됨 task helloWorld { // 여기 configuration code 가 존재하면configuration phase 에서 실행됨 doFirst {} doLast {} }
3. Execution phase : tasks 를 기준으로 생성된 DAG 따라서 순서대로 tasks 를 실행함 DAG 는 메모리 위에 있다고 함 execution phase 에서 실행하는 code 는 task 안쪽에 있음.
task helloWorld { doFirst { // 여기 execution code 가 존재하면 execution phase 에서 실행됨 } doLast{ // 여기 execution code 가 존재하면 execution phase 에서 실행됨 } }
여러 프로젝트의 build.gradle 내에 동일 코드가 반복되어 쓰여지는 것을 방지하고 중복 코드를 효율적으로 관리하기 위해 plugin 이라는 기능을 제공함.
그러면 build.gradle 내에 helloWorld task 가 들어가게 되어, 아래처럼 gradle 명령어 실행이 가능함
gradle helloWorld
gradle 에서 제공하는 "base" 라는 plugin 을 사용할 수 있음 base plugin 은 유용한 빌드 기능을 제공함 예를 들어 - gradle clean : 정리 작업 - gradle assemble : 프로젝트 산출물을 jar 로 패키징 하는 작업 - gradle check : 프로젝트 테스트 실행하는 작업
gradle 에서 제공하는 "java plugin" 의 핵심 기능은 컴파일링(빌드), 테스팅, 그리고 번들링(배포)임 빌드는 java, scala 코드 class 들을 빌드해주고 배포는 class 들을 jar 파일로 아카이빙 해주는 것
만약 gradle 이 없었다면, 직접 javac 명령어와 jar 명령어를 통해 빌드하고 배포했을 것임 이러한 명령어 사용 대신, java plugin 의 기능을 이용하여 빌드 및 배포 가능
java plugin 을 사용하기 위해서, java plugin 에 필요한 코드 구조를 충족해줘야 함 아래와 같은 구조를 갖추면, java plugin 이 알아서 이를 인지하고 test, compile, bundle 작업에 사용한다고 함
-src ㄴ main ㄴ java : production source code 가 포함된 dir ㄴ resources : 런타임에 사용되는 리소스들이 포함된 dir ㄴ test ㄴ java : src/main/java 를 테스트하기 위한 code 가 포함된 dir ㄴ resources : 테스트에 사용되는 리소스들이 포함된 dir
java plugin 이 작업을 마친 후 생성한 결과들(classes, jar) 은 아래에 위치하게 됨
-build ㄴ classes : java plugin 이 compile 한 class 파일들이 저장되는 dir ㄴ libs : java plugin 이 생성한 jar 파일이 저장되는 dir ㄴ resources
build.gradle 은 최상위 root dir 에 위치시키면 됨 build.gradle 내에 아래와 같이 추가하면 됨
java plugin 의 명령어 중 compileJava 는 src/main/java 에 위치한 production source code 를 컴파일하는 작업을 진행함
java compile 가 실행 가능하도록 PATH 환경 변수 설정이 되어있어야 하며 컴파일 후 생성되는 바이너리 파일들(class 파일들)은 build/classes 에 저장됨
아래처럼 complieJava 명령어 실행
./gradlew compileJava --console=verbose
java plugin 의 명령어 중 processResources 는 src/main/resources 에 위치한 리소스 파일들을 build/resources/main dir 에 옮기는(copy) 작업을 진행함
추후 jar 파일을 만들 때 build dir 아래 있는 것들을 jar 로 만드는데 이 때 리소스 파일들까지 함께 jar 에 넣기 위해 위와 같은 작업을 하는 것임
java plugin 의 명령어 중 classes 는 위에서 실행한 두 명령어( compileJava , processResources) 를 한 번에 실행하는 작업임
java plugin 이 컴파일 할 때 자바 버전을 직접 지정할 수 있음 그리고 compile 하다가 이슈가 발생하면 어떻게 하고 싶은지도 지정 가능 이를테면 나는 JAVA 11 버전을 사용하고 싶다! 그러면 build.gradle 을 아래와 같이 설정하면 됨
plugins { id 'java' }
java { sourceCompatibility = JavaVersion.VERSION_11 // compile 할 production java code 의 버전 targetCompatibility = JavaVersion.VERSION_11 // 앞으로 생성될 class 파일이 java 11 버전에서 실행할 수 있도록 설정 }
compileJava { options.compilerArgs << '-Werror' }
아래는 sourceCompatibility와 targetCompatibility 의 차이를 chatgpt 에게 물어본 결과임
Gradle에서 sourceCompatibility와 targetCompatibility는 Java 컴파일러가 소스 코드와 바이트코드를 처리하는 방식을 제어하는 속성입니다. 이 두 속성은 컴파일 시 호환성을 설정하는 데 사용되며, 각각의 의미는 다음과 같습니다.
1. sourceCompatibility
의미: 소스 코드가 어떤 버전의 Java 문법과 API를 사용할 수 있는지를 정의합니다.
설정하는 값: 이 값은 소스 코드를 컴파일할 때, 컴파일러가 해당 Java 버전에서 허용된 문법과 API를 사용하도록 제한합니다.
사용 예시: 만약 sourceCompatibility = '1.8'으로 설정하면, 소스 코드는 Java 8의 문법과 API를 사용할 수 있습니다. 그 이후 버전(Java 9, Java 10 등)의 기능을 사용하려고 하면 컴파일 오류가 발생합니다.
sourceCompatibility = '1.8'
2. targetCompatibility
의미: 컴파일된 바이트코드가 어떤 Java 런타임 버전에서 실행될 수 있는지를 정의합니다.
설정하는 값: 이 값은 컴파일된 바이트코드가 어떤 버전의 JVM에서 실행될 수 있는지를 결정합니다. 즉, 컴파일 결과물이 해당 버전의 JVM과 호환되는 바이트코드로 변환됩니다.
사용 예시: targetCompatibility = '1.8'으로 설정하면, 컴파일된 바이트코드는 Java 8 버전의 JVM에서 실행할 수 있게 컴파일됩니다. 만약 이 값을 Java 7으로 설정하면, 더 낮은 버전의 JVM에서도 실행할 수 있게 바이트코드를 생성하지만, 소스 코드의 문법이나 API는 여전히 sourceCompatibility에 의해 제한됩니다.
targetCompatibility = '1.8'
주요 차이점
**sourceCompatibility**는 소스 코드 레벨에서 사용할 수 있는 Java 버전을 설정합니다. 즉, 어떤 Java 문법과 API를 사용할 수 있을지를 결정합니다.
**targetCompatibility**는 바이트코드 레벨에서 어떤 Java 버전과 호환될지를 설정합니다. 즉, 컴파일된 클래스 파일이 어떤 JVM에서 실행될 수 있는지를 결정합니다.
실제 사용 시나리오
1. 같은 버전으로 설정: 대부분의 경우, sourceCompatibility와 targetCompatibility는 같은 버전으로 설정됩니다. 예를 들어, Java 8을 대상으로 하고, 그 버전에서 실행되도록 만들 때:
2. 호환성을 위해 다른 버전으로 설정: 예를 들어, 최신 Java 문법을 사용하지만, 더 오래된 JVM에서 실행할 수 있는 코드를 생성하고 싶다면 sourceCompatibility를 최신 버전으로, targetCompatibility를 더 낮은 버전으로 설정할 수 있습니다.
이 설정은 Java 11의 문법을 사용하되, 최종적으로 Java 8에서 실행 가능한 바이트코드로 컴파일됩니다. 단, 이 경우 Java 8에서 존재하지 않는 API를 사용하려고 하면 런타임 오류가 발생할 수 있습니다.
요약
sourceCompatibility: 소스 코드가 사용할 수 있는 Java 문법과 API를 정의.
targetCompatibility: 컴파일된 바이트코드가 실행될 JVM 버전을 정의.
이 두 설정을 적절히 사용하면, 특정 Java 버전에서 코드를 작성하고, 더 넓은 범위의 JVM에서 실행되도록 조정할 수 있습니다.
--console=verbose 옵션이 붙으면, 빌드 실행 내용을 콘솔에 더 자세하게 출력함 --console=plain 옵션이 붙으면 단순한 텍스트 모드로 출력함 --console=rich 옵션이 붙으면 콘솔에 컬러 및 애니메이션 적용하여 빌드 진행 상태를 시각적으로 출력함.이게 default 값임 --console=auto 옵션이 붙으면 콘솔 환경에 따라 rich 혹은 plain 으로 자동 설정됨
jar 파일 (java archive 파일) 을 생성하는 jar 명령어가 실행될 때 만약 compile 되지 않아서 class 파일이 존재하지 않는다면, 자동으로 compile 명령어를 실행하고 jar 파일을 만든다고 함 (굳이 직접 compile 명령어( compileJava,processResources , classes )를 실행하지 않아도 된다는 말인 듯)
jar 명령어는 아래처럼 사용 가능
./gradlew jar
이렇게 생성된 jar 파일은 build/lib dir 에 생성됨
생성된 jar 파일 이름은 01_01.jar 처럼 의미없이 생성됨 우리가 아래처럼 build.gradle 에 jar 를 추가함으로써, 직접 이름을 지정할 수 있음
jar { archiveBaseName = 'eyeballs' }
그럼 build/lib/eyeballs.jar 가 생성됨
아래처럼 jar 파일에 버전도 추가 가능
version = '1.0.0' jar { archiveBaseName = 'eyeballs' }
그럼build/lib/eyeballs-1.0.0.jar 가 생성됨
build.gradle 에 application plugin 을 추가하면 production source code 를 간단하게 실행해줌
만약 실행할 때 argument 도 같이 넣어 실행하고 싶다면 run 명령어 뒤에 --args 를 붙이면 됨
./gradlew run --args="2024 eyeballs"
Gradle Dependency Management Engine은 maven 같은 repository 로부터 lib 를 다운받고 프로젝트에 적용함 빌드하는 동안 lib(dependencies) 를 다운받고, local cache 에 저장하고 project 의 classpath 에 추가하여 project 에서 해당 lib 를 사용할 수 있도록 함
또한, 서로 의존성이 있는 다양한 modules 로 구성된 프로젝트에서 modules 간 의존성을 설정할 수 있음. 이를 multi-project build 라고 부름
또한, artifact 를 생성하고 repository 에 등록하여, 다른 end user 가 다운받아 사용할 수 있도록 만들 수 있음 이를 publishing 이라고 함
dependencies 는 build.gradle 에 아래와 같이 등록 가능함 예를 들어 maven repo 로부터 commons-cli:commons-cli:1.4 라는 lib 를 다운받고 프로젝트에 추가한다고 하자
repositories { mavenCentral() // maven repo 를 등록하여, 다운받을 lib 를 찾을 수 있도록 함 }
dependencies { implementation 'commons-cli:commons-cli:1.4' // 다운받아 프로젝트에 적용할 lib 를 여기 추가 }
참고로 repo 에 들어있는 lib 의 형태는 아래와 같이 group, artifact, version 세 가지 정보로 이루어져 있음
Gradle 에 dependencies 를 계속 추가하여 복잡해져도, "dependencies task" 를 이용하여 한 눈에 tree 형태로 dependencies 를 볼 수 있음
아래와 같은 명령어로 실행하면 됨
./gradlew dependencies
이 dependency 가 왜 필요한지, 어디서 왔는지 등을 알고 싶다면 "dependencyInsight task" 를 실행하여 확인할 수 있음
아래와 같은 명령어로 실행하면 됨
./gradlew dependencyInsight
" dependencyReport task" 도 사용해보자
./gradlew dependencyReport
app 과 api 라는 두 개의 프로젝트 존재하고, app 은 api 의 class 를 사용하는, 즉 api 에 의존성을 갖는 프로젝트라고 하자
이런 상황에서 gradle 이 이슈 없이 app 을 빌드하려면 어떻게 해야 할까? gradle 통해 app 과 api 프로젝트들의 서로 간 의존성을 관리하고 빌드하면 됨 이를 multi project build 라고 함
root dir 에 위치한 settings.gradle 에 아래와 같이 추가함
< settings.gradle > rootProject.name = 'eyeballsProject' include ':app', ':api' // app, api 두 프로젝트를 나의 'eyeballsProject' 의 하위 프로젝트로 추가함
그리고프로젝트의 하위 프로젝트들을 표시하는 명령어인 project task 를 실행하여 잘 추가되었는지 확인함
app 이 빌드되기 위해선 app 에서 api 의 class 를 import 할 수 있어야 함 이를 위해, 아래와 같이 app/build.gradle 의 dependencies 에 api 프로젝트를 추가해 줌
< app/build.gradle >
dependencies { implementation project(':api') }
그리고 전체 프로젝트르 빌드하면 app 과 api 프로젝트 간 의존성이 해소되어 문제 없이 빌드됨
./gradlew classes
추가로, multi project build 를 위해 하위 프로젝트들을 include 로 추가하면 얻는 이점에 대해 Gemini 에게 물어본 결과는 아래와 같음
include 키워드를 사용하여 하위 프로젝트를 추가하는 가장 큰 이유는 다음과 같습니다.
1. 멀티 모듈 프로젝트 관리: 하나의 루트 프로젝트: 여러 개의 관련된 모듈을 하나의 루트 프로젝트 아래에서 관리할 수 있습니다. 이는 프로젝트의 복잡성을 줄이고 유지보수를 용이하게 합니다. 모듈 간 의존성 관리: 각 모듈은 독립적으로 개발하고 테스트할 수 있으며, 필요한 경우 다른 모듈에 의존하도록 설정할 수 있습니다. 2. 모듈별 빌드 설정: 모듈별 설정: 각 모듈마다 다른 빌드 설정을 적용할 수 있습니다. 예를 들어, 특정 모듈은 Java로 개발되고 다른 모듈은 Kotlin으로 개발될 수 있습니다. 유연한 빌드: 다양한 유형의 모듈을 하나의 프로젝트에서 관리할 수 있어 빌드 프로세스를 유연하게 구성할 수 있습니다. 3. 코드 재사용: 공통 모듈: 여러 프로젝트에서 공통적으로 사용되는 코드를 별도의 모듈로 분리하여 관리하고 재사용할 수 있습니다. 코드 중복 방지: 코드 중복을 줄이고 유지보수 비용을 절감할 수 있습니다. 4. 모듈별 테스트: 모듈별 테스트: 각 모듈에 대한 단위 테스트를 수행하고, 통합 테스트를 통해 모듈 간의 상호 작용을 검증할 수 있습니다. 테스트 효율성 향상: 모듈별로 테스트를 수행하여 테스트 커버리지를 높이고 테스트 시간을 단축할 수 있습니다. 5. 모듈별 배포: 모듈별 배포: 각 모듈을 독립적으로 배포할 수 있습니다. 배포 유연성: 필요에 따라 특정 모듈만 배포하거나, 여러 모듈을 함께 배포할 수 있습니다.
./gradlew installDist 명령어는, Gradle 프로젝트를 독립 실행 가능한 배포판으로 만드는 명령 해당 프로젝트를 다른 환경에서 별도의 Gradle 설치 없이 실행할 수 있도록 모든 필요한 파일과 설정을 하나의 디렉토리에 패키징하는 작업을 수행함
해당 명령어가 실행되면 프로젝트의 최상위 디렉토리에 build/install 디렉토리가 생성되고 그 안에 완성된 배포판이 포함됨
해당 명령어를 통해 생성되는 파일 구조 - bin 디렉토리: 프로젝트를 실행하는 데 필요한 실행 스크립트 (예: gradlew, gradlew.bat)가 포함됨 - lib 디렉토리: 프로젝트에서 사용하는 모든 라이브러리 파일이 포함됨 - conf 디렉토리: Gradle 설정 파일이 포함됨 - resources 디렉토리: 프로젝트의 리소스 파일이 포함됨
JUnit 은 많은 인정을 받는 표준 java test framework 임 Gradle Java plugin 은 test source code 를 찾아 실행(테스팅)함 예를 들어 아래와 같은 dir 를 갖춘 프로젝트에서 src/test/java/*.java 를 테스트 할 수 있음
여기서 junit-jupiter-api 는, 개발자가 테스트 코드를 작성할 때 사용하는 API를 제공하는 lib 예를 들어 @BeforeEach, @AfterEach 등의 애노테이션을 제공한다거나, 다양한 assertion 메소드를 제공함
junit-jupiter-engine 는, 실제로 테스트를 실행하고 결과를 만드는 lib @Test 애노테이션이 달린 메소드를 찾아 테스트 대상으로 지정하고 테스트를 실행
useJunitPlatform() 은 빌드 프로세스에서 JUnit Platform을 테스트 실행 엔진으로 사용하겠다는 의미 Gradle의 'test' task 에 JUnit Platform을 설정하는 메소드이며 이 설정을 통해 Gradle은 프로젝트에서 JUnit 5 스타일의 테스트를 실행할 수 있도록 환경이 구성됨
testLogging 은 테스트 실행 시 출력되는 로그의 상세함과 내용을 조절하는 설정임 즉, 테스트 결과를 얼마나 자세하게 보여줄지를 설정 테스트를 시작할 때(started), 테스트가 실패했을 때(failed) 발생하는 이벤트를 콘솔로 출력해달라는 의미임
test 코드를 생성한 이후에 실제로 Test 를 하기 위해 compileTestJava 명령어로 테스트 코드를 compile 함
./gradlew compileTestJava
그리고 test 명령어로 테스트 코드 실행
./gradlew test
test 를 마치고나면, JUnit 이 HTML 및 XML 로 리포팅 페이지를 만들어 줌 HTML 파일은 build/reports/tests/test/index.html 에 위치하며, 열어보면 테스트 결과를 볼 수 있음 XML 파일은 build/test-results/test/*.xml 에 위치하며, 열어보면 테스트 결과를 볼 수 있음
추가로, testImplementation 과 testRuntime 의 차이에 대해 Gemini 에게 물어본 결과
언제 어떤 configuration을 사용해야 할까요? testImplementation: 테스트 코드에서 직접 사용하는 의존성 (테스트 더블, 테스트 데이터 생성 라이브러리 등) 테스트 코드 컴파일 시 필요한 의존성 testRuntime: 테스트 실행 환경을 구축하는 데 필요한 의존성 (임베디드 DB, 웹 서버 등) 테스트 코드 컴파일에는 필요하지 않지만, 테스트 실행 시에만 필요한 의존성 왜 구분하여 사용해야 할까요? 테스트 코드의 독립성: 테스트 코드를 생산 코드와 분리하여 관리하고, 테스트 환경에 필요한 의존성만 포함시킴으로써 테스트 코드의 독립성을 보장할 수 있습니다. 빌드 시간 단축: 테스트 실행에만 필요한 의존성은 컴파일 시에 포함되지 않으므로 빌드 시간을 단축할 수 있습니다. 의존성 관리: 테스트 환경에 필요한 의존성을 명확하게 관리하여 의존성 충돌을 방지하고 프로젝트를 안정적으로 유지할 수 있습니다.