TDD라기 보다는 테스트를 꼼꼼히 한번 짜봤다고 해야 할까. 좋은 점은 안정감이다. 코드를 여러군데 수정해도 테스트 코드가 있으니 안심이 된다. 과감하게 리팩토링을 할 수 있게 됐다. '테스트 없이는 리팩토링도 없다’는 말을 몸소 체득하게 된 것. 앞으로 나는 테스트 없이 개발을 하지 못할 것 같다.

왜 이렇게 테스트를 짜는데 망설여 왔을까? 테스트의 중요성에 대해서는 누누이 들었다. 여기저기서 읽기도 하고 신입교육 때도 들었다. 스프링 개발을 할 때는 어설프게 짠 적이 있었지만 커버리지가 매우 낮았다.

이유는 내가 앱 개발을 하고 있기 때문인 것 같다. 급박한 일정에 쫓기다보면 뷰 컨트롤러가 비대해지기 마련인데 이 경우 테스트 코드를 짜기 쉽지 않다. 뷰를 처리하는 로직이 다른 것과 섞여 그렇다. 

결과적으로 테스트 코드를 짜고자 하는 노력은 어떻게 하면 SOLID 원칙을 충실히 따르면서 구조화된 코드를 짤 수 있을까 하는 고민으로 이어졌다. Clean Code(로버트 마틴), Refactoring(마틴 파울러), 구현 패턴(켄트 백) 등의 서적을 열심히 탐독하고 있다. 여기에 헤드 퍼스트 디자인 패턴도 같이 읽고 있다. 


로버트 마틴의 Clean Coder에서는 TDD의 세 가지 법칙을 다음과 같이 소개한다.

1. 실패한 단위 테스트를 만들기 전에는 제품 코드를 만들지 않는다.
2. 컴파일이 안 되거나 실패한 단위 테스트가 있으면 더 이상 단위 테스트를 만들지 않는다.
3. 실패한 단위 테스트를 통과하는 이상의 제품 코드는 만들지 않는다.

실무에 적용해보니 이는 매우 유용하다.

-

같은 책에서는 확신, 결함 주입 비율, 용기, 문서화, 설계 등을 TDD의 장점으로 꼽는다. 내가 이미 느끼는 바가 책에 쓰여 있어 반가웠고 더욱 확신이 들었다. 

특히 설계에 있어 많은 효용을 가져다주는 것 같다. 테스트를 하기 위해서는 함수가 public 상태로 실행될 수 있어야 한다. 이를 만족시키기 위해서 고민하다 보면 자연스레 설계가 좋아지는 것 같다.



앱을 업데이트하고 올렸더니 다음과 같은 메시지가 나왔다.

"zip 정렬되지 않은 APK를 업로드했습니다. APK에 zip 정렬 도구를 실행한 다음 다시 업로드해야 합니다."

stackoverflow를 검색해보니 zipalign을 사용하면 된다고 나와 있었다. zipalign이 무엇인지는 문서에서 확인할 수 있다. 

sdk 폴더의 build-tools로 이동한 다음 빌드에 사용한 gradle plugin 버전으로 들어간다. 여기에 zipalign이 위치해 있다.

zipalign -v 4 foo.apk bar.apk

다음과 같이 입력해주면 되지만… 결과는 fail이었다. 마지막에 다음과 같이 찍혔다.

4413714 resources.arsc (BAD - 2)
Verification FAILED

구글링을 해보니 명확한 원인은 찾을 수 없었고 수동으로 signing을 하면 된다고 하는 글이 있었다.

수동 사인은 다음과 같이 할 수 있다.

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore [keystore file] foo.apk [alias]

여기서 alias가 뭔지 고민했는데 다음 명령어를 통해 확인할 수 있다.

keytool -keystore [keystore file] -list -v

이번에는 다음 메시지가 나온다.

jarsigner: unable to sign jar: java.util.zip.ZipException: invalid entry compressed size

이는 이미 사인되어 있는 apk일 경우에 나오는 메시지라 한다. 사이닝 정보다 담기는 META-INF 폴더를 다음 명령어로 지운다.

zip -d foo.apk META-INF/\*

자 이제 올려볼까? 이젠 디버그 모드로 되어 있다고 하면서 안 된다. 마지막으로 menifest에 다음을 추가한다.

android:debuggable="false"

된다… 힘들었다


'Tech > Android' 카테고리의 다른 글

SyncAdapter는 무엇이죠?  (0) 2016.05.10
UNEXPECTED TOP-LEVEL EXCEPTION Multiple dex files define  (0) 2015.09.09
AsyncTaskLoader는 무엇이죠?  (0) 2015.08.14
optional을 쉽게 설명해보자.

물음표 뜻 그대로 '없을 수도 있어’라는 뜻이다.

Objective-C에서는 값이 없으면 nil을 return하는 방식으로 이를 구현했는데 이는 NSObject를 상속한 개체만 가능했기 때문에 한계가 있었다. structure, enum 등까지 모두 포함해 ‘값이 없음’을 표현하게 위해 optional이 도입됐다.

만약 var number: Int?라고 선언하게 된다면 number는 nil 상태로 초기화된다.

이렇게 선언한 number를 그냥 쓰게 되면 값이 있을 수도 있고 없을 수도 있기 때문에 컴파일 에러가 발생한다. xCode는 느낌표를 쓸 거냐고 물어본다. 이는 force unwrapping인데 값이 무조건 있다고 가정하고 넘어가자는 것이다.

만약 값이 없으면 크래쉬가 발생하기에 위험하다. 이를 막기 위해 optional binding을 쓴다.

if let actualNumber = Int(possibleNumber) {
     "print("\"\(possibleNumber)\" has an integer value of \(actualNumber)”)"
}

이는 "만약 Int(possibleNumber)가 반환한 Int가 값을 가지고 있으면 이를 새로운 변수인 actualNumber에 할당해라"는 뜻이다.

guard let을 사용할 수도 있다.

guard let rowCount = branch?.services?.count else {
     return 0
}

guard는 if와 비슷하다. 조건 구문의 boolean 값에 따라 statements가 실행된다. 다른 점은 늘 else가 있다는 것이다.

if와 달리 예외 처리에 주로 사용된다.

-

optional에 대해 한번 정리하는 차원에서 글을 썼다. 사실 앱 개발을 공부하는데 있어서 모든 레퍼런스는 집어 치우고 공식 문서만 보면 된다(고 고수들이 그랬다).

'공식 문서를 보면 되는데 왜 나는 쓰고 있지?'란 생각이 가끔 들었다. 사실 나는 학습에 외국어보단 모국어가, 글보단 영상이 효과적이라고 생각한다. 요즘엔 멍 때리면서 tryhelloworld 보는 게 낙이다. 

이건 공식 문서와 달리 한글로 쓰여있기 때문에 1g의 의의를 지닐 수 있을 것이다.


'Tech > iOS' 카테고리의 다른 글

스위프트 타입 호환성  (0) 2017.01.11
[Swift] Class와 Struct 중에 뭘 쓸까?  (0) 2016.10.28
@noscape, @autoclosure  (0) 2016.10.24
Hashable Protocol  (0) 2016.02.28

+ Recent posts