Android 크기 지정에 사용할 수 있는 단위

기본

http://www.androidpub.com/83442

Advertisements

xUnit Example ToDo

기본

Goal

  • 테스트 프레임워크 만들기

ToDo

  • 테스트 메서드 호출하기
  • 먼저 setUp 호출하기
  • 나중에 tearDown 호출하기
  • 테스트 메서드가 실패하더라도 tearDown 호출하기
  • 테스트 여러 개 실행하기
  • 수집된 결과를 출력하기
  • WasRun에 로그 문자열 남기기
  • 실패한 테스트 보고하기
  • setUp 에러를 잡아서 보고하기
  • TestCase 클래스에서 TestSuite 생성하기

Reference

Test-Driven Development 1부 리뷰

기본

TDD는 총 3부로 나눠지며, 첫 번째 챕터는 다중 통화 산술 프로그램을 TDD로 구현하는 내용입니다. 1부는 마치 켄트 벡의 코딩을 옆에서 지켜보는 것 같은 느낌이 들었습니다. 저자의 사고 과정, TDD의 흐름을 느껴볼 수 있죠.

이 포스트에선 책에서 나왔던 중요한 개념에 대해서 정리해 보려고 합니다.

TDD?

다음 두 가지가 TDD의 코어 룰입니다.

  1. 어떤 코드건 작성하기 전에 실패하는 자동화된 테스트를 작성하라.
  2. 중복을 제거하라.

1번 룰에서 성공하는 테스트가 되기 위해선 모든 죄악(하드코딩, 복사 붙이기 등)을 저질러도 됩니다. 단, 2번 룰에서 리팩토링(기능의 변화 없이 설계를 개선시키는 과정)을 통해 구원 받아야 합니다.

2번 룰: 중복

의존성(dependency)이 없다면 프로그램이 동작할 수도 없겠지만 그렇다고 의존성이 커지면 변경의 여파가 너무나 커져 버립니다. 그래서 의존성을 최고화 하는게 관건이고, 이 의존성이 높아지는 징후가 바로 중복(duplication)입니다.

따라서 중복을 제거해 의존성을 최소화 시킵니다.

TDD의 테스트

김창준님의 블로그에 있는 포스트 중 켄트 벡이 대답하길 2탄에 TDD의 테스트는 설계를 위한 것이다라는 문장이 있었습니다. 어제 리포트 하느라 다이어그램 그리면서 느낀건데 객체간 이런 메시지를 주고 받는다라는걸 도형으로 추상화 시킨게 다이어그램이라면 그런 추상화를 코드로 옮겨 놓은게 바로 TDD의 테스트가 아닐까 싶더라구요.

Todo

책에서 코딩하기 전 할 일을 To Do 리스트에 추가해서 관리합니다. 이것의 중요성은 매번 코딩할 때마다 느껴지더라구요.

이클립스에선 취소선이 안그어져서 뭔가 성취감이 없어요. 그래서 아예 todo 카테고리를 하나 팠죠. 이거 늘려가는 재미도 꽤 있을 것 같아요.

Once and only once (필요한 것을 하되 단 한 번만 하라)

예제를 진행하다 보면 한 호흡이 너무 짧아 보입니다. 그에 대해서 켄트 벡이 하는 말은 이렇습니다.

TDD의 핵심은 이런 작은 단계를 밟는 것이 아니라, 이런 작은 단계를 밟을 능력을 갖추어야 한다. 만약 정말 작은 단계로 작업하는 방법을 배우면, 저절로 적절한 크기의 단계로 작업할 수 있을 것이다.

죄악을 효과적으로 저지르는 방법

최대한 빨리 초록색 막대를 보기 위해 취할 수 있는 전략 세 가지.

  • 가짜로 구현하기: 상수를 반환하게 만들고 단계적으로 상수를 변수로 바꾸어 간다.
  • 명백한 구현 사용하기: 먼저 이상적인 설계를 정의해 놓고 거꾸로 구현해 나간다.
  • 삼각측량: 두 개의 테스트 예제를 가지고 구현을 일반화 시킨다.

켄트 벡은 명백한 구현을 사용해 구현하다가 막히는 부분이 있을 때 가짜로 구현하기 방법을 사용하고 어떻게 리팩토링(또는 설계) 할지 전혀 감이 오지 안 올 때만 삼각측량을 사용한다고 합니다. 삼각측량은 문제를 조금 다른 방향에서 생각해볼 기회를 제공해 줍니다.

TDD를 잘하는 방법

중복이 사라지기 전에는 집에 가지 않겠다고 약속한다.

ㅎㄷㄷ

테스트 작성 기준

적절한 테스트를 갖추지 못한 코드에서 TDD를 하는 경우 리팩토링을 하면서 실수를 해도 여전히 테스트가 통과될 수도 있습니다. 이때 어떻게 해야할까요?

있으면 좋을 것 같은 테스트를 작성하라.

테스트 인셉션

테스트 작성 도중 다른 테스트가 필요할 때가 있습니다. 이때 교리상으론 기다리는게 맞지만 켄트 벡은 작은 작업일 경우 하던 일을 중단하고 그 작업부터 먼저 끝낸다고 합니다. 단, 하던 일을 중단하고 다른 일을 하는 상태에서 그 일을 또 중단하지는 않는다고 합니다. 꿈은 2단계 까지만.

옳고 그름

코드가 맞았는지 틀렸는지 하나하나 따라가 보면서 알아낼 수도 있지만, 책에서 그것이 잘 작동할지는 테스트가 말하게 하라고 합니다. 그편이 쉽죠.

테스트 라이프사이클

기존의 소스 구조에선 필요했지만 새로운 구조에서는 필요 없게 된 테스트는 과감히 제거합니다.

테스트하기 쉬운 코드

켄트 벡은 핵심이 되는 객체가 다른 부분에 대해서 될 수 있는 한 모르도록 노력한다고 합니다. 그렇게 해야 핵심 객체가 가능한 오랫 동안 유연할 수 있고 테스트 하기도 쉬울 뿐만 아니라 재활용하거나 이해하기도 모두 쉬워지기 때문입니다.

결국 디자인 원칙을 지키는 코드가 테스트 하기도 쉽다는 말이겠죠.

보폭의 크기

개인적으로 좀 이상하기도 하고 신기하기도 했던 부분이 구체 클래스에서 중복되는 메소드를 제거하기 위해 슥 상속을 하는 부분과 시그니처가 다른 메소드를 다형성을 통해 같은 시그니처로 만들어 하나의 인터페이스로 만드는 부분 등 기존에 생각지 못했던 작은 단계를 밟는 부분이었습니다.

이런 작은 단계가 단지 일을 작게 나눠서 진행하는게 아니라 무언가 더 구체적인 흐름이나 원리를알아야 할 수 있는게 아닌가 싶었고, 테스트를 성공하기 위한 세 가지 전략에 대해서도 익숙해져야 할 것 같습니다.

Test-Driven Development 리뷰를 시작하며…

기본

일반 개발자가 작동하는 깔끔한 코드(clean code that works)를 얻기 위한 가장 쉬운 방법은?

테스트주도 개발 표지

테스트주도 개발, Kent Beck

누군가 이런 질문을 한다면 저는 TDD라고 답하겠습니다. 물론 TDD가 쉽다는건 아니고 어떤 정량적인 측정 방법으로 자신의 실력을 확인하는건 불가능 하겠지만 TDD를 사용하기 이전과 이후는 확실히 무언가 달라졌다 라고 생각합니다.

특히 어느 부분이 달라졌냐고 물어본다면 바로 코드에 대한 확신이라고 하겠습니다.

저는 처음으로 100라인이 넘게 코딩한 프로그램이 뭔지 기억합니다. 불과 1년도 지나지 않았으니까요. 그런데 그 프로그램에는 테스트라는게 없었습니다. 그냥 main() 에서 실행해서 하나하나 눌러보는게 전부였었죠. (GUI 프로그램이었거든요.)

나중에 프로그램이 너무 복잡해 져서 어느 부분이 어떻게 돌아가는지 모를 지경이 됐을 때 제가 한 첫 번째 행동은 변수 이름을 바꾸는 것이었습니다. 지금 생각해보면 이게 리팩토링의 시작이었던 것 같네요. 그리고 또 직접 돌려보면서 혹시 꼬인게 없는지 하나하나 테스트를 했더랬죠.

그렇게 main() 에서 수없이 프로그램을 돌려봤음에도 불구하고 마지막에 교수님께 제출할 때까지 얼마나 초조했는지 모르겠습니다.

시간이 흘러 JUnit을 접하게 됐고 지금은 TDD를 공부하고 있습니다. 지금 1년 전과 달라진 것은 적어도 테스트를 작성한 부분에 있어선 그런 초조함이 없다는 겁니다. 왜 그럴까요? 틀리면 틀렸다고 맞으면 맞다고 대답해줄 자동화 테스트 프레임워크가 있기 때문입니다. 테스트에 헛발질을 한게 아니라면 JUnit이 맞다면 맞는거죠. 틀렸다고 하면 고치면 됩니다. 뭐가 걱정인가요?

TDD는 테스트를 먼저 만들면서 프로그램을 구체화 시키는 방법론 입니다. 장점은 일단 테스트가 많으니까 견고하다는 거겠죠. 단점은 일반적인 방법을 거스르기 때문에 어렵다는 겁니다. 추가로 테스트 툴이나 라이브러리에 대한 어느 정도의 학습이 필요합니다. 잘못하면 배보다 배꼽이 더 큰 격일지도 모르죠. 그런데 그건 실무하는 사람들 얘기고 저같은 학생은 좋은 습관을 들일 수 있는 훌륭한 방법론이라고 생각합니다.

리뷰하면서 수필 쓸 생각은 없기 때문에(…) 차차 리뷰를 진행하면서 TDD에 대해 간단히 정리해 볼 생각입니다.

Multi-currency Arithmetic ToDo

기본

Goal

  • 통화가 다른 두 금액을 더해서 주어진 환율에 맞게 변한 금액을 결과로 얻을 수 있어야 한다.
  • 어떤 금액(주가)을 어떤 수(주식의 수)에 곱한 금액을 결과로 얻을 수 있어야 한다.

ToDo

  • $5 + 10CHF(Swiss Franc) = $10(환율이 2:1일 경우)
  • $5 + $5 = $10
  • $5 + $5에서 Money 반환하기
  • Bank.reduce(Money)
  • Money에 대한 통화 변환을 수행하는 Reduce
  • Reduce(Bank, String)
  • Sum.plus
  • Expression.times
  • $5 x 2 = $10
  • amount를 private으로 만들기
  • Dollar 부작용(side effect)?
  • Money 반올림?
  • equals()
  • hashCode()
  • Equals null
  • Equals object
  • 5CHF x 2 = 10CHF
  • Dollar/Franc 중복
  • 공용 equals
  • 공용 times
  • Franc과 Dollar 비교하기
  • 통화?
  • @Test francMultiplication 제거

Agile Java Lesson 15

기본

assertion과 annotation

이번 레슨은 JUnit4의 어노테이션 기반 TestRunner를 구현합니다. 그리고 중강 중간 자바의 assert 키워드를 사용해서 테스트를 합니다.

1. assert

자바의 assert 키워드를 인식하게끔 하려면 다음 인수를 설정해야 합니다.

java -enableassertions ClassName

줄여서 -ea 라고 쓸수도 있고 패키지, 클래스 단위로 on/off 할 수도 있습니다.

java -ea:my.package... -da:your.package.FooBar

…은 서브패키지도 포함한다는 의미입니다.

실제 코드에선 다음과 같이 사용합니다.

assert 1 == list.size() : "Error Message"

2. annotation

어노테이션 정의는 처음해 봤는데 좀 헷갈릴 소지가 있는건 Retention 같습니다만 아마 특별한 경우가 아닌한 RUNTIME으로 사용할 것 같네요.

어노테이션은 C++ 처럼 디폴트 값을 줄 수도 있고 어노테이션에서 다른 어노테이션을 사용할 수도 있습니다.

리플렉션에서 어노테이션 타입을 가져올 수 있고, 정보를 한 곳에 모아 가독성을 높이기 때문에 많이 사용하는게 아닌가 싶습니다.

Agile Java Lesson 14

기본

일반화

Generic은 간단히 사용할 땐 아무런 문제가 없는데 클래스나 메소드를 설계 하려고 하면 이해할 수 없는 에러가 발생해서 막연히 손대기 어렵다고 생각했었습니다. 그런데 그 원인이 내부 동작을 이해하지 못해서 였네요.

1. erasure

Map<Date, String> map = new HashMap<Date, String>();

이런 객체를 만들었을 때 K, V로 선언한 곳이 Date나 String으로 대체되는 줄 알고 있었는데 내부에선 이러한 parameterized type을 전부 날려버리고 Object 타입으로 받아서 캐스팅을 합니다. 따라서 몇 가지 제한이 생기는데 Sun은 하위 버전 호환성을 위해서 이런 구조를 채택했다고 하네요.

2. extends

extends는 parameterized type의 최상위 타입을 Object가 아닌 extends의 타입으로 제한합니다. 컴파일러는 parameterized type을 최상위 타입으로 바꾸기 때문에 하위 타입을 사용할 수도 있습니다.

public class EventMap<K extends java.util.Date, V> extends MultiHashMap<K, V> { ... }

EventMap<java.util.Date, String> map = new EventMap<java.util.Date, String>();
 map.put(new java.util.Date(), "Date Type");
 map.put(new java.sql.Date(new java.util.Date().getTime()), "SQL Date Type");

EventMap 내부에선 K를 java.util.Date 타입이라고 가정할 수 있습니다.

private boolean hasPassed(K date) {
 Calendar when = new GregorianCalendar();
 when.setTime(date);
...

3. wildcard

Generic은 기본적으로 해당 타입으로만 제한하는데 어떤 타입이 올지 알 수 없는 경우 wildcard를 사용할 수 있습니다.

public static String concatenate(List<?> list, Object object) { ... }

문제는 wildcard를 사용할 경우 어떤 타입이 올지 알 수 없기 때문에 parameterized type을 사용한 메소드를 호출할 수 없다는 겁니다.

list.add(object); //! Error

에러가 발생하는 이유는 메소드의 파라미터로 넘겨받는 실제 list는 Date로 parameterized 됐는데 해당 메소드에서는 그걸 알 방법이 없기 때문입니다. 결국 다른 타입의 객체가 들어가서 타입 안정성을 해칠 수도 있습니다. 만약 메소드에서 List<? extends Object> list 같이 코드를 수정한다고 해도 이미 파라미터로 넘어오는 list 객체는 어떤 타입으로 erasure 됐기 때문에 에러가 발생합니다. (extends하는 타입으로 parameterized type을 바꿔야 하는데 이미 erasure 돼버렸죠.)

반면에 list.get() 메소드는 이미 erasure된 후, 캐스팅된 객체를 꺼내오기 때문에 이러한 문제가 없습니다. 따라서 wildcard는 데이터 구조에서 엘리먼트를 읽어들일 때 사용해야 합니다.

4. Generic Method

위의 문제는 제네릭 메소드를 사용해 해결할 수 있습니다.

public static <T> String concatenate(List<T> list, T object) { ... }

이 경우 T가 파라미터로 넘어오는 list 객체의 parameterized type으로 바뀌기 때문에 add() 메소드를 호출해도 문제가 없습니다.

또는 와일드카드를 사용한 메소드에서 제네릭 메소드를 호출하면 제네릭 메소드에서는 와일드카드의 타입을 알아낼 수 있기 때문에(wildcard capture) 이런 문제를 해결할 수 있습니다.

정리하자면 메소드의 파라미터 간 또는 파라미터와 리턴 타입에 의존성이 있는 경우 제네릭 메소드를 사용해야 하고 그렇지 않을 경우 와일드카드를 사용합니다.

6. super

extends와는 반대로 하위 타입을 제한합니다. 따라서 super의 타입의 수퍼 클래스 타입도 사용할 수 있습니다.