개발자들은 각각의 도메인분야의 개발영역이 있고, 주로 특정 도메인분야에 매진을 하는 편입니다.

 

안드로이드 개발자라면 안드로이드 앱을 개발할때 쓰이는 자바와, 안드로이드 API들

 

리눅스 커널 개발자라면 OS에 대한 지식과 디바이스 드라이버 개발 혹은 BSP 등

 

AI개발자라면 파이썬과 딥러닝분야에 대한 학습

 

서버개발자라면 Spring Framework 혹은 node js, 자바스크립트 등등

 

Domain에 따라 사용하는 주언어와 프레임워크, 학습분야 심지어 IDE종류도 천차만별로 달라지게됩니다.

 

심지어 한 평생 한 도메인에만 종사하는 개발자분들도 있으십니다.

 

과연 자기의 도메인에서만 쓰이는 것 만 열심히 갈고 닦는게 맞을까요?

 

물론 회사의 규모와 사정에 따라 한 도메인에 머물거나, 이곳저곳 다양한 도메인을 

경험하는 개발자들이 있는것처럼 개인의 사정에 따라 모두 다르게됩니다.

 

그러나 현대 Software는 점차 빠르게 변화하면서

SW개발자들에게 다양한 Domain 분야 에서의 업무를 요구하는 추세입니다.

 

Domain분야가 바뀐다면 그때 그때마다 당장에 일을 하는데 있어서 필요한 분야를 Study 하며 

따라가는게 중요합니다.

 

하지만, 전역적으로 Domain을 아우를 수 있는 그러한 SW 분야는 없을까요?

 

있습니다. 여기 제목에 쓰여져 있듯이 저는 알고리즘소프트웨어 공학이라고 생각합니다.

 

알고리즘

알고리즘

개발자가 알고리즘을 잘하면 무엇이 좋을까요?

우선 어떤 문제에 대해서 구현하는 것에 대해서는 막힘이 없게됩니다.

또한, 알고리즘 역량을 더 강화시키면, 성능을 최대한 끌어올릴 수 도 있게됩니다.

 

즉 요구사항이 주어질때 막힘없이 구현해내며, 성능을 최대한으로 끌어올려 최적화를 이루어낼 수 있습니다.

 

그렇지만 문제가있습니다.

개발자가 알고리즘역량만 발달되어 있어서 너무 구현과 성능에 치중한 나머지

간단하게는 코드가독성 부터하여 품질 중 변경용이성, 확장성 등을 놓칠 수 있게됩니다.

오히려 유지보수는 더 어려워지며 같이 일하는 동료들도 아무리 기똥찬 알고리즘이지만

이해를 못해서 생산성이 떨어지는 경우도 종종 발생하게 됩니다.

 

그렇기에 알고리즘역량 뿐만 아니라 아래의 소프트웨어공학 분야에 대한 역량역시 필요합니다.

 

소프트웨어 공학

소프트웨어 공학

알고리즘은 다들 무엇인지 아실겁니다. 다만 소프트웨어 공학에 대해서는 처음 들어보시는분들도 심지어 있으실 수 있습니다. 혹은 학창시절에 "소공은 들어선 안되는 과목이야" 라고 어디서 주워들어서 피하신분들도 있으실 수 있습니다 ^^;;

 

위키백과에서는 소프트웨어 공학을 아래와 같이 정의하고 있습니다.

 

-> 소프트웨어의 개발, 운용, 유지보수 등의 생명 주기 전반을 체계적이고 서술적이며 정량적으로 다루는 학문이다

 

아..역시 난해합니다 ㅡㅡ....제가 접하면서 느낀 소프트웨어 공학은, SW를 개발하고, 유지할때 필요한 학문들을 총괄하여 말하는 것 같습니다.

세부적인 분야로는 디자인 패턴, 요구공학, 테스트 프로그래밍, OOAD/UML, 리팩토링 등이 있습니다.

최종적으로 소프트웨어 공학의 학문들을 한번 Study해보시고 현업에 적용하시게되면 소프트웨어 아키텍트로 최종 거듭나실 수 있습니다.

소프트웨어공학분야를 접하게되는 순간, SW 개발할때 처음부터 끝까지 다 관여하실 수 있게 되거든요.

 

처음에는 Stake holder들과 미팅을 가지면서 요구사항들을 다 뽑아내고, 그들이 가장 우선시하는 품질들을 뽑아내어 기능 / 비기능 요구사항을 분리하여 정리합니다.

 

그 이후 설계에서는 어떤 디자인 패턴아키텍쳐를 사용할지 결정하고, 구현을 하게 됩니다.

구현 중간중간에서는 Code Quality를 높이기 위해 열심히 코드리뷰와 code metric을 이용하여 점수를 측정하고 올리기 위해 고민합니다.

또한 테스팅을 위해 어떤 프레임워크를 사용하며, 필수 테스팅 항목들을 뽑아내고 TC를 작성하다가, 

잘못 작성된 구현코드를 다시 수정하는등의 활동도 하게됩니다.

SW가 최종 릴리즈가 되고나서도 유지보수 단계에서 리팩토링이 들어가면서 다시한번 가독성도 올리고 Side effect도 줄일 수 있도록 훌륭하게 바꾸는 활동도 할 수 있습니다.

 

 

다시 정리해보자면 구현능력을 올리기위해선 우선적으로 알고리즘 역량을 쌓는것이 맞습니다.

하지만 SW를 정식 출시하고 유지보수를 좀 더 잘하기 위해서는 소프트웨어공학 역량을 쌓으셔야 합니다.

이 두 가지 분야는 어느 Domain이든 통하기 때문에 학습해놓으시면 SW개발자로 일하시면서 심지어 관리자로 일할때도 도 계속해서 사용하 실 수 있을것입니다.

 

 

Youtube 영상링크

https://www.youtube.com/watch?v=CqwrENwR6UM

스팅 시작하기 앞서

 Gradle을 도입한 목표중 하나는 단일 소스 코드로 목적에 맞는 다양한 APK 생성입니다. 모듈 내부에서 디버그, 릴리즈와 같은 빌드 타입별로 세부사항을 변경하거나, lite, full 버전 과 같이 기능 일부를 비활성화 할 수 있습니다.(feature 변경) 

 빌드 변형은 빌드타입과 제품특성을 합한 개념입니다. 어떤 모듈에 3가지 빌드타입과 4가지 제품 특성이 존재한다면 빌드 변형은 3*4=12가지 경우입니다. 이번 포스팅에서 빌드타입에 대해 살펴보도록 하겠습니다.


빌드타입

build type 에는 debug와 release가 존재합니다. 디버깅이 포함된 apk이냐 마켓에 배포할 apk냐에 따라 구분되어집니다. 

debug build는 default대로 진행하면 되고, release 빌드때는 pro guard 를 비활성화 하였습니다.

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

다음과 같이  minifyEnabled true로 하면 프로가드가 활성화됩니다. 소스코드난독화하여 역컴파일을 방지하게 됩니다.

프로젝트 폴더에들어가서 gradlew :app:assemble 명령을 치면 빌드가 완료되고나서 debug, release apk 가 생성됨을 확인하실 수 있습니다.

또한, 그렇게 하기 귀찮으시면 gradle 콘솔(안드로이드 프로젝트)을 열어서 assemble 태스크를 실행하시면 되겠습니다. app모듈에 build 그룹에 있습니다.


다음과 같이 assemble task를 실행시키고나면, debug용도와 release용도의 apk가 생성됨을 확인하실 수 있습니다.




앱 서명 첨부하기

release 용을 빌드할떄는 앱서명(siging)에 관한정보를 직접 지정하여 등록하셔야 합니다. signing을 할때 module build.gradle을 수정하는데, android 블록아래에 signingConfigs를 입력합니다. 물론 siginingConfigs는 buildTypes 블록보다 먼저 정의되어야 합니다.


signingConfigs{
release{
storeFile file('app.keysave')
storePassword 'keypass'
keyAlias 'key'
keyPassword 'mypassword'
}
}


buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

그런데 문제는, 앱서명에 관한정보를 직접 썼다는게 보안상의 문제로 될수있습니다. 그러므로 담당자 서버의 환경변수에 별도로 지정하는게 좋겠습니다.

signingConfigs{
release{
storeFile file('app.keysave')
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}

그리고 환경변수를 윈도우에서 설정해줍시다. 예로들어 KEYSTORE_PASSWORD라는 환경변수를 읽어오기 위해서는 ORG_GRADLE_PROJECT_KEYSTORE_PASSWORD 라고 환경변수 이름을 짓고, 변수값을 넣어서 생성하면 gradle에서 환경변수로 값을 얻어오실 수 있습니다.


제품 특성

빌드타입이 빌드의 속성을 변경하는것이라고 앞서 설명드렸습니다. 제품 특성은 리소스 교체 혹은 특정 feature를 활성화 또는 비활성화 시킬 수 있습니다. module build.gradle에 기술하시면 되겠습니다.



제품 특성 생성 해보기

제품 특성 생성하려면 android 블록에 productFlavor 블록을 추가합니다. lite와 full이라는 제품 특성 추가해보겠습니다.


productFlavors{
lite{
applicationId 'com.lite.HelloWorld'
}
full
}

.



제품 특성 확인


추가된 제품 특성을 확인하려면 Android Studio IDE 좌측 하단에 세로방향으로 위치한 Build Variants 창을 활용합니다. app모듈에 build Varient를 선택해보시면, full,lite 조합 release, debug로 나오는것은 확인하실수있습니다. 2*2 =4가지경우가있네요. 원하시는 값을 선택후 build apk를 하였더니 아래그림처럼, app-gull-release apk가 생성됨을 확인할 수 있었습니다.





제품 특성 활용

제품특성을 간단히 적용하려면 AndroidManifest.xml 을 변경한후 재빌드 하면 되긴합니다. 엄청 번거롭죠. 그러나 제품특성을 활용하면 더욱 쉽고 간편하게 적용시킬 수 있습니다. 예를든다면 lite 버전의 로고와 app label을 변경합니다.  app 모듈의 src폴더 아래 lite 폴더를 만들고 AndroidManifest.xml 파일을 추가합니다. Project View로 진행하시면 됩니다.



프로젝트뷰로 다음과 같이 lite폴더에 AndroidManifest.xml 파일을 추가하였습니다.

그리고 Android 뷰로 돌아오면 lite버전의 AndroidManifest.xml 파일이 추가된것을 확인하실 수 있습니다. 추가된 파일에는 (lite)라고 추가됨을 확인하실 수 있습니다. 

제품특성은 module build.gradle의 android.defaultConfig블록의 속성값을 공유하게 됩니다. defaultConfig블록은 간단히 요약하면 AndroidManifest.xml 의 내용 중 gradle로 재정의할 수 있는 속성들입니다. 또한 제품특성은 소스 코드를 재정의할 수 있습니다. 변경량은 최소화 하는게 좋습니다.




제품 특성으로 특정기능 활성화 (Feature On)

 제품특성을 활용하면 전체 app,의 기능 중 일부를 활성화 또는 비활성화 할 수 있습니다. 예를들어 full 제품 특성에서는 현재 시간 표시 기능을 활설화 하고 demo제품 특성에서는 현재시간을 보여주지 않게 합니다. 모듈 build.gradle에서 BuildConfig변수를 활용하면 됩니다.


productFlavors{

   full{

  buildConfigField "boolean", "SHOW_CURRENT_TIME", "true"

  }

   lite{

   buildConfigField "boolean", "SHOW_CURRENT_TIME", "false"

  }

}


SHOW_CURRENT_TIME이라는 boolean 변수를 추가했습니다. 내용을 입력하고 SYNC NOW버튼을 누르면 변수가 자동으로 생성되어 소스코드에서 참조할 수 있습니다.


 if(BuildConfig.SHOW_CURRENT_TIME){

  

}


실제확인하려면 app/build/generated/source/buildConfig/lite/debug/com/프로젝트 폴더에 BuildConfig클래스를 열어봅니다. applicationid등 빌드스크립트가 생성한 다양한 변수가 있는것을 확인하실 수 있습니다.



git rebase: 브랜치 이력을 확인하면서 병합합니다.


이전 블로그 포스팅에서 살펴본 병합과정은 사실 자주 실행하는 작업이 아닙니다. 실제git을 이용한 버전 고나리시스템의 작업흐름은 평소에는 여러개의 branch와 commit 내역을 만들고, 마지막에 작업내역을 확인하고 올바른 작업물만 병합하는 것입니다. 

 git의 특징 중 하나는 commit내역을 수정할수 있다는 것입니다. 하지만 수정할 수 있다고해서 이미 원격 저장소에 push가 끝난 commit내역을 수정하는 것은 특별한 상황이 아닌 이상 절대로 권장할만한 일이 아닙니다.


 push 하기전에 git merge명령을 이용해서 병합하면, 충돌해결 커밋이나, --no-ff로 만든 병합 commit을 남기게 됩니다 .이는 작업 흐름을 일관되게 파악하는데 깔끔하지 않습니다. 따라서 할 수 있다면 로컬 저장소에 있던 commit을 깔끔하게 정리해서 push하는것이 좋습니다.


 그런 정리를 할 수 있는것이바로 git rebase입니다. 여기서는 언제 git rebase를 사용하는것을 고려해야하는지, 그리고 commit내역을 깔끔하게 정리함으로써 얻을 수 있는 장점을 예와함께 살펴보도록 하겠습니다.



아래 그래프는 일반적으로 병합했을때의 모습입니다. 이전 블로그에서 작업했던 저장소의 모습과도 같습니다.


<일반적 작업 흐름의 그래프>




하지만 두개를 넘어서 세개 이상의 브랜치가 master 브랜치에 병합된다고 생각해보겠습니다. myBranch1을 만든 이후 master 브랜치에 어떤 commit내역이 생신 상태로 myBranch2, myBranch3를 만들어서 각각 commit을 했고, myBranch1에도 다른 commit 내역이 있는 상황입니다. 이를 차례대로 master 브랜치에 병합한다고 가정해봅시다.

<세개 이상의 브랜치 병합 시>

.


그럼 이것들을 합쳐야겠지요? 먼저 myBranch1을 master 브랜치와 병합니다.그리고 myBranch2를 maser와 병합합니다. commit 그래프가 꼬이기 시작합니다. 마지막으로 myBranch3을 병합합니다. 아래는 최종형태입니다.


프로젝트 멤버가 세명 이상이면 혹은 동시에 개발중이 기능이 여러개라면 branch가 세개 이상으로 생성되는 일은 매우 흔한 상황입니다. 그럴때마다 각자의 코드를 master브랜치에 반영하면 commit 내역 그래프가 매우 알아보기 어려울 것입니다. 실제로 작업한 내역을 git log --graph 명령을 실행하면, 계속 충돌을 해결해야하는 모습을 볼 수 있습니다. 하지만 git rebase 명령을 사용하면 이를 깔끔하게 정리할 수 있습니다.


실습을 위해 다음과 같이 구성해보도록 하겠습니다. myRebase라는 디렉토리를 만들고 다음과 같이 master와 myBranch123 브랜치들과 커밋내역을 만들도록 합시다.



branch             file               code               message

master         hello.py    print("Hello World")      Hello world

                                print("Hello World2")    Hello world2


myBranch1  hello.py     print("hello World")     Tell your World

                               print("Tell your world")  


myBranch2  hello.py    print("Hello World")     Tell His world

                              print("Hello World2")

  print("Tell His world")


myBranch3  hello.py   print("Hello World")     Tell Her world

                              print("Hello World2")

                             print("Tell Herworld")



$mkdir myRebase

$cd myRebase

$git init


$vim hello.py, 파일내용 추가합니다. print("Hello World")

$git add hello.py

$git commit -a -m "Hello world"


myBranch1을 땁니다.


 git branch myBranch1

$ vim hello.py      Tell your World

$ git add hello.py

$ git commit -a "Tell your World"


다시 master 브랜치로와서 hello world2 print 문 추가해주고 commit해줍시다.

$git commit -a -m "Hello world2"


자 다시 마스터 브랜치에서 브랜치를 땁니다. 2개를 만들어야하네요.

 git branch myBranch2

 git branch myBranch3

그리고 각각 브랜치를 이동하면서 Tell his world, tell her world를 hello.py에 추가하고 commit에도 같이 수정해줍시다.



이런 모양이 되겠지요?


먼저 myBranch1 브랜치부터 정리해보도록 하겠습니다.  master Branch앞으로 myBranch1 브랜치를 이동시키는 짓을 하려고합니다. 따라서 git checout myBranch1 명령을 쳐서, myBranch1으로 브랜치 이동을 합시다. 그리고 git rebase master 명령을 실행해봅니다.


당연히 충돌이 납니다. 이름도 REBASE가 붙은것으로 바뀐것을 확인하실 수 있으십니다.


rebase명령은 아래와 같이 세가지 옵션을 제공합니다.


git rebase --continue: 충돌 상태를 해결한 후 계속 작업을 진행할 수 있도록 해줍니다.

git rebase --skip: 병합 대상 브랜치의 내용으로 강제 병합을 실행합니다. 여기서 명령을 실행하면 master 브랜치를 강제로 병합한 상태가 됩니다. 또한 해당 브랜치에서는 다시 git rebase명령을 실행할 수 없게 됩니다. 

git rebase --abort: git rebase명령을 취소합니다. 다시 git rebase myBranch1명령을 실행할 수 있게 됩니다.


어쨌든 현재 브랜치는 다음과 같이 (myBranch1|REBASE 1/1) 의 상태임을 알 수 있습니다.

PJH@PJH-PC MINGW64 /d/blog/git/myRebase (myBranch1|REBASE 1/1)


그럼 충돌을 해결해보도록 하겠습니다. vim hello.py을 실행해서 충돌내용을 수정합니다. 그리고 git add hello.py 실행합니다. 또한, 마지막으로 아래와 같이 충돌 상태를 해결하는 rebase명령을 내립시다.


$ git rebase --continue


명령을 실행하면 master branch의 공통 부모까지의 myBranch1 Branch commit을 master branch의 뒤에 차례대로 적용합니다. 다음 아래의 그림과 같이 전체 작업 흐름의 위치가 이동한다고 생각해보시면 됩니다. myBranch1 rebase into master라는 것입니다. 현재 myBranch1을 master로 다시 설정하게 된다는 의미로 파악하시면 되겠습니다.

즉 첫번째 git rebase명령을 실행했을때 작업흐름은 다음과 같습니다.

여기까지면 단순하게 myBranch1과 master 브랜치가 따로따로 있는 것에 불과하게 됩니다. master 브랜치는 파랑, myBranch1은 노랑으로 따로따로 있게 된 상태이죠. myBranch1의 base(기반, 밑부분)을 다시 re, 재설정한것과 같은 효과입니다. 병합해야만 비로소 master 브랜치에 myBranch1 브랜치가 반영이 되는것입니다. 지금 master브랜치에는 myBranch1에 반영했던 hello.py 내역이 없습니다. 정말 merge된건 아니라는 말인거죠. myBranch1이 master 브랜치에 rebase만 된것입니다.

 git rebase 명령을 실행하면 무조건 fast-forward가 가능하지만, 이런 경우 병합 commit을 남기는것도 좋습니다. git merge myBranch1 --no-ff라는 명령을 실행해 fast-forward를 하지말라는 옵션을 주어서(사실 no fast-forward는 일반적인 병합시에도 매우 좋습니다. 병합한 흔적을 명시적으로 commit 그래프에 남기는 셈이니깐요.)병합을 실행하면 다음 아래과 같은 그래프가 됩니다.



git merge 명령을 실행했을 때 작업 흐름은 아래와 같습니다.

따라서  아래와 같이 명령을 실행해 master 브랜치로 이동한 후, git merge myBranch1 --no-ff 명령을 실행해 최종 병합해줍니다.


$ git checkout master

$  git merge myBranch1 --no-ff


이제 cat hello.py 를 실행해보면 병합되어져서 master브랜치의 hello.py에 수정병합 내용이 반영된것까지 확인하실 수 있게됩니다.


이제 위의 그림과 같이 myBranch2, 3역시 병합을 해봅시다. 블로그에서 글올린 순서대로 진행하시면 어려움없이 하실 수 있을 것입니다.


그리고 마지막으로 commit graph를 살펴보겠습니다. 아래와 같이 명령을 실행합니다.

$ git log --graph

어느 시점에서 병합했는지 아래와 같이 알 수 있어서 깔끔하고 좋습니다.

아름답게 나오는것을 확인하실 수 있습니다.


git rebase - i: 커밋 내역 합하기 명령입니다.

 git rebase 명령에는 활용도가 높은 옵션 -i가 있답니다. i는 interactive의 약어입니다. 상호작용하면서 rebase할 commit을 선탁핼 수 있습니다. 지금부터 앞서 작업했던 최근 commit 내역 두개를 합쳐보겠습니다. 다음 아래와 같이 명령을 실행합니다.


$ git rebase -i HEAD~~


commit 메세지창이 뜨고, 어떤 접두어로 해야하는지 알려주는 창이 뜨는것을 확인하실 수 있습니다.


수정할때는 다음 원칙을 지켜야합니다.

- 남기는 commit 메시지 앞에는 접두어로 pick을 붙입니다.

- 없애는 commit 메세지앞에는 접두어로 fixup을 붙입니다.

- commit SHA-1 체크섬 값은 꼭 남겨두어야 합니다.

- 기존 commit 메시지를 새롭게 수정할 수는 없습니다.


아래와 같이 커밋 메세지를 수정했습니다.


$ pick SHA값 Tell his world      

$ fixup SHA값 tell her world


아래 그림을 보시면, Tell Her World"라는 commit 내역과 그 사이에 있었던 병합 commit 내역이 모두 Tell His World Commit에 포함된것을알 수 있습니다. 그리고 commit 샤값도 새롭게 바뀐것을 확인하실 수 있습니다. 정리하자면 여러개 commit 중에서 필요한 것을 고른 후에 새롭게 commit 하게 되는 것입니다. 그렇다고 파일내용이 바뀌고 그런게 전혀아닙니다. commit내역을 정리한것입니다. rebase한 내역들의 commit들을 보기좋게 바꾼거라 생각하시면 되겠습니다.


$ git log



$ git log --graph



앞서 git 명령어 블로그 포스팅에 이어서 시작하겠습니다.

이전 블로그 내용이 궁금하시면 다음 링크 (새창)선택하셔서 확인하시면 되겠습니다.



git revert: 공개된 commit의 변경 내역을 되돌립니다.

이미 공개된 commit 내역을 수정하는것은 매우 risk가 큽니다. 할 수는 있긴있으나 정말 웬만하면 하지마세요. 하지만 안전하게 변경내역을 되돌리는 방법이 있습니다. commit으로 발생한 변경 내역의 반대 commit을 하면됩니다. 다시말하자면, 추가했던 코드는 빼고, 지운 코드는 다시 추가하는 commit을 하는 것입니다. 이를 수행하는 명령어가 바로 git revert 입니다. 아래와 같이 사용합니다.


$ git revert 커밋SHA-1체크섬값


이 명령을 특정 지점의 commit SHA-1 체크섬 값을 입력하면 해당 지점까지 변경내역을 취소하게 됩니다. 먼저 가장 최근 commit 변경 내역을 되돌려 보겠습니다. 먼저 아래와 같이 명령을 칩니다.


$ git log -5


가장 최근것을 취소해보도록하게습니다. SHA값은 b92c이군요. 아래와 같이 명령 입력합니다.

$ git revert b92c

vim 창이 등장하면서 commit 메세지를 수정하게됩니다. 원래 '원래메세지' + revert가 추가된것을 확인하실 수 있습니다. 저는 그상태로 저장하고 끝냈습니다. 되돌리기가 완료되었습니다. 실제 파일 내용을 확인해보면 추가했던것이 사라진것을 확인하실 수 있어요.


그런데 정말 fact는 실제로 되돌리는것이아니라, 되돌리는 것같은 효과를 낸다는 것입니다. git revert를 실행한 시점부터, 대상 commit까지 변경내역을 거꾸로 적용하는 새로운 commit을 만드는 것입니다. 



 Com0 -> com1 -> com2 -> com3 -> com4 -> com5

com2, com3, com4를 지우고 싶어서 revert com2를 한경우 com5라는 commit이 생성되면서 com1로 되돌아 간것같은 효과가 납니다.


git commit -3을 하면, revert를 하기전의 commit 내역 역시 존재하는것을 확인하실 수 있습니다. 이미 공개된 commit 내역은 이런 안전한 방법으로 되돌려야 합니다.

.


git reset: 이전 작업 결과를 저장한 상태로 되돌립니다.


git reset 명령은 어떤 특정 commit을 사용하지않게 되어 다시 되돌릴떄 사용합니다. git revert명령이 이전 commit을 남겨두는 명령이었다면 git reset명령은 이전 commit을 남기지않고 새로운 commit을 남긴다는 차이가 있습니다. git reset명령은 현재 commit인 HEAD의 위치, 인덱스, 작업하는 저장소 디렉터리 등도 함께 되돌릴리를 선택하기 위한 모드를 지정할 수 있습니다. 아래의 표를 참고하시면 되겠습니다.


git reset 명령 모드

 모드

의미 

HEAD 위치 

인덱스 

저장소 디렉토리 

 hard

완전히 되돌립니다. 

변경 

변경 

변경 

mixed 

인덱스 상태를되돌림. 모드를 지정하지 않았을때의 기본값입니다. 

변경 

변경 

변경 안함 

soft 

커밋만 되돌립니다. 

변경 

변경 안함 

변경 안함 


인덱스는 실제 commit 전 변경 내역을 담는 준비 영역입니다. git add 명령을 실행했을 때 이영억으로 이동하게 됩니다. 저장소 디렉토리는 실제 파일이 담겨있는 작업영역을 의미한답니다.



git reset 명령 옵션

 ^ 혹은~

~ 은 커밋내역 하나를 의미합니다. 표시한 수 만큼 커밋을 되돌립니다. 하나면 최종 커밋내역일 것이고 두개면 최종 커밋내역과 바로 전 커밋 내역이 됩니다.

 ORIG_HAED

 git reset명령을 실행했을 때 지운 커밋 내역을 보관합니다. 해당 명령을 통해 git reset 명령으로 지운 커밋을 되돌릴 수 있습니다.

 

 


최근 commit의 세번째 commit까지, 그리고 commit만 되돌려보기 위해 soft모드를 사용하겠습니다. git log -5명령 실행해서 최근 다섯개의 commit내역을 확인합니다. 그리고 아래와 같이 명령을 실행합니다.


~은 커밋내역 하나를 의미한다고 했습니다.

$ git reset --soft HEAD~~~

명령을 수행하고 아무 결과가 나타나지않습니다. 하지만 git log -3 명령을 실행해보면 가장 최근 commit이 바뀐것도 확인하실 수 있습니다. 또한 cat hello.py명령을 실행해보면 실제로 파일 내용이 바뀐것이 아님을 알 수 있습니다.


아래와 같이 명령을 쳐서 reset으로 날린 commit을 다시 원래 상태로 되돌리겠습니다.


$ git reset --hard ORIG_HEAD

위와 같이 명령을 치시고 다시 git log -5를 하면 commit내역이 돌아온것을 확인하실 수 있습니다.


음 커밋은 reset되었는데 commit이랑 다르게 파일은 그대로 보관되는것을 확인하실 수 있었죠? 이제는 hard모드로 진행해보도록 하겠습니다.


$ git reset --hard HEAD~~~

현재 HEAD의 위치를 알려줍니다. 

$ cat hello.py

파일 내용이 바뀐것을 알 수 있습니다.

$ git log -3 

역시 커밋내역이 위와 동일하게 사라졌음을 알 수 있습니다. 커밋과 함께 파일내역 수정된것도 반영되어 되돌아갔음을 확인하실 수 있습니다.


자 다시 되돌립시다.

$ git reset --hard ORIG_HEAD


git checkout head -- filename : 특정파일을 최종 commit 시점으로 되돌립니다.

이번에는 파일 하나를 대상으로 변경 내역을 통째로 원래대로(변경직전의 최종커밋시점으로) 되돌릴떄 사용할 수 있는 유용한 명령을 보도록 하겠습니다.


$ git checkout HEAD -- 파일이름

위 명령을 실행하면 파일이름 파일의 내용이 최종 commit 시점으로 되돌아 가게됩니다 "--"는 포함하는 것이 좋습니다. git checkout 명령에 뒤따라 오는 것이 파일이라는 것을 확실하게 해주는 것이라는 표시입니다. 만약 --가 없다면, 파일 이름이 브랜치 이름과 같을 경우 해당 브랜치로 checkout하거나, 특정 commit 시점으로 저장소 전체가 되돌아 갈 수 있습니다.


아래서부터는 README.md 파일을 다루겠습니다. vim README.md 명령 실행하고 파일에 수정사항을 둡니다. git checkout HEAD TEST 라고 쓰겠습니다.

그리고 아래와 같이 실행합니다. 아래와 같이 명령을 실행하면 인덱스에 변경 내역을 저장하게 됩니다.

$ git add README.md

git status명령을 실행해보면 commit할 내역이 있다고 알려줍니다. 이제 아래와 같은 명열을 실행합니다. 음 아무런 표시가 안뜨는군요


$ git checkout HEAD -- README.md


자 아래와 같은 명령을 수행해서 확인해보겠습니다.

$ cat README.md

원래대로 돌아갔음을 확인하실 수 있습니다.

아래와 같이 명령을 치면 또한 commit할 내역이 없음을 확인할수있습니다.

$ git status


git checkout -- 파일이름 명령은 git add 파일이름 명령을 실행한 후 추가 수정사항이 있을때 [$ git add 파일이름] 명령을 실행한 상태로 되돌리는 명령입니다. HEAD가 빠져있음을 확인하세요~!


git reset역시 파일하나를 되돌릴 수 있는것처럼 설명되어있는데요. 하지만 다른점이있습니다. git reset명령은 hard모드가 아니라면 저장소 디렉터리의 파일 내용은 명령을 실행한 시점 그대로 남습니다. git reset명령으로 되돌린다음 필요한 부분만 수정작업하고 다시 commit할 수 있습니다. 하지만 git checkout은 파일을 완전하게 대상 commit 시점으로 되돌립니다. 파일 내용이 대상 commit시점으로 완전하게 되돌아 가는것입니다. 즉 git reset명령의 hard모드를 실행한것처럼 index와 작업전부를 되돌리게 됩니다. 




Git을 사용할때 알아두면 유용한 명령어들이 있습니다. 아래와 같이 정리하였습니다. 참고해주세요.


git tag, 커밋을 참조하기 쉽도록 알기쉬운 이름을 붙입니다.

git commit --amend 같은 브랜치 상에 있는 최종 커밋을 취소하고 새로운 내요을 추가하거나 설명을 덧붙인 커밋을 할 수 있습니다.

git revert  이전에 작성한 commit을 삭제합니다. 그런데 특정 commit의 내용을 지우는 새로운 commit을 만들어 지운내역을 모든 사람이 알 수 있게 합니다.

git reset 어떤 commit을 버리고 이전의 특정버전으로 다시 되돌릴때 사용합니다. git revert와 다른점은 지운 커밋내역을 남기지 않는다는 점입니다. 

git checkout HEAD --filename 아직 commit하지 않은 변경 내역을 취소합니다. 

git rebase  git merge처럼 병합할때 사용합니다. 하지만 branch가 많을 경우 branch 이력을 확인하면서 병합합니다.

git rebase -i  서로다른 두 개의 commit 내역을 합칩니다.



git tag: 특정 commit을 참조하는 이름 붙이기

git tag 명령은 저장소의 commit에 tag를 붙이는 명령입니다. 


가장 최근commit에 tag를 붙이고 싶다면 간단하게 다음 명령을 실행하면됩니다.

$ git tag 태그이름


저는 1.0 버전이라는 태그를 붙이겠습니다.

$ git tag 1.0

그리고 로그와 함꼐 태그를 보겠습니다.


$ git log --decorate -1 


tag와 commit SHA-l 체크섬 값을 같이보려면 아래와 같이 칩니다.

$ git show-ref --tags 



특정 commit에 tag 붙이는 방법.

먼저 SHA-1 체크섬값을 알아야 합니다. 여기에서는 최근 커밋 바로 애라의 커밋에 태그를 붙이겠습니다.

아래와 같이 명령어를 실행합니다.


$ git log -2

이전의 commit의 SHA값 8591 앞자리를 기억합니다.


그 이후 아래와 같이 엔터칩니다.

$ git tag 0.9 8591

그이후 또 아래와 같이 명령을 실행합니다.

$ git show-ref --tags 

태그가 붙여진것을 확인하실 수 있습니다.


git tag 명령으로는 'light weight' tag만을 생성하게 됩니다. 누가, 언제 붙였는지 젼혀 알 수 없습니다.

tag 그자체에 대한 기록은 이름외에 아무것도 없는 상태입니다.


하지만 "annotated"태그는 누가 언제 태그를 붙였는지 기록하고, 추가 메세지 까지 저장합니다.

로그를 살펴보면서 태그들을 보다가 해당 태그 시점에 태그에 대한 의문이 생기면 누구에게 질문해야하는지 한번에 알수있게 됩니다.

또한, 커밋과 다른 시점에 붙은 버전 태그가 있다면, 언제 해당버전이 배포되었는지 알수있기도 합니다.


이번에는 annotated 태그를 붙여보겠습니다.


$ git log -3

3번째전거의 SHA 앞자리를 확인합니다. 

그 이후 아래와 같이 명령을 치고 엔터를 칩니다.


$ git tag -a 0.8 68eb


이렇게 명령을 실행하면 vi 편집기가 싱행되는것을 확인하실 수 있습니다. 기록할 메세지를 입력하고 저장하면 tag 붙이이기가 완료되게 됩니다.


$git show 태그이름


git show 태그이름 명령을 실행해 확인하면, 아까와는 달리 누가 언제 어떤 메시지를 입력해 태그를 붙였는지 확인하실 수 있습니다.


$ git show 0.8


git commit --amend: 마지막 commit 수정하기


마지막 commit message를 수정하는 명령은 간단합니다.

$ git commit --amend

위의 명령을 실행하면 마지막 commit과 commit하지 않는 상태에 있는 변경내역이 서로 합쳐진 새커밋을  만들게 됩니다. 만약 아무런 변경내역을 만들지 않고 명령어를 실행하면 커밋 메시지만 변경하게 되는것과 같은 효과를 낼 수 있습니다.


한번 일부러 hello.py 파일을 변경해보겠습니다. (vi 편집기창을 열어서 it's git commit --amend 라고 변경사항늘 만들어 놓겠습니다.)


변경 내역을 추가하기위해서 아래와 같이 명령을 실행합니다.

$git commit --amend


다시 commit 메시지를 입력하는 편집기가 등장합니다. 변경 내역이 있었으므로, 기존 commit message였던

conflict resolved github뒤에 by jinss 라는 커밋메시지를 추가한후 저장하겠습니다.


저는 이상태에서(변경내역이 없는상태)에서 commit message만 바꿔보겠습니다.

$git commit --amend 명령을 다시 실행합니다.

그리고 뒤에 by pjh를 붙였습니다.


그리고 아래와 같이 명령을 실행해봅니다.

$git log -1


좀더 설명드리자면, git commit --amend는 최종 commit을 수정하는 것이 아니라, 최종 commit을 대체하는 새로운 commit 을 만드는 것입니다.

명려을 실행하기전과 후의 commit의 sha값을 비교해보면 바껴있는것을 확인하실 수 있습니다. 또한 최종commit에서 추가되어 새로운 commit이 나오는게 아니라 마지막 commit에서 메세지값이 바껴진 새로운 commit이 대체되는것을 확인하실 수 있습니다. 물론 파일에서 변경사항이 있었다면, 그변경사항(파일)과 메세지까지 바뀐게 commit에 반영되어 SHA값이 새로 commit 되는거죠.


git log -5를 해봐도 마지막 commit을 계속 덧붙혀으나 위에는 최종 commit에서 덧붙혀진게 하나만 나오고 또한, 그전 마지막에있었던 commit의 SHA값이 달라짐을 확인하실 수 있습니다.


$ git log -5

여기서 git commit --amend를 쳐서 저장하고 다시 git log -5 를 쳐면 맨마지막 commit의 sha값이 변경됨을 확인하실 수 있습니다.


$ git commit --amend

$ git log -3




'개발 Support > GIT' 카테고리의 다른 글

git rebase, git rebase -i  (0) 2017.07.16
git revert, reset, checkout head -- filename  (0) 2017.07.15
원격저장소와 GIT  (0) 2017.07.15
원격저장소와 GitHub  (0) 2017.07.09
로컬 저장소 git 기본 및 실습  (1) 2017.07.09


들어가기 앞서.

 분산버전 롼리시스템은 다른사람과의 협업을 염두에두고 만들어진 툴입니다. 결국 원격저장소와 로컬 저장소 사이를 얼마나 효율적으로 관리하느냐가 관건입니다. Git에서는 원격저장소와 소통하기 위한 기능을 제공하고있습니다.



GIT 명령어를 알아봅시다.


git clone:   원격 저장소의 모든 내용을 로컬저장소에 복사합니다.

git remote: 로컬저장소를 특정 원격 저장소와 연결합니다.

git push:    로컬 저장소의 내용을 보내거나 로컬 저장소의 변경사항을 원격 저장소로 보냅니다.

git fetch:    로컬저장소와 원격 저장소의 변경사항이 다를때 이를 대조하고 git merge 명령어와 함께 최신 데이터를 반영하거나 충돌문제등을 해결합니다.

git pull:      git remote 명령을 통해 서로 연결된 원격저장소의 최신내용을 로컬 저장소로 가져오면서 병합합니다. gir push와 반대 성격의 명령입니다. 




Git Clone: 원격저장소의 내용을 로컬 저장소로 가져옵니다.


클론을 정의하자면 다음과 같습니다.

1. 내가 생성한 원격 저장소를 내컴퓨터와 연결해서 데이터를 복사하는 작업.

2. 포크한 원격 저장소를 내 컴퓨터와 연결해서 데이터를 복사하는 작업.


앞선 포스팅에서 만들었던 http 주소를 가져옵니다.


그리고 앞서 블로그에서 설치했던 GIT BASH를 실행 해주십다. 

$ mkdir git_remote

$ cd git_remote

그리고 clone 명령을 해서 땡겨옵니다.

$ git clone https://github.com/pjh2174/myTistoryExam.git


$ cd myTistoryExam

$ git status


status 명령을 치면, 현재 아무것도 커밋되어있지않고, clean한 디렉토리임을 알 수 있습니다.



Git remote: 로컬저장소와 원격저장소를 연결합니다.

앞부분에선 cloen을 통해 생성했던 원격저장소를 로컬저장소로 파일을 복사했습니다. 하지만 뭔가 모순된점이 있을 수 도 있는데요.

만약 유저가 이미 로컬에서 작업했던 내용이 있는 상태였다면, 원격저장소에있는걸 clone할필요없이 바로 연결해서 올려야하는 절차가 필요할 수도있습니니다. 그렇기때문에 일단 깃허브에서 원격 저장소를 임의로 생성해봅니다.

$ git remote add origin https://github.com/pjh2174/myRemoteExam.git

저는 이전블로그에 hello.py 가있는 브랜치로 접근하여 위의 명령어를 쳤습니다.

원격 저장소와 연결되었는지 호가인하려면 아래와 같이 명령을 치도록합니다.


$ git remote -v



git push: 로컬 작업 내역을 원격 저장소에 올립니다.


이제 원격저장소와 로컬 저장소가 연결되었습니다. 이젠 원격저장소에 자신의 결과물을 업로드 해야할 차례입니다. git push라는 명령어를 사용하면 수월하게 진행하실 수 있습니다.


$git push


음 거절당하게 될겁니다. 어느 원격 저장소로 발행할 것인지, 로컬의 어느 브랜치에 푸시할 것인지 명시하지 않았기 때문입니다.


$git push origin --all 


명령어를 자세히 분석해보겠습니다.

$ git push 원격저장소별칭 로컬브랜치이름 

위에서 실행한 -all은 origin 저장소에 로컬의 모든 브랜치를 푸시하는겁니다. git은 원격 저장소에 로컬 저장소의 브랜치와 같은 이름의 브랜치가 있다면 해당 브랜치를 변경하고 없다면 새브랜치를 원격 저장소에 만듭니다. 단 주의할점은, 같은 이름의 브랜치가 있는데 서로의 내역이 다르다면 푸시를 거부하게 됩니다. 


아무튼 명령을 수행하면 깃허브 로그인창이뜨게되고, 아이디와 비번을 입력합니다. 성공적으로 업로드가 수행되어집니다.

성공적으로 올라온것을 확인하실 수 있습니다.



원격저장소에 파일을 하나 추가한후 다시 내용을 푸시해보도록하겠습니다.


$ cat >> README.md

remote repository of myRemoteExam

$ git add README.md

$ git commit -m "remote repository add a README.md files"


자 다시한번 push해보겠습니다. 원격저장소 별칭(origin)과 master 브랜치에 push push 해보겠습니다.

$ git push origin master


역시 깃허브에 반영된것을 확인하실 수 있습니다. 추가된모습을 보실수 있으세요.


혹은 README.md 파일을 다른 브랜치에도 추가하고 싶다면, 로컬저장소에서 git push origin master 명령을 실행하기전에

로컬 저장소를 병합한 후, git push origin -all 형태로 명령을 실행하셔도 됩니다.

.



git fetch와 git pull: 원격저장소와 로컬 저장소의 간격 메꿉니다.


원격 저장소의 commit내역들을 Local 저장소로 가져와 병합하는 방법은 git fetch와 git pull  크게 두가지가 있습니다.

git pull 명령은 원격 저장소의 정보를 가져오면 자동으로 로컬 브랜치에 병합까지 수행하는 것입니다. 자동으로 병합되기때문에 어떤 점들이 변했는지 알아보기가 힘들 수 있습니다. 그러므로 git fetch를 사용하길 권고합니다.


git fetch와 git pull을 이용해보기위해 실습순서를 아래와 같이 진행할 것입니다.


1. 원격저장소, github에서 파일 수정합니다.

2. 로컬저장소에서도 같은파일 수정합니다.

3. push 시도와 실패합니다.

4. fetch 실시합니다.

5. 병합시도합니다.

6. 푸시 재시도를 할것입니다.

7. 원격저장소에서 제대로되었는지 확인할 것입니다.



github로 가서, hello.py 파일을 변경합니다. (원격저장소에서 commit 한다는 의미입니다.)



자이제 로컬저장소에서도 hello.py를 수정해봅시다.

그리고 아래와 같이 로컬에서 커밋해버립니다.

$ git commit -a -m "hello.py modified in local"


그리고 push를 원격저장소에 해봅니다.

$ git push origin master       master 로컬 브랜치의 내용을 origin 원격저장소에 push하겠다.


하지만 오류를 내면서 git pull 명령을 힌트로 알려줍니다만 별로 유용하지않습니다.


우리는 git fetch를 사용할것입니다.


$ git fetch

$ git status

뭐 큰이상 없는것같습니다.


$ git branch -a 

위의 명령을 치면 현재 어떤 브랜치가 있는지 알 수 있습니다. 모든 로컬저장소와 원격 저장소의 브랜치정보를 볼 수 있습니다.


$ git merge origin/master

위의 명령을 치면, 충돌이 발생했다고 출력합니다.


이제 충돌을 해결해봅시다.

git diff 브랜치이름 명령을 이용해봅시다. git diff는 로컬저장소의 브랜치와 원격 브랜치 저장소 사이에 어떤 차이점이 있는지 미리 알아보는 명령입니다.

만약 git pull명령을 이용했다면 페치와 병합을 자동으로 수행해버려서 어떤 변경사항이있는지 알기가 매우어려워집니다.

그리고 이제 로컬에서 원하는 방향으로 다시 수정해봅시다. conflict을 해결하는 방향으로요!.


그리고 다음 명령과 같이 커밋합니다.

$ git commit -a -m "conflict resolved github"
$ git push origin master


이제 제대로 병합된것을 확인하실수있습니다.


또한, insights -> graphs -> Network를 누르시면, 지금까지 작업과정을 확인하실 수 있습니다.



만약 위의 과정에서  git pull origin master 이런식으로 명령어를 쳤다면, 변경내용을 가져오고 자동으로 병합해버립니다. 그러므로 그상태에서 git commit 과 git merge명령을 실행해도 이미 작업할 것이 없다는 메세지를 확인하실 수 있습니다.



추천드리자면, fetch를 통해 원격저장소의 커밋을 가져오고 로컬저장소에서 이를 확인한 다음 수동으로 병합하는 방법을 가장 추천드립니다.




Android application Test 방법


크게 2가지가 있습니다. 안드로이트 테스트(Instrumentation Test)와 Local PC의 JVM을 활용하는 새로운 개념의 Local Unit Test 입니다. 


참고) 로컬 유닛 테스트는 로컬 JVM을 활용하기 때문에 타깃 디바이스와 연결피 필요없습니다. 그리고 테스트 코드의 전체 실행속도가 향상되는 효과가 있습니다. 안드로이드 테스트에서는 APK를 생성하여 타깃 디바이스에 설치하고 실행하는 과정에서 시간 소모가 많은 편입니다.



Gradle을 활용화여 안드로이드 테스트 코드와 로컬 유닛테스트코드를 실행하는 방법을 알아보겠습니다.




로컬 유닛 테스트

app module의 build.gradle을 보시겠습니다. dpendencies부분에 아래와 같이 testCompile이 웬만하면 디폴트로 지정되어있으실겁니다.


dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
}

그리고 ExampleUnitTest 클래스를 보시면 아래와 같이 코드가 미리작성되어있는것을 확인하실 수 있습니다. Junit4에서는 테스트코드에 @Test라는 애너테이션을 부착합니다.

public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

로컬유닛 테스트 코드를 실행하는 방법은 간단합니다. 패키지명을 우크릭하고 "Run Tests in 프로젝트명" 클릭하면 Run창에 테스트 결과가 출력됩니다. 

해당 unitTest 클래스 오른쪽 마우스로누르고, run "ExampleUnitTest" 선택시 정상적으로 테스트됩니다.


로컬유닛테스트를 실행할때 호출되는 중요한 Gradle Task는 다음과 같습니다.


:app:mockableAndroidJar UP-TO-DATE

:app:compileDebugUnitTestSources UP-TO-DATE


1. mock(app:mockableAndroidJar ) 테스크는 안드로이드의 android.jar 파일을 mock 인터페이스로 컴파일합니다. 로컬 유닛 테스트가 동작하려면 안드로이드 코드에 대한 mock 인터페이스가 필요합니다. mock 인터페이스 호출 시, 가짜객체를 주입하는 경우도 있습니다. 가짜 객체(mock object)를 이용한 테스트 기법은 구글문서를 참고하십시오.


mock 테스트 실행결과는 /build/generated/mockable-android-26.jar(26은 버전명) 입니다. 


2. complieDebugUnitTestSources 태스크입니다. 태스크 이름자체대로 디버그 모드로 로컬 유닛 테스트 코드를 컴파일합니다. 릴리즈 모드일때는 릴리즈 이름에 맞게 테스크가 실행되는것을 확인하실 수 있습니다. 



또한, 콘솔에서도 테스트코드를 실행하실 수 있습니다. HTML로 결과가 출력되는데요.  프로젝트 폴더에서 cmd창을 연 후,


> gradlew :app:testDebug 

와같이 치신후 엔터를 누르고, app/build/reports/tests/debug 폴더에 테스트 결과를 확인하실 수 있습니다.




안드로이드 테스트

안드로이드 테스트코드는 Activity, Fragment, ButtonView, TextView 등 UI컴포넌트와, 유틸리티클래스와 같은 Java클래스 모두 테스트할 수 있습니다. 다만 에뮬레이터나, 타깃 디바이스에서 실행해야하는 제약이 있습니다 .

저는 MainActivityTest라는 클래스를 만들고 아래와 같이 코드작성 후, 실시해보겠습니다.

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
public MainActivityTest() {
super(MainActivity.class);
}


public void testHelloLabel(){
Activity ac = getActivity();

}

app 모듈의경우 /app/src/androidTest/java 폴더에 위치하고있습니다. 실행하는 방법은 로컬 유닛테스트와 유사합니다.

오른쪽마우스 누르고, run "MainActivityTest" 를 실행합니다.


:app:compileDebugAndroidTestSources

이번에 실행된 gradle test는 unitTest가 아닌, AndroidTest로 바뀐것을 확인하실 수 있습니다.


콘솔에서도 역시 확인하실 수 있습니다.

gradlew :app:connectedAndroidTest + 엔터 치시면됩니다.gradle 콘솔창에 뜨듯이 진행후, 완료구문을 확인하실 수 있습니다.



.


Espresso 연동하기

Espresso는 Android Testing Support Library에 편입되었습니다. 강력한 테스팅 도구이죠. 연동하는 방법을 살펴보시겠습니다.


app모듈의 build.gradle의 내용 추가입니다.


defaultconfig{


  testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}


그다음 denependencies블록에 추가할 내용입니다.


androidTestCompile 'com.android.support.test:runner:0.4.1'

androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'


compile 'com.android.support:appcompat-v7:23.0.1'




로컬 유닛 테스트의 제약사항

로컬 유닛테스트 같은 경우 andoir.util.Log 클래스에 대한, mock 인터페이스가 제공되지안항 오류를 낼때가있습니다.

이를 피하려면  모듈 build.gradle에 다음 내용을 추가해야합니다.


android{

    

   testOptions{

unitTests.returnDefaultValues = true

   }


}

근본적으로 해결하려면 PowerMock과 같은 라이브러리를 활용하여 log 클래스의 static 메서드를 위한 가짜 객체를 주입하는 방법이 있습니다. PowerMock 관련 문서를 참조하시기 바랍니다.





들어가기 앞서

 Gradle 에서는 프로젝트를 단일 프로젝트나 멀티 프로젝트로 구성할 수 있습니다. 멀티프로젝트는 하위 폴더에 여러개의 Module을 추가할 수 있습니다. Android Studio 는 프로젝트를 생성하면 자동으로 멀티 프로젝트로 구성되고, 하위에는 App Module이 추가되어 있습니다. 



Android Library Module 만들기
 안드로이드 라이브러리 모듈은 com.android.libaray 플러그인이 필요하고, 그결과 AAR파일을 생성 할 수 있습니다. 
AAR파일은 JAR파일과 비슷해보일 수 있지만 다른점은, 하나의 앱처럼 안드로이드 화면을 포함할 수 있다는 점입니다. JAR 파일은 순수 로직만 포함할 수 있습니다. 하지만 AAR파일은 res폴더의 xml 이미지, 리소스등을 포함할 수 있기때문에 안드로이드 화면을 표현할수 있게 됩니다. 

다음은 안드로이드 라이브러리 모듈을 생성하는 과정입니다. 안드로이드 스튜디오에서 [File -> New -> New Module]을 선택합니다. 그 이후 안드로이드 라이브러리 모듈을 선택하고 이름은 MyLibModule이라고 하겠습니다.

그리고 build.gradle에 settings.gradle에 보시면 include된것을 확인하실 수 있습니다.
include ':app', ':mylibmodule'
또한 build->makd module 'mylibmodule' 을선택하면 aar파일이 생성되는것을 outputs 폴더에서 확인하실 수 있습니다.


Gradle 로컬 저장소 만들기에 앞서
 현업에서 상용프로젝트를 개발할때, 라이브러리를 gradle을 이용해서 참조해서 사용하는 경우가 있습니다. 그런데 jcenter나 maven center에 올려버리면 보안상 문제가 생길 우려가 엄청납니다. 그러므로 사내에 로컬 저장소를 만들어서 운영해야 할 것입니다.


Gradle 로컬 저장소 만드는 방법
 Gradle에서 로컬 저장소를 지정하는 방법을 배워보겠습니다. 
공동작업을 위해서는 프로젝트 폴더 하위에 로컬 저장소를 두는것이 일반적입니다. 먼저 프로젝트 홈폴더를 환경변수에 등록하겠습니다. 

ORG_GRADLE_PROJECT_HOME 환경변수는 gradle 스크립트에서 $PROJECT_HOME으로 참조할 수 있습니다.

다음과 같이 Project의 build.gradle을 변경합니다.

allprojects{
   reposotories{
     jcenter()
     mavne{
      url "file://($PROJECT_HOME)./myReposiroy"
     }
   }

}


참조할 수 있는 로컬 저장소가 생성되었습니다. URL은 위의 "file://($PROJECT_HOME)./myReposiroy" 와 같습니다.

그다음 mylibmoudle을 빌드한 결과를 로컬 저장소에 업로드할 수 있도록 uploadArchives 블록을 정의합니다.

mylibmoudle 모듈의 build.gradle 파일 아래쪽에 다음 내용을 추가합니다.


apply plugin: 'maven'

group ='com.exam.mylibmodule'

version = '1.0'


uploadArchives {

  repositories{

     mavenDeployer{

         repository( url: "file://($PROJECT_HOME)./myReposiroy")

      }

   }

 

}


업로드할 저장소의 URL과 그룹이름, 버전을 지정하면됩니다. group, version, repository url을 확인하실 수 있습니다.


uploadArchives 태스크를 실행해야합니다. 해당 프로젝트 폴더로 가서 gradlew :mylibmodule:uploadArchives 라고 치시면, 해당 태스크를 실행하고 .aar파일이 지정했던 repository에 생성되는것을 확인하실 수 있습니다. 




마지막으로 로컬저장소에 배포된 .aar파일을 참조하도록 하겠습니다.

app모듈에서 build.gradle을 아래와 같이 변경하면 되겠습니다.


dependencies{

  compile 'com.exam.mylibmoudle:mylibmodule:1.0@aar'

}


이제 app moudle에서 library module 의 액티비티를 직접 실행하실수있습니다. 라이브러리르 모듈을 참고하기 때문이죠.

이상입니다.




- 들어가기 앞서.

 안드로이드 앱을 개발하다보면, 다양한 외부라이브러리를 참조해야할 경우가 발생합니다. 이를 프로젝트에서의 dependency(의존성) 이라고 합니다. 과거 이클립스의 경우에는 라이브러리 파일을 다운로드하여 프로젝트의 libs 폴더에 복사하고 다시지정하는 과정이 매우 번거로웠습니다. 하지만 gradle 에서는 이런 문제점을 해결하였습니다. 그만큼 gradle은 이전에 문제가 되었던 많은 부분들을 보완한것을 알 수 있습니다.



- 가장 기본적 Gradle 외부라이브러리 참조 방법

 app module의 dependencies 블록에 ㅂ외부 라이브러리를 지정합니다. 외부 라이브러리를 지정할때 compile 명령을 사용합니다. 

http://fsd-jinss.tistory.com/36 앞서 생성했던 안드로이드 그래들 프로젝트에서의 app module gradle dependencies 부분을 살펴 보시겠습니다.

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) //1
testCompile 'junit:junit:4.12'                                     //2
compile 'com.android.support:appcompat-v7:26.0.0-alpha1'            //3
}


1. libs폴더의 JAR파일을 추가하는 방법입니다.

2. 테스트 빌드시 참조하는 라이브러리 입니다.

3. 로컬에 없는, 외부라이브러리를 참조합니다. Gradle 에서 외부 라이브러리를 참조하기 위해서는 다음 세가지 정보가 필요합니다.


- group Id : 라이브러리 패키지 이름이 필요합니다. (com.android.support)

- arifact id : 라이브러리 이름이 필요합니다. (appcompat)

- version   : 참조하는 버전명이 필요합니다. (v7:26.0.0-alpha1)




다른 모듈 소스코드 참조하기


아래는 라이브러리 모듈(library module, app module 말고요)의 소스코드 참조하는 방법입니다.

dependencies {

  compile project(':mylibrary')

}


다음은 하위폴더에 위치한 라이브러리 모듈의 소스코드를 참조하는 방법입니다.

lib_modules 폴더 하위에 mylibrary가 위치한다면 다음과 같이 기술합니다.

dependencies {

  compile project(':lib_modules:mylibrary')

}


디버그 모드와 릴리즈 모드 참조하기

 dependencies {

 debugCompile 'com.abcd.test:myTest-android:1.3'

 releaseCompile 'com.abcd.test:myTest_release-android:1.3'

}

디버그 컴파일과 릴리즈 컴파일로 구분합니다.



안드로이드 유닛 테스트 참조하기

dependencies {
androidTestCompile 'junit:junit:4.12'
}

참고로 androidTestCompile은 안드로이드 UI 테스팅할때 사용하는 명령어로, testCompile과 구별됩니다.



공개라이브러리 참조하기 - JAR 파일

HTTP 클라이언트와 웹소켓 클라이언트를 지원하는 AndroidSync 참조 예입니다. github에 Gradle에서 간편하게 추가하는 방식을 소개하고있습니다.

 dependencies{

 compile 'com.koushikdutta.async:androidasync:2.+'

}



공개라이브러리 참조하기 - AAR 파일

AAR파일을 참조해야하는 경우가 있습니다.  @aar을 끝에 붙여주면 됩니다.


dependencies{

  complie 'com.abcd:andrid-xxx:1.0.1@aar'

}



로컬에 있는 AAR파일 참조하기

로컬에 AAR을 복사하여 참조하는 경우도 있을 수 있습니다. 이럴때는 libs 폴더에 복사후 gradle을 통해 참조할 수 있습니다. 

이때 module build.gradle 파일의 dependencies 블록만 변경해서는 안되고, repositories 블록도 함께 변경해야 합니다.

repositories{
flatDirs{
dirs 'libs'
}
}


다음 예는 mylibrary 모듈의 패지 이름과 aar파일 예시이름 버전 1.0으로 로컬에 있는 경우 예입니다. 

dependencies{

  compile 'com.example.mylibray:myTest:1.0@aar'

}

.



.so(JNI)파일 참조하기

so 파일을 참조해야하는 경우도 있습니다. libs폴더 하위에 x86, armeabi-v7a 등 폴더를 만들고 그에맞는 .so파일을 복사하면 됩니다. (JNI 참고하세요)

아래와 같이 작성하시면 되겠습니다.

sourceSets는 안드로이드 앱을 gradle로 빌드하기 위해 사전에 약속된 폴더 구조입니다. 메인소스의 jniLibs.srcDirs 항목을 libs로 지정합니다.


android {

 sourceSets{

    main{

     jniLibs.srcDirs = ['libs']

    }

  }


}


그 외의 경우.

Gradle은 외부라이브러리르 가져올때, 그라이브러리가 내부적으로 참조하는 다른라이브러리도 함께 땡겨옵니다. 이럴 때exclude group 으로 특정 참조그룹을 제외시킬 수 도 있습니다. 현재 프로젝트와 의도지않게 가져오는 라이브러리의 버전 충돌문제로 사용하는 편입니다.


compile('xxx.xxx.xx:2.2.2'){

 exclude group: 'com.xxx'

}


transitive 옵션을 줄 수 있습니다. aar을 붙여 참조하게되면 transitive 옵션은 false 로 변경됩니다. (aar의 경우 그런것 같습니다.)

명시적으로 true값을 준다면, aar 파일이 필요로 하는 라이브러리를 추가로 땡겨 오게 됩니다.


compile('xxx.xxx.xx:2.2.2'){

transitive = true or false

}



'개발 Support > Gradle' 카테고리의 다른 글

안드로이드 Gradle Test  (0) 2017.07.12
안드로이드 Gradle 멀티 프로젝트 사용  (0) 2017.07.10
Gradle Task와 생명주기  (0) 2017.07.10
Android studio Gradle 프로젝트 생성  (0) 2017.07.09
Gradle 개요  (0) 2017.07.08

시작하기 앞서

 Gradle의 기본단위는 Task 입니다. Android Studio는 쉽게 빌드하는 것처럼보이지만, 내부적으로 모두 Gradle Task가 동작합니다. Gradle Task와 Task의 생명주기에 관해 알아보도록 하겠습니다.


프로젝트 빌드하고 실행

 안드로이드 스튜디오에서 기본적으로 빌드하려면 build -> make project 하면 됩니다. 

안드로이드 스튜디오 오른쪽화면을 보면 Gradle Projects가 있습니다. Sync버튼을 누르면 Gradle 관점에서 모듈별 Task목록들을 보실 수 있습니다.

저는 GradeTest라는 프로젝트를 생성했었습니다. 생성할때 처음 기본 모듈이름은 app이라고 안드로이드 스튜디오에서 기본적으로 설정됩니다.

모듈은 콜론으로 구분합니다. 그래서 :app으로 되어있는것을 보실 수 있습니다. 


app 모듈의 build그룹을 열어서, assembleDebug를 더블클릭하여 실행해봅시다. 

앞으로는 프로젝트를 직접 빌드하기보다는 Module 단위로 빌드를 진행합니다. 멀티프로젝트에서는 각 Module이 빌드의 주체가 됩니다.

빌드가 성공된것읗 확인할 수 있습니다.

또한, Project이름 > app > build > outputs > apk 폴더에 빌드된 apk 파일을 확인하실 수 있습니다.

 

 




Gradle Task 개념

 Module의 개수가 늘어나면 안드로이드 Gradle 플러그인에서 제공하는 Task 외 새로운 Task 를 추가하여 활용하게 됩니다. Task의 개념을 알고있으면 유용하게 써먹을 수 있게 됩니다.


Task 에서 Log 출력

 sayHi라는 Task를 추가해보겠습니다. 또한 Hello MyGradle 문구를 출력하는 Task를 만들도록하겠습니다. 문자열 출력할때는  ''를 사용해도되고 ""를 사용해도됩니다. "" 같은경우는 내부변수를 문자열에서 참조할때 사용하기도 합니다.



안드로이드 스튜디오 프로젝트에서, app module(프로젝트 모듈말고!)의 build.gradle을 열고 task를 추가해봅시다.


task sayHi << {

   println 'hello MyGradle'

}


그리고 오른쪽 gradle project창에서 싱크버튼을 누릅니다. Other 그룹을 펼쳐보면 sayHi Task가 생성된것을 확인할 수 있습니다. 거기서 더블클릭하면 sayHi Task 가 실행됩니다. 아래와 같이 app:sayHi task가 실행되면서, hello MyGradle이 출력되는것을 확인할 수 있습니다.


참고로 위에서 작성한코드는 아래코드와 같은 코드입니다. doLast부분이 들어가는것을 주의깊게 보세요. 뒤에서 설명합니다.

task hello {
    doLast {
        println 'Hello world!'
    }
}



Task 그룹 지정

Gradle Task는 그룹을 가질 수 있습니다. 앞서만들었던 sayHi를 help 그룹에 편입시켜 보겠습니다.

task sayHi(group: "help")<< {
println 'hello MyGradle'
}

sayHi는 help그룹에 어울리않으니 다른 그룹을 생성해서 지정해보겠습니다. insa 그룹을 생성하고 지정했습니다.

def insa = 'insa'
task sayHi(group: insa)<< {
println 'hello MyGradle'
}


sayHi Task가 insa 그룹에 편입된것을 확인하실 수 있습니다.


Task 설명 지정하기

Task가 어떤일을 하는 지 설명을 해야할 필요가 있겠죠? description을 넣고 설명을 달았습니다.

def insa = 'insa'
task sayHi(group: insa, description: "my insa is hello")<< {
println 'hello MyGradle'
}

Gradle 내장태스크 tasks를 실행하면 다음과 같이 설명글을 볼 수 있습니다.

insa 그룹의 tasks들이 출력되고, sayHi Task의 설명을 확인하실 수 있습니다.


Insa tasks

----------

sayHi - my insa is hello

.



Task에 dependency 지정하기

 어떤 task를 내 task 보다 먼저 실행되도록하려면 dependsOn 이라는 속성을 지정하면 됩니다.

meetPerson Task를 먼저 실행하고 sayHi Task를 진행하도록 하겠습니다.

def insa = 'insa'
task sayHi(group: insa, description: "my insa is hello",
dependsOn: 'meetPerson')<< {
println 'hello MyGradle'
}

task meetPerson(group: insa) << {
println "I met person"
}

동기화를 해보고 sayHi를 실행해보겠습니다.

meetPerson Task 실행되고, sayHi Task가 실행되는것을 확인하실 수 있습니다.



Gradle 생명주기

Gradle 생명주기는 초기화 단계, 설정 단계, 실행 단계로 구분됩니다.



초기화단계

 프로젝트 build.gradle에 해당하는 Project Instance를 생성합니다. 앞서 봤던 buildscript, allprojects 블록등도 포함됩니다.



설정단계

 Project객체의 detail한 값을 설정합니다. 하위 프로젝트에 공통으로 적용되는 내용은 앞선 포스팅에서 설명했던 allprojects() 을 통해 전달됩니다. 사용자 정의 task의 경우 doFirst()에 넣은 내용이 이때 실행됩니다.



실행단계

설정 단계를 마치고, 각 Task는 이제 실행되기만 하면됩니다. 사용자 정의 Task 의 경우 doLast() 또는 <<에 넣은 내용이 이때 실행되게 됩니다. 


만약 sayHi task에서 doFirst()와 doLast()를 각각 새로 정의했다면 doFirst()에 넣은 내용은 설정단계에서, doLast()에 넣은 내용은 실행단계에서 실행됩니다.

def insa = 'insa'
task sayHi(group: insa) {
doFirst{
println 'Look at me'
}
doLast{
println 'hello MyGradle'
}

}


:app:sayHi

Look at me

hello MyGradle


BUILD SUCCESSFUL



Look at me가 실행되고 Hello MyGradle이 출력되는것을 확인하실 수 있습니다.
참고로 위의 코드에 dependsOn: 'meetPerson' 을 넣으면, 

meetPerson Task가 실행되고, Look at me 다음 hellot MyGradle이 실행됩니다. 






+ Recent posts