2019년 상반기 회고!

유튜브(YouTubu) 2019년 상반기 회고 영상 : https://youtu.be/3lhh-npx_cI

작년에 게을러 쓰지 못했던 회고를 올해는 상/하반기 나누어 올해 처음으로 했던

재미있는 일 중심으로 간단히 써보려 한다

1. 새로운 취미 : 피아노

작년 10월쯤에 시작했다가 멈추었던 피아노 1:1 개인레슨을 올해 3월들어 다시

시작했다.

매주 토요일 오전마다 하고 있는데 벌써 4개월째가 되었다.

아직 초보단계라 내가 원하는 수준까지 이르려면 적지 않은 시간과 노력이

필요하겠지만, 내가 좋아하는 뉴에이지곡과 가요등을 자유자재로 칠 수 있는

그때까지 이번에는 멈추지 않고 꾸준히 해 볼 생각이다

2. “데이터”에 관심을 갖다

나의 직업은 “프로그래머”이다. 지금까지 벡엔드 서버개발쪽 커리어 중심이었는데

최근 들어 “데이터“에 관심이 생겼고, 유저들이 생산한 서비스 데이터 중

의미있는 데이터를 잘 뽑아내 거기서 데이터 분석을 통해 점진적인 서비스 개선을

해 보는게 나의 최종목표이다.

그 “점진적인 서비스 개선“에서 내가 하고 싶은 기능은 바로 “추천“서비스이다.

우리가 온라인 쇼핑몰에서 내가 산 상품 혹은 내가 본 상품의 데이터등을 통해서

나에게 맞는 상품을 추천해 주는 것처럼, 데이터 분석을 통해 개인화된

추천서비스를 잘 만들어 실질적으로 유저들에게 도움이 되었으면 좋겠다

3. 개인방송을 시작하다

작년 12월에 처음 시작한 개인방송을 올해 1월 잠깐 회사를 쉬는 동안, 본격적으로

시작해 보았다.

우선, 스타크래프트 빠른무한(빨무) 게임 생방송을 시작으로 지금은 여자배구,

피아노연습, 일상 브이로그등 다양한 주제로 영상을 올리고 있다.

개인방송도 벌써 시작한지 반년이 훌쩍 넘었다.

하반기에는 “개인투자”등 보다 다양한 주제를 시도해 볼 생각이다.

4. 2019년 하반기에는..

사실 회고 영상에 얘기하지 않았지만, 상반기에 이룬 성취 중 하나는

바로 “꾸준함“에 대한 것이다.

전에는 뭐 하나를 해도 꾸준하게 하지 못하고 쉽게 포기하여 자기부정에 대한

안 좋은 감정들에 휩싸이곤 했는데 올해 5월부터는 좋은 습관을 들일 수 있도록

나름의 노력을 하고 있고, 이 노력은 지금도 계속 진행중이다.

이 가운데에는 사사키 후미오의 “나는 습관을 조금 바꾸기로 했다” 책을 읽으며

많이 도움을 받았고, 그 책에 나오는 “Way of Life”앱을 유료구매하여

좋은 습관을 만드는 데 도움이 되는 항목을 적어놓고 매일마다 실천하여

습관화될 수 있도록 지금도 노력하고 있다.

사실, 말처럼 습관화 되는 것이 쉽지는 않지만 진득하게 꾸준히 하다 보면

이것도 어느새인가 나도 모르게 몸에 자연스럽게 베지 않을까 생각한다.

2019년 하반기의 가장 큰 도전 중에 하나가 바로 “꾸준함”에 대한 도전이

될 것이라 생각한다. 화이팅!

ps. 하반기에는 상반기보다 좀 더 재미있고 즐겁고 도전적인 일을 더 많이 해보자!

유튜브(YouTubu) 2019년 상반기 회고 영상 : https://youtu.be/3lhh-npx_cI

[독서]2019 올해 읽은 책

[기술]

6월

  • 블록체인 기업으로 가는 길
  • R을 이용한 추천시스템(6/19, 완독 100%,  만족도 40%)
    • R을 잘 모르는 상황에서 top-down형식으로 공부
    • 실제 전처리된 데이터가 3개의 항목을 가진 데이터가 있다면 몇가지 명령어만으로 쉽게 모델을 만들고 예측해서 쉽게 추천된 리스트를 얻을 수 있음
    • R과 추천시스템을 잘 몰라도 따라하며 해볼 수 있음

 

[그외]

6월

  • 나는 습관을 조금 바꾸기로 했다

7월

  • 마법의 돈 굴리기(70%)

 

 

[독서]2018 올해 읽은 책

[기술]

[그외]

1월

  • 허영만의 3천만원 1
  • 주식천재 홍대리

2월

  • 선대인의 대한민국 경제학
  • 읽으면 진짜 재무제표 보이는 책
  • 주식투자 무작정 따라하기
  • 계속하게 만드는 하루관리 습관

3월

  • 홀딩, 턴
  • 트렌드코리아 2018

4월

  • 자식들에게만 전해주는 재테크 비밀수첩

[번역]Interaction Based Testing

Spock의 공식문서 중 Interaction Based Testing를 번역해 보았습니다.

보실 때 오역이 있음을 감안하시고, 번역하기에 애매한 부분은 원문 그대로 옮겼습니다.


상호작용 기반 테스팅은 2000년대 초반 Extreme 프로그래밍(XP) 커뮤니티에서 등장한

설계 및 테스팅 기술이다. 그것은 상태가 아닌 객체의 동작에 초점을 맞추고 명세안에서 객체가

메서드 호출을 통해 그들의 협력자들과 어떻게 상호작용 하는지 연구한다.

예를 들어, 구독자에게 메세지를 보내는 게시자가 있다고 가정해보자.

class Publisher {
  List<Subscriber> subscribers = []
  void send(String message){
    subscribers*.receive(message)
  }
}

interface Subscriber {
  void receive(String message)
}

class PublisherSpec extends Specification {
  Publisher publisher = new Publisher()
}

게시자를 어떻게 테스트 할 것인가? 상태기반 테스팅에서는, 게시자가 그의 구독자를 추적하는지

검증 할 수 있다. 더 흥미로운 질문은 게시자가 보낸 메세지를 구독자가 수신하는지 여부이다.

이 질문에 대답하려면, 게시자와 구독자간의 대화를 듣는 구독자의 특별한 구현이 필요하다.

이러한 구현을 mock 객체라고 부른다.

우리는 구독자의 가짜 구현을 손으로 만들 수 있지만,  이 코드를 만들고 유지보수하는 것은

메서드수와 상호작용의 복잡성이 증가함에 따라 괴로워 질 수 있다.

이곳이 mocking framework가 들어오는 곳이다.

그것은 명세안에서 객체와 그들의 협력자 사이의 예상되는 상호작용을 설명하는 방법을

제공하고, 이러한 예상을 검증하는 협력자의 가짜 구현을 만들 수 있다.

가짜 구현은 어떻게 생성되는가?

대부분의 자바 mocking framework와 마찬가지로 Spock은 런타임에 가짜 구현을 만들어내기

위해 JDK dynamic proxies(인터페이스를 mocking할 때)와 CGLIB proxies(클래스를

mocking할 때)를 사용한다.

Groovy  메타 프로그래밍을 기반으로 하는 구현과 비교하면 자바 코드를 테스트할 때도 유용하

다.  자바세계에서는 인기있고 성숙한 mocking framework(JMock, EasyMock, Mockito)이 많

다. 이 도구들 각각은 Spock과 사용할 수 있지만, 우리는 Spock의 명세언어와 긴밀하게 통합된

자체 mocking framework을 사용하기로 결정했다. 이 결정은 Groovy의 모든 기능을 활용하여

상호작용기반 테스트를 보다 더 쉽게 만들고, 더 읽기쉽고, 궁극적으로 더 재미있게 만들려는

의도에서 비롯되었다. 이 장의 끝에서 당신은 이러한 목표를 달성하는 데 동의하게 되기를 바란

다. 표시된 부분을 제외하고, Spock의 mocking framework의 모든 기능은 java 및 Groovy

코드 테스트를 위해 둘다 모두 작동한다.

Creating Mock Objects

Mock 객체는 Mock 메서드로 생성된다. 두개의 가짜 구독자를 만들어보자.

def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)

또는 자바와 비슷한 문법이 지원되므로 더 나은 IDE 지원을 제공할 수도 있다.

Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()

여기 mock 타입은 할당의 왼쪽에 있는 변수 타입으로부터 유추된다.

NOTE | mock 타입이 할당의 왼쪽에서 주어지면 (필수는 아니지만)오른쪽에서는 생략할 수

있다. Mock 객체는 말 그대로 타입을 구현(또는 클래스의 경우 확장)한다. 즉, 우리 예제의

subscriber는 Subscriber이다. 따라서 이 타입을 예상하는 정적타입(Java) 코드로

전달 할 수 있다.


Default Behavior of Mock Objects

Lenient vs. Strict Mocking Frameworks

Mockito에서처럼, 우리는 mocking framework가 기본적으로 관대해야 한다고 믿는다.

이것은 mock 객체에 대한 예상치못한 호출(다른 말로 현재 테스트중인 테스트와 관련없는

상호작용)이 허용되고 기본응답으로 대답함을 의미한다. 반대로, EasyMock 및 JMock 같은

mocking framework는 기본적으로 엄격하고, 예상치 못한 메서드 호출에 예외가 발생한다.

엄격함은 엄함을 강요하지만, 과한 명세로 이어질 수 있고, 결과적으로 다른 내부코드가

변경될때마다 테스트가 깨질 수 있다. Spock의 mocking framework를 사용하면 명세와 관련된

함정을 피하면서 상호작용과 관련된 내용만 쉽게 설명할 수 있다.

초기에 mock객체는 아무런 동작을 하지 않았다. 메서드 호출은 허용했지만 메서드의 리턴타입

(false, 0, 혹은 null)에 대한 기본값을 리턴하는 것외는 아무런 효과가 없었다.예외

는 Object.equals, Object.hashCode, and Object.toString 메서드같은 기본동작을 갖는다.

mock 객체는 그 자체로 동일하고 고유한 해시코드와 그것이 나타내는 타입의 이름을 포함한

문자열 표현을 가진다. 이 기본동작은 메서드를 stubbing하여 재정의할 수 있고,

Stubbing section에서 배울 것이다.


Injecting Mock Objects into Code Under Specification

게시자와 구독자를 만든 후에 우리는 게시자에게 구독자를 알릴 필요가 있다.

class PublisherSpec extends Specification {
  Publisher publisher = new Publisher()
  Subscriber subscriber = Mock()
  Subscriber subscriber2 = Mock()

  def setup() {
    publisher.subscribers << subscriber // << is a Groovy shorthand for List.add()
    publisher.subscribers << subscriber2
  }

이제 두 당사자간의 예상되는 상호작용을 설명할 준비가 되었다.


Mocking

Mocking은 명세하에서 객체와 협력자들 사이의 상호작용을 기술하는 행위이다.

여기 예제가 있다.

def "should send messages to all subscribers"() {
  when:
  publisher.send("hello")

  then:
  1 * subscriber.receive("hello")
  1 * subscriber2.receive("hello")
}

게시자가 ‘hello’ 메세지를 보낼 때, 두 구독자들은 정확히 한번씩 메세지를 받아야 한다.

이 기능 메서드가 실행되면, when block에서 실행 중에 일어난 mock 객체들의 모든

호출이 then: block에서 기술된 상호작용과 일치될 것이다.

상호작용 중 하나가 충족하지 않으면, InteractionNotSatisfiedError가 발생할 것이다.

이 검증은 자동으로 수행되므로 어떤 추가코드도 필요하지 않다.

Interactions

상호작용은 단지 보통의 메서드 호출인가?

그렇지 않다. 상호작용은 보통의 메서드 호출과 유사해 보이지만, 그것은 메서드 호출이 일어날것

으로 예상되는 표현의 방법일 뿐이다. 상호작용에 대한 좋은 생각은 mock 객체로 들어오는

모든 호출과 서로 일치한다는 것이다. 상황에 따라, 상호작용은 0,1 또는 다수의 호출과

일치할 수 있다.

그럼 then: block에 대해 자세히 살펴보자. 두가지 상호작용은 각각 carinality, a target

constraint, a method constraint, an argument constraint 4개의 뚜렷한 파트를

포함한다.
1 * subscriber.receive("hello")
|   |          |       |
|   |          |       argument constraint
|   |          method constraint
|   target constraint
cardinality

Cardinality

상호작용의 cardinality는 얼마나 자주 메서드 호출이 발생할지를 설명한다.

그것은 고정된 번호이거나 범위로 주어질 수 있다.

1 * subscriber.receive("hello")      // exactly one call
0 * subscriber.receive("hello")      // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls (inclusive)
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello")      // any number of calls, including zero
                                     // (rarely needed; see 'Strict Mocking')

Target Constraint

상호작용의 target constraint는 mock 객체가 어떤 메서드 호출을 받을 것으로 예상되는가를

설명한다.

1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello")          // a call to any mock object

Method Constraint

상호작용의 method constraint는 어떤 메서드가 호출될 것인가를 설명한다.

1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello")  // a method whose name matches the given regular expression
                                // (here: method name starts with 'r' and ends in 'e')

getter 메서드 호출이 예상될 때, Groovy 속성 구문은 메서드 구문 대신 사용할 수 있다.

1 * subscriber.status // same as: 1 * subscriber.getStatus()

setter 메서드 호출이 예상될 때, 오직 메서드 구문만 사용할 수 있다.

1 * subscriber.setStatus("ok") // NOT: 1 * subscriber.status = "ok"

Argument Constraints

상호작용의 argument constraints는 예상되는 메서드 인자값을 설명한다.

1 * subscriber.receive("hello")     // an argument that is equal to the String "hello"
1 * subscriber.receive(!"hello")    // an argument that is unequal to the String "hello"
1 * subscriber.receive()            // the empty argument list (would never match in our example)
1 * subscriber.receive(_)           // any single argument (including null)
1 * subscriber.receive(*_)          // any argument list (including the empty argument list)
1 * subscriber.receive(!null)       // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive({ it.size() > 3 }) // an argument that satisfies the given predicate
                                          // (here: message length is greater than 3)

Argument constraints는 여러개의 인자가 있는 메서드에 대해서도 예상대로 동작한다.

1 * process.invoke("ls", "-a", _, !null, { ["abcdefghiklmnopqrstuwx1"].contains(it) })

vararg 메서드를 다룰 때, 다음과 같은 상호작용에서도 varrag 구문이 사용된다.

interface VarArgSubscriber {
    void receive(String... messages)
}

...

subscriber.receive("hello", "goodbye")
Spock Deep Dive: Groovy Varargs

Groovy는 배열타입의 마지막 파라미터를 vararg style로 호출할 수 있다.따라서,

vararg 구문은 그러한 메서드와 일치하는 상호작용에 사용될 수 있다.

Matching Any Method Call

때때로 임의의 “어떤 것”과 일치할 때 다음과 같은 단어를 쓰면 유용하다.

1 * subscriber._(*_)     // any method on subscriber, with any argument list
1 * subscriber._         // shortcut for and preferred over the above

1 * _._                  // any method call on any mock object
1 * _                    // shortcut for and preferred over the above

(..) * .(*_) >> _는 유효한 상호작용 선언이지만, 특별히 좋은 스타일도 아니다.

Strict Mocking

어떤 메서드 호출과 일치하는 것이 유용할까? 좋은 예는 명시적으로 선언된 것 이외의

상호작용이 허용되지 않는 mocking 스타일의 엄격한 mocking이다.

when:
publisher.publish("hello")

then:
1 * subscriber.receive("hello") // demand one 'receive' call on 'subscriber'
_ * auditing._                  // allow any interaction with 'auditing'
0 * _                           // don't allow any other interaction

0 *은 then: block 또는 메서드의 마지막 상호작용에서 의미가 있다.

모든 호출은 auditing component와 상호작용할 수 있는 _ *를 사용해라

_ * 는 엄격한 mocking의 문맥에서만 의미가 있다. 특히, Stubbing 호출시에는 필요하지 않다.

예를 들어, _ * auditing.record() >> “ok”는 auditing.record() >> “ok”로 할 수 있다.

Where to Declare Interactions

지금까지 우린 then: block에 모든 상호작용을 선언했다. 이는 자연스럽게 스펙을 읽도록 해준다.

그럼에도,  그것들을 만족시키는 상호작용을 when: 블록전에 어느곳이든 넣을 수 있다.

특히, 이것은 상호작용이 setup 메서드에도 선언될 수 있음을 의미한다.

또한 동일한 specification 클래스의 어떤 helper 인스턴스 메서드에도 상호작용을 선언할 수

있다. mock 객체가 상호작용이 일어날 때, 상호작용은 상호작용이 선언된 순서와 일치한다.

만약 호출이 여러 상호작용과 일치한다면, 상위 호출한계에 도달하지 못한 가장 초기에 선언된

상호작용이 승리할 것이다. 이 규칙에 예외가 하나 있다.

then: block에 선언된 상호작용은 다른 상호작용보다 먼저 비교된다.

예를 들어, then block에 선언된 상호작용으로 setup 메서드에서 선언된 상호작용을

오버라이드 할 수 있다.

Spock Deep Dive: How Are Interactions Recognized?

달리 말하면, 정규 메서드 호출이 아니고 상호작용 선언을 표현하는 것이 무엇인가?

Spock은 간단한 구문 규칙으로 상호작용을 인식한다.

즉, 표현식이 statement 위치에 있고, * 또는 right-shift 연산인 경우, 상호작용으로 간주되어

구문 분석됩니다. 그러한 표현은 statement 위치에서 거의 가치가 없으므로, 의미를

바꾸는 것은 좋은 일이 될 것이다.

연산이 cardinality (mocking할 때) 혹은 응답 발생기(stubbing시)를 선언하는 구문과 일치하는

방법에 주목하라. 둘 중 하나는 항상 존재해야 한다.

foo.bar()는 혼자 상호작용으로 간주되지 않을것이다.

Declaring Interactions at Mock Creation Time (New in 0.7)

mock이 변하지 않는 기본 상호작용이 있을경우, mock 생성시 다음과 같이 선언할 수 있다.

def subscriber = Mock(Subscriber) {
   1 * receive("hello")
   1 * receive("goodbye")
}

이 기능은 Stubbing 및 전용 Stubs에 특히 유용하다. 상호작용은 target constraint를

가지지 못한다. 그들이 속한 mock 객체의 문맥에서 명확하다.

상호작용은 mock과 함께 인스턴스 필드를 초기화할 때 선언할 수 있다.

class MySpec extends Specification {
    Subscriber subscriber = Mock {
        1 * receive("hello")
        1 * receive("goodbye")
    }
}

Grouping Interactions with Same Target (New in 0.7)

동일한 대상을 공유하는 상호작용은 Specification.with block에서 그룹화할 수 있다.

Mock 생성시 상호작용 선언과 유사하게, 이것은 target constraint 반복이 불필요하다.

with(subscriber) {
    1 * receive("hello")
    1 * receive("goodbye")
}

with block은 또한 같은 대상에 한해 조건을 그룹화하여 사용할 수 있다.

Mixing Interactions and Conditions

then: block은 상호작용과 조건 둘다 포함한다. 그것이 꼭 필요하지 않더라도, 조건 전에

상호작용을 선언하는 것이 일반적이다.

when:
publisher.send("hello")

then:
1 * subscriber.receive("hello")
publisher.messageCount == 1

publisher가 ‘hello’ 메세지를 보내면, subscriber는 정확히 한번 메세지를 받고,

publisher의 메세지 카운트는 1이 될 것이다.

Explicit Interaction Blocks

내부적으로, Spock은 기능 실행 전 예상되는 상호작용에 대한 완전한 정보를 가지고 있어야한다.

then: block에서 선언된 상호작용이 어떻게 이루어질까? 대답은 when: block 진행전에

곧바로 then: block에 선언된 상호작용으로 이동한다.

대부분 이것은 잘 동작하지만 때때로 문제를 일으킬 수 있다.

when:
publisher.send("hello")

then:
def message = "hello"
1 * subscriber.receive(message)

여기 예상된 인자값을 위한 변수를 선언했다.(혹은 cardinality를 위한 변수선언을 했다)

하지만, Spock은 상호작용이 변수와 본질적으로 연결되어 있다고 말할 만큼 똑똑하지 않다.

그래서 상호작용으로 이동하면 MissingPropertyException이 발생할 것이다.

이 문제를 해결하는 한가지 방법은 when: block전으로 변수 선언을 옮기는 것이다.

(Data Driven Testing에선 변수를 where: block 안으로 옮길 수 있다)

예를 들어, 메세지를 보낼때 같은 변수를 사용하는게 더 이득이다.

다른 해결책은 변수 선언과 상호작용이 함께 속해있다는 사실을 명시하는 것이다.

when:
publisher.send("hello")

then:
interaction {
    def message = "hello"
    1 * subscriber.receive(message)
}

MockingApi.interaction block은 항상 전체적으로 이동하기 때문에, 코드는 정상동작한다.

Scope of Interactions

then: block안에 선언된 상호작용은 when: block 보다 선행된다.

when:
publisher.send("message1")

then:
subscriber.receive("message1")

when:
publisher.send("message2")

then:
subscriber.receive("message2")

이렇게 하면 subscriber가 message1을 수신하는 동안 when: block이 실행되고,

두번째 when: block이 실행되는 동안 message2를 수신받는다.

then: block 밖에 선언된 상호작용은 그들의 선언에서 기능 메서드 끝까지 활성화된다.

상호작용은 항상 특별히 기능 메서드에 범위가 지정된다.

따라서 상호작용은 static 메서드, setupSpec 메서드, cleanupSpec 메서드에는

선언될 수 없다. 달리말하면, mock 객체는 static이나 @Shared 필드에 저장될 수 없다.

Verification of Interactions

mock 기반 테스트가 실패할 수 있는 두 가지 주요 방법이 있다. 상호작용이 허용하는 것보다

더 많거나 더 적은 호출수와 일치할 수 있기 때문이다. 이전 케이스에서는 호출이 일어날 때

바로 감지되어 TooMayInvocationsError가 발생한다.

Too many invocations for:

2 * subscriber.receive(_) (3 invocations)

너무 많은 호출을 쉽게 인지할 수 있도록, Spock은 상호작용과 일치하는 모든 호출을

보여줄 것이다.

Matching invocations (ordered by last occurrence):

2 * subscriber.receive("hello")   <-- this triggered the error
1 * subscriber.receive("goodbye")

output에선, receive(“hello”) 중 하나가 TooManyInvocationsError를 일으켰다.

하나의 라인에 집계된 output으론 두 호출의receive(“hello”)를 구분할 수 없으므로,

receive(“goodbye”) 전에 첫번째 receive(“hello”)에 호출되었을것이다.

두번째 경우,(필요한 것보다 적은 호출의 경우) when block의 실행이 완료된 후

탐지할 수 있다.  (그때까진, 추가적인 호출이 일어날 수 있다)

그것은 TooFewInvocationsError를 발생시킨다.

Too few invocations for:

1 * subscriber.receive("hello") (0 invocations)

메서드가 전혀 호출되지 않았 든, 동일한 메서드가 다른 인자로 호출되었는지,

같은 메서드가 다른 mock 객체에서 호출되었는지 또는 다른 메서드가 이 메서드 대신

호출되었는지 여부는 중요하지 않다.

이 경우 모두 TooFewInvocationsError 에러가 발생합니다.

누락된 호출 대신 일어난 것을 쉽게 진단하기 위해, Spock은 상호작용과 일치하지 않는

상호작용의 유사성으로 정렬해서 모든 호출을 보여줄 것이다.

특별히, 상호작용의 인자를 제외한 모든 것과 일치하는 호출이 먼저 보여질 것이다.

Unmatched invocations (ordered by similarity):

1 * subscriber.receive("goodbye")
1 * subscriber2.receive("hello")

Invocation Order

종종, 정확한 메서드 호출순서는 관련이 없으므로 시간이 흐르면 변한다.

over-specification을 피하기 위해, Spock은 지정된 상호작용이 충족되면 어떤 호출순서도

허용한다.

then:
2 * subscriber.receive("hello")
1 * subscriber.receive("goodbye")

여기서 “hello” “hello” “goodbye”, “hello”, “goodbye” “hello” “goodbye” “hello” “hello”

같은 어떤 호출 순서도 ​​지정된 상호 작용을 충족시킨다.

호출 순서가 중요한 경우 상호 작용을 여러 개의 then: block으로 분할하여 순서를 지정할 수

있다.

then:
2 * subscriber.receive("hello")

then:
1 * subscriber.receive("goodbye")

지금 Spock은 “goodbye” 전에 “hello”가 수신되었는지 검증할 것이다.

달리 말하면, 호출순서는 then: block안이 아니고 사이에 적용된다.

then: block과 and: block을 구분하는 것은 어떤 순서도 적용되지 않는다.

and: 는 오직 문서 목적의 의미만 있고 다른 어떤 의미도 전달하지 않는다.

Mocking Classes

인터페이스 이외에도, Spock은 클래스 mocking을 지원한다. Mocking 클래스는

인터페이스 mocking과 같이 작동한다. 추가로 필요한 것은 cglib-nodep-2.2 혹은 그 이상

그리고 objenesis-1.2나 그 이상을 클래스패스에 넣는 것이다.

이 라이브러리 중 하나가 없다면, Spock은 너에게 부드럽게 알려줄 것이다.

Java8은 CGLIB 3.2.0부터 지원된다.

Stubbing

Stubbing은 협력자들이 메소드 호출에 특정 방식으로 응답하게하는 행위다.

메서드를 stubbing할 때, 메서드를 얼마나 많이 호출했는지 횟수는 신경 쓰지 않아도 됩니다.

호출 될 때마다 특정 값을 반환하거나 일부 부작용을 수행하기만 하면 됩니다.

다음 예제를 위해 구독자의 receive 메서드를 구독자가 메시지를 처리 ​​할 수 ​​있었는지 여부를

알려주는 상태 코드를 반환하도록 수정해보자.

interface Subscriber {
    String receive(String message)
}

이제 receive 메서드는 모든 호출마다 “ok”를 반환할 것이다.

subscriber.receive(_) >> "ok"

subscriber가 메시지를 받을 때마다 ‘ok’로 응답하도록 만들어라.

mocked된 상호작용과 비교해 stubbed 상호작용은 왼쪽 끝에 cardinality가 없지만

오른쪽 끝에 응답 발생기가 추가된다.

subscriber.receive(_) >> "ok"
|          |       |     |
|          |       |     response generator
|          |       argument constraint
|          method constraint
target constraint

stubbed된 상호 작용은 일반적인 위치에서 선언 할 수 있다. 즉,  then: 블록안 또는 when  block

이전에 선언 할 수 있다.  mock 객체가 stubbing에만 사용된다면, setup: block이나

mock 객체 생성시 선언하는 것이 일반적이다.

Returning Fixed Values

우린 이미 right-shift(>>) 연산자로 고정된 값을 반환하는데 사용하는 것을 보았다.

subscriber.receive(_) >> "ok"

다른 호출 시 다른 값을 반환하기 위해, 여러 상호작용을 사용할 수 있다.

subscriber.receive("message1") >> "ok"
subscriber.receive("message2") >> "fail"

이것은 message1을 수신받으면 ok를 반환하고, message2를 수신받으면 fail을 반환할 것이다.

메서드에 선언된 반환 타입과 호환되는 경우, 리턴되는 값에 제한은 없다.

Returning Sequences of Values

성공적인 호출시 다른 값을 반환하기 위해, triple-right-shift 연산자를 사용해라.

subscriber.receive(_) >>> ["ok", "error", "error", "ok"]

이것은 첫번째 호출시 ok를 리턴하고 두번째와 세번째 호출시는 error, 그리고 남은 모든

호출시에는 ok를 리턴할 것이다. 오른쪽은 Groovy가 반복하는 방법을 알아야 한다.

이번 예에서는 일반적인 리스트를 사용했다.

Computing Return Values

메서드의 인자를 기반으로 한 반환값을 계산하려면, closure와 함께 right-shift(>>) 연산자를

사용한다. closure에 타입이 지정되지 않은 단일 파라미터가 선언되면, 메서드의 인자 리스트로

전달한다.

subscriber.receive(_) >> { args -> args[0].size() > 3 ? "ok" : "fail" }

여기선 메세지의 길이가 3을 넘으면 ok, 아니면 fail을 반환한다.

대부분의 경우에 메서드의 인자에 직접 접근하는게 편리하다.

closure가 둘 이상의 파라미터 혹은 단일 타입 파라미터를 선언하면, 메서드 인자는

하나씩 closure 파라미터로 맵핑될 것이다.

subscriber.receive(_) >> { String message -> message.size() > 3 ? "ok" : "fail" }

이 응답기는 전과 동일하게 동작하지만, 틀림없이 읽기가 더 쉽다.

인자보다 메서드 호출에 더 많은 정보가 필요하다면,

org.spockframework.mock.IMockInvacation을 봐라.

이 인터페이스에 선언된 모든 메서드들은 그들 앞에 접두사를 쓸 필요없이 closure 안에서

사용할 수 있다. (Groovy 용어에선, closure는 IMockInvocation의 인스턴스에 위임한다.)

Performing Side Effects

때때로 반환값을 계산하는 것보다 더 필요한 것이 있을 수 있다. 전형적인 예로는

예외를 던지는 것이다. closure는 다음과 같다.

subscriber.receive(_) >> { throw new InternalError("ouch") }

물론, closure는 println 구문같은 더 많은 코드를 포함할 수 있다.

그것은 상호작용과 일치하는 매 호출마다 실행될 것이다.

Chaining Method Responses

메서드 응답은 체이닝할 수 있다.

subscriber.receive(_) >>> ["ok", "fail", "ok"] >> { throw new InternalError() } >> "ok"

이것은 3번째 호출까진 ok, fail, ok를 반환하고, 네번째 호출부터는 InternalError를 던지고

이후 호출에 대해서는 ok를 반환한다.

Combining Mocking and Stubbing

Mocking과 Stubbing은 함께 실행된다.

1 * subscriber.receive("message1") >> "ok"
1 * subscriber.receive("message2") >> "fail"

mocking과 stubbing이 같은 메서드에서 호출될 때, 그들은 같은 상호작용이 일어난다.

특별히, Mockito-style의 stubbing과 두개로 분리된 구문을 mocking하면 작동하지 않는다.

setup:
subscriber.receive("message1") >> "ok"

when:
publisher.send("message1")

then:
1 * subscriber.receive("message1")

상호 작용을 선언하는 곳에서 설명한 것처럼 수신 호출은 먼저 then : 블록의 상호 작용에 대해

일치시킨다. 이 상호 작용은 응답을 지정하지 않으므로 메서드의 반환 유형 (이 경우 null)의

기본값이 반환된다. (이것은 mocking에 대한 Spock의 관대 한 접근 방식의 또 다른 측면이다)

따라서 setup : 블록의 상호 작용은 절대로 일치 할 기회를 얻지 못한다.

동일한 메서드 호출을 Mocking하고 stubbing하는 것은 동일한 상호작용에서 발생해야 한다.

Other Kinds of Mock Objects (New in 0.7)

지금까지 MockingApi.Mock 메서드를 사용하여 mock 객체를 만들었다.

이 메서드외에도, MockingApi 클래스는 mock 객체의 더 특별한 종류를 만들기 위한

여러 다른 팩토리 메서드를 제공한다.

Stubs

stub은 MockingApi.stub 팩토리 메서드에서 만들어진다.

def subscriber = Stub(Subscriber)

mock은 stubbing과 mocking 모두에 사용할 수 있지만, stub은 stubbing에만 사용할 수 있다.

협력자를 stub으로 제한하면 그 역할을 specification의 독자들에게 전달할 수 있다.

stub 호출이 기본적인 상호작용(1 * foo.bar()와 같은)과 일치하면, InvalidException이

발생한다.

mock처럼, stub도 예상치못한 호출을 허용한다. 그렇지만, 그러한 경우에 stub에 의해

반환된 값들은 더 야심찬 것들이다.

  • primitive 타입은 기본타입의 기본값이 반환된다
  • non-primitive 숫자값은(BigDecimal) 0이 반환된다.
  • non-numerical 값들은, 빈값이나 더미객체가 반환된다. 이것은 빈 문자열, 빈 collection
    기본생성자로부터 만들어진 객체, 혹은 기본 값을 반환하는 다른 stub일 수 있다.
    자세한건 org.spockframework.mock.EmptyOrDummyResponse 클래스를 보라.

stub은 종종 고정된 상호작용 세트가 있는 경우가 있다. mock 생성시 상호작용을 선언하는 것이

특히 매력적이다.

def subscriber = Stub(Subscriber) {
    receive("message1") >> "ok"
    receive("message2") >> "fail"
}

Spies

(이 기능을 사용하기 전에 두번 생각해라. 명세에서 코드의 디자인을 바꾸는 것이 더 나을 수 있다.)

spy는 MockingApi.spy factory 메서드로 만들어진다.

def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])

spy는 항상 실제객체를 기반으로 한다. 그래서 특정 생성자 인자와 함께 인터페이스 타입보다

클래스타입을 제공해야 한다.

인자없는 생성자가 제공되면, 타입의 기본 생성자가 사용될 것이다.

 

spy의 메서드 호출은 자동으로 실제 객체에게 위임된다. 마찬가지로, 실제 객체의 메서드로부터

반환된 값들은 spy를 통해 호출자에게 다시 전달된다. spy를 만들고 난 후에는 호출자와

실제객체 사이의 대화를 spy가 들을 수 있다.

1 * subscriber.receive(_)

receive가 정확히 한번 호출되는 것을 제외하고는 publisher와 스파이의 기초를 이루는

SubscriberImpl 객체간 대화는 변경되지 않는다.

spy에서 메서드를 stubbing하면 더 이상 실제 메서드가 호출되지 않는다.

subscriber.receive(_) >> "ok"

SuscriberImpl.receive 호출대신에, receive 메서드는 지금 간단히 “ok”를 반환할 것이다.

때때로, 실제 메서드를 실행하고 위임하는 것이 바람직하다.

subscriber.receive(_) >> { String message -> callRealMethod(); message.size() > 3 ? "ok" : "fail" }

여기선 실제 객체에게 메서드 호출을 위임하는 callRealMethod()를 사용했다.

message 인자값을 주지 않아도 자동으로 전달된다.

callRealMethod()는 실제 상호작용의 결과를 반환하지만, 이번 예에서는 우리가 만든 결과를

반환하기로 결정했다. 실제 메서드에서 다른 메세지를 전달해야 한다면, 우리는

callRealMethodWithArgs(“changed message”)를 사용할 수 있다.

Partial Mocks

(이 기능을 사용하기 전에 두번 생각해라. 명세에서 코드의 디자인을 바꾸는 것이 더 나을 수 있다.)

Spies는 또한 partial mocks를 사용할 수 있다.

// this is now the object under specification, not a collaborator
def persister = Spy(MessagePersister) {
  // stub a call on the same object
  isPersistable(_) >> true
}

when:
persister.receive("msg")

then:
// demand a call on the same object
1 * persister.persist("msg")

Groovy Mocks (New in 0.7)

지금까지 mocking 기능의 모든 것은 자바 혹은 그루비로 만든 것과 상관없이 동일하게 동작한다.

Groovy의 동적기능을 활용하여, Groovy mocks는 Groovy 코드를 테스트하기 위한 몇몇

특별한 부가기능을 제공한다. 그것은 MockingApi.GroovyMock(), MockingApi.GroovyStub()

MockingApi.GroovySpy() factory 메서드에서 만들어진다.

Groovy Mock은 정규 Mock보다 언제 더 좋은가? Groovy mock은 명세 하의 코드가

Groovy로 작성되고 고유한 Groovy mock 기능 중 일부가 필요할 때 사용해야 한다.

Groovy mocks는 Java 코드에서 호출 될 때 일반 mock처럼 작동한다.

명세하의 mocked 타입 코드가 Groovy로 만들어졌다고 Groovy mock을 사용할 필요는 없다.

Groovy mock을 사용해야하는 구체적인 이유가 없다면, 일반적인 mock을 써라.

Mocking Dynamic Methods

모든 Groovy mocks은 GroovyObject의 인터페이스를 구현한다. 그들은 물리적으로

선언된 메서드처럼 동적 메서드의 mocking과 stubbing을 지원한다.

def subscriber = GroovyMock(Subscriber)

1 * subscriber.someDynamicMethod("hello")

Mocking All Instances of a Type

(이 기능을 사용하기 전에 두번 생각해라. 명세에서 코드의 디자인을 바꾸는 것이 더 나을 수 있다.)

보통 Groovy는 일반 mocks와 같은 명세하에서 코드를 주입하는 것이 필요하다.

그럼에도 Groovy mock은 전역으로 생성되면 기능 메서드가 실행되는 동안 mocked된

모든 실제 객체가 자동으로 대체된다.

def publisher = new Publisher()
publisher << new RealSubscriber() << new RealSubscriber()

def anySubscriber = GroovyMock(RealSubscriber, global: true)

when:
publisher.publish("message")

then:
2 * anySubscriber.receive("message")

여기 실제 구독자를 구현한 두 객체와 게시자가 있다.

그때 우린 동일한 타입의 전역 mock을 만들 수 있다. 이것은 실제 구독자들의 모든

메서드 호출을 mock 객체로 변경한다. mock 객체의 인스턴스는 게시자에게 전달되지

않는다. 그것은 상호작용을 설명할때만 사용된다.

global mock은 클래스타입만 생성된다. 기능 메서드가 실행되는 동안 타입의 모든

객체들은 효율적으로 대체된다.

글로벌 mock은 글로벌 효과가 있기 때문에 GroovySpy와 함께 사용하는 것이 편리하다.

이는 상호 작용이 일치하지 않는 한 실제 코드가 실행되도록 해서 객체를 선택적으로 받고

필요한 경우 행동을 변경하도록 한다.

글로벌 Groovy Mock 어떻게 구현되는가?

Global Groovy mocks Groovy 메타 프로그래밍에서 강력한 힘을 얻는다.

보다 정확하게 말하자면, 전역적으로 mocked된 모든 유형에는 기능 메서드의 지속 기간 동안

맞춤 메타 클래스가 할당된다. 글로벌 Groovy mock은 여전히 CGLIB 프록시를 기반으로 하므로

Java 코드에서 호출 될 때 일반적인 mocking 기능 (super power는 아님)을 유지한다.

Mocking Constructors

(이 기능을 사용하기 전에 두번 생각해라. 명세에서 코드의 디자인을 바꾸는 것이 더 나을 수 있다.)

Global mocks는 생성자의 mocking을 지원한다.

def anySubscriber = GroovySpy(RealSubscriber, global: true)

1 * new RealSubscriber("Fred")

spy를 사용하면, 생성자 호출로 반환된 객체는 변하지 않는다.

생성될 객체를 변경하기 위해, 생성자 stub을 사용할 수 있다.

new RealSubscriber("Fred") >> new RealSubscriber("Barney")

일부 코드가 Fred라는 이름으로 subscriber를 만들려고 시도할 때, 우리는 대신 Barney라는

subscriber를 만들것이다.

Mocking Static Methods

(이 기능을 사용하기 전에 두번 생각해라. 명세에서 코드의 디자인을 바꾸는 것이 더 나을 수 있다.)

Global mock은 static 메서드의 mocking과 stubbing을 지원한다.

def anySubscriber = GroovySpy(RealSubscriber, global: true)

1 * RealSubscriber.someStaticMethod("hello") >> 42

동적 메서드에서도 마찬가지로 동작한다.

global mock이 생성자와 static 메서드를 mocking하는데만 사용되는 경우,

mock의 객체는 꼭 필요하지 않다. 이 경우엔 다음과 같이 사용할 수 있다.

GroovySpy(RealSubscriber, global: true)

Advanced Features (New in 0.7)

대부분 이 기능은 필요하지 않다. 하지만 그렇게 해야 한다면 그들을 가질 수 있다.

A la Carte Mocks

Mock (), Stub () Spy () 팩토리 메소드는 특정 구성을 사용하여 mock 객체를 만드는

사용할 있다.  mock 설정보다 세밀한 제어를 원할 경우

org.spockframework.mock.IMockConfiguration 인터페이스를 살펴봐라.

인터페이스 모든 속성은 명명 인자 Mock () 메서드에 전달할 있다. 예를 들어

def person = Mock(name: "Fred", type: Person, defaultResponse: ZeroOrNullResponse, verified: false)

여기서 우리는 Mock () ​​기본 반환 값과 일치하지만 호출이 검증되지 않은(Stub ()처럼)

모의 객체를 만든다.

ZeroOrNullResponse 전달하는 대신 예기치 않은 메서드 호출에 응답하기 위해

자체 customorg.spockframework.mock.IDefaultResponse 제공 ​​ 수 있다.

Detecting Mock Objects

특정객체가 Spock mock 객체인지 확인하려면,

org.spockframework.mock.MockDetector을 사용해라.

def detector = new MockDetector()
def list1 = []
def list2 = Mock(List)

expect:
!detector.isMock(list1)
detector.isMock(list2)

detector는 mock 객체에 대한 더 많은 정보를 얻는데 사용할 수 있다.

def mock = detector.asMock(list2)

expect:
mock.name == "list2"
mock.type == List
mock.nature == MockNature.MOCK

RestTemplate 사용시 주의사항

최근에 RestTemplate을 사용하면서 겪은 장애상황과 해결포인트에 대해 간략히 정리해 보았다.

발단

jetty를 이용한 웹서비스에서 다른 백엔드 컴포넌트의 rest api를 호출하는 부분의 구현방식을

변경해야 할 요구사항이 생겼다.

총 api 호출횟수는 (해당 사용자의 총 데이터 수 / 100)로 데이터가 많은 유저일수록

api호출횟수가 늘어났고, 전보다 rest api의 응답속도가 빨라져 단위시간당 api 호출횟수가

더 많아진 상황이었다.

이 부분에 RestTemplate을 적용해 배포하고, 대략 20분 뒤 웹서비스의 응답이 급격히

느려지면서 access log의 요청횟수가 평소의 절반으로 떨어지기 시작했다.

그리고 VisualVM으로 해당서버를 모니터링 해보니 jetty의 worker thread가 평소와 다르게

계단 모양으로 증가하는 추세를 보였다.

문제원인 분석

일단 의심스러운 구간별로 처리시간을 로그로 남겨 확인해 본 결과, 예상대로 RestTemplate으로

rest api를 호출하는 부분이 오래 걸리는 것을 데이터로 확인했다.

그리고 해당서버의 시스템리소스를 확인해보니, 유독 tcp connection 갯수가 평소의 적게는 3배

많게는 8배까지 증가한 걸 확인할 수 있었다.

그렇다면 왜 tcp connection이 갑자기 급격하게 증가했을까?

해결포인트

앞서 언급했듯이 호출횟수도 많긴 하지만 그보다 RestTemplate은 기본적으로 connection

pooling을 하지 않기 때문에, 매 요청마다 새로운 로컬포트를 열어서 서버로의 커넥션을

생성하게 된다. 이로 인해 tcp connection이 급격하게 증가하게 되었고,

이를 해결하기 위해 아래와 같이 connection pooling관련 설정을 추가한 HttpClient객체를

HttpComponentsClientHttpRequestFactory에 주입해 해결했다.

HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(xxx);
httpRequestFactory.setReadTimeout(xxx);
<strong>HttpClient httpClient = HttpClientBuilder.create()
 .setMaxConnTotal(xxx)
 .setMaxConnPerRoute(xxx)
 .build();
httpRequestFactory.setHttpClient(httpClient);</strong>
RestTemplate restTemplate = new RestTemplate(httpRequestFactory);

여기서 중요한 건,  HttpClient의 maxConnTotal과 maxConnPerRoute인데 이에 대한

설명은 https://stackoverflow.com/a/31892901 에서 확인하면 된다.

maxConnTotal과 maxConnPerRoute 갯수는 각 서비스의 상황에 맞게 적절한 값을

넣어주면 된다.

내가 겪은 상황이 일반적인 상황은 아니지만, 혹시나 비슷한 상황하에 RestTemplate을

사용한다면 꼭 connection pooling 설정추가를 고려해 보았으면 한다.

참고로, 아래링크는 최대 커넥션 수에 영향을 주는 클라이언트/서버관련 사항들이다.

https://stackoverflow.com/a/3923785

DbUnit 사용시 주의사항

최근에 테스트코드를 만들 때 spock-dbunit을 사용하면서 이상하게 동작하는 부분이 있어서

관련사항을 간단히 정리해 보았습니다.


1. spock-dbunit @DbUnit 데이터 초기화 문제

테스트코드 실행 전, DB의 데이터를 원하는 데이터로 초기화하기 위해 spock-dbunit

라이브러리의 @DbUnit 어노테이션을 아래와 같이 사용하였더니 예상과 다른 데이터가

들어갔습니다.

@DbUnit
def content = {
    User(id: 1, name: 'janbols')
    User(id: 2, name: 'bluepoet', ip: '127.0.0.1')

    Other_User(id: 1, name: 'bluepoet', ip: '127.0.0.1')
    Other_User(id: 2, name: 'janbols')
    Other_User(id: 3, name: 'tester', ip: '1.2.3.4')
}

Other_User 테이블은 3개 row의 id, name, ip 필드에 세팅한대로 값이 들어갔지만,

User 테이블의 2번 아이디의 ip필드의 “127.0.0.1”값은 들어가지 않았습니다.

관련해서 간단하게 테스트코드를 만들어 pr을 날렸고, 해당 pr이 merge되면서 프로젝트

운영자로부터 그것이 DbUnit의 결함이라는 피드백을 받았습니다.

참고 url : https://github.com/janbols/spock-dbunit/issues/4

%e1%84%89%e1%85%b3%e1%84%8f%e1%85%b3%e1%84%85%e1%85%b5%e1%86%ab%e1%84%89%e1%85%a3%e1%86%ba-2017-01-16-%e1%84%8b%e1%85%a9%e1%84%92%e1%85%ae-7-45-54

스크린샷 2017-01-16 오후 7.56.42.png

결론적으로, 내가 검증하고자 하는 필드는 첫번째 행에 모두 넣어야 합니다.

Other_User는 첫번째 행에 ip필드가 들어가 있어서 3번째 행에도 ip필드에 값이 정상적으로

들어가게 됩니다.

이와 관련한 DbUnit의 아래 내용을 읽어보시면 도움이 될 것 같습니다.

http://dbunit.sourceforge.net/faq.html#differentcolumnnumber

참고로, spring-test-dbunit은 동일한 테스트에서 위와 같은 문제가 발생하지 않았습니다.

@Test
@DatabaseSetup("sampleData1.xml")
public void tesNoValueLastName() throws Exception {
    List<Person> personList = this.personService.find("bl");
    assertEquals(1, personList.size());
    assertEquals("blue", personList.get(0).getFirstName());
    assertEquals("poet", personList.get(0).getLastName());
}
<!-- sampleData1.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
	<Person id="0" title="Mr" />
	<Person id="1" title="Mr" firstName="Mario" lastName="Zagar"/>
	<Person id="2" title="Mr" firstName="blue" lastName="poet"/>
</dataset>

참고 url :  http://bit.ly/2j7HEM4

2. spring-test-dbunit @ExpectedDatabase 반복사용 문제

spring-test-dbunit 1.3.0 이전버전을 사용하면, @ExpectedDatabase 어노테이션을

하나의 메서드에 반복 사용시, 마지막 어노테이션의 내용만 검증되는 버그가 있습니다.

그러므로 아래 코드의 expectedProductData.xml의 데이터가 틀려도 이 테스트는

성공하게 됩니다.

@ExpectDatabases 어노테이션도 동일하게 동작합니다.

참고 url : http://bit.ly/2jmqwTE

@Test
@DatabaseSetup("sampleData.xml")
@DatabaseSetup("ProductInitData.xml")
@ExpectedDatabase(value = "expectedProductData.xml", assertionMode = DatabaseAssertionMode.NON_STRICT) // verify error!
@ExpectedDatabase(value = "sampleData.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
public void testVerifyMultiTableValues() throws Exception {
}

스크린샷 2017-01-18 오후 3.17.44.png

이에 대해 override 설정을 “false”로 주면 해결되지만, 이에 대해 불편을 제기한 이슈가 있어서

1.3.0버전에 이 버그가 패치되었습니다.

1.3.0 버전에선 위 테스트가 원래 의도한 대로 실패하게 됩니다.

참고 url : https://github.com/springtestdbunit/spring-test-dbunit/issues/64
               http://bit.ly/2iGrbxi

 

 

 

[2016]올해를 마치며.. 회고!

2016 올해도 하루밖에 남지 않았다. 예전에 어른들이 나이가 들면 들수록 시간이 화살처럼

쏜살같이 지나간다는 말을 했었는데 마흔줄로 들어가고 있는 요즘 그걸 조금씩 실감하고 있다.

정신없이 지나갔지만 나름 열심히 달려왔던 2016 병신년 한해를 회고해본다.


1. 도전!!

올 한해를 보내며 스스로 가장 기억에 남는 건 “실시간 데이터 처리를 위한 인프라 구성과

관련 프로젝트를 진행한 것“이다.

openstackzookeeper, kafka, storm등의 기술을 이용해 클라우드를 구성하는 것부터가

경험이 없는 나로선 쉽지 않은 도전이었다.

다행히 사내 인프라가 잘 갖춰져 있었고 모르는 부분에 대해서 조언해줄 수 있는 동료가

곳곳에 있었기 때문에 가능한 일이었다고 생각한다.

프로젝트 초반엔 ansible의 역할이 참 컸는데, 다수의 가상 인스턴스를 가지고 클라우드를

세팅/운영하는데 있어 단시간에 많은 일들을 할 수 있었다.

kafka로 데이터를 보내는 역할(producer)은 logstash를 이용했고, storm에서 소비한

데이터는  사전에 설계한 HBase 테이블에 저장해 서비스에 활용했다.

그리고 운영시 storm supervisor 인스턴스를 늘려도 원하던 성능이 나오지 않아 고민하던 때,

storm 커미터이자 PMC 멤버인 임정택님의 도움을 받아 여러 세팅관련 조언을 듣고

운영상 어려움을 해결 할 수 있었다.

여담으로, storm topology  배포시 로컬과 운영 클라우드 환경이 다르기 때문에 꼭 서비스 전

운영 클라우드 환경에 배포해 테스트 해보는 것이 좋다.

그리고 데이터를 소비하는 어플리케이션 개발 특성상 테스트코드를 만들때

spockData Driven Testing을 이용해 보는 것도 좋을 듯 싶다.

서비스 어플리케이션 중 하나는 사내 mesos, marathon 인프라 위에 docker image를 만들어

배포하고 운영했는데 이것도 전에 없던 색다른 경험이었다.

2. 프로그래밍 언어

올해 단 한줄이라도 코드를 만들어봤던 프로그래밍 언어를 한번 나열해봤다.

java, groovy, golang, python, clojure, R, perl, shell script

예전에 비해 직/간접적으로 다뤄봤던 언어의 스펙트럼이 많이 넓어졌다.

그로 인해 자바의 세계에만 갇혀있던 내 시야도 조금은 트이는 한해가 된 것 같다.

그리고 아무래도 현재 하고 있는 일이 백엔드쪽 일이고 상대적으로 프론트엔드에 대한 관심이

적어서인지 전회사에서 많이 사용했던 javascript는 조금씩 멀어진 듯 하다.

groovy는 spock과 gradle을 쓰며 많이 사용했고, 간단한 스크립트 작업에도 유용하게

사용했다. clojure는 회사동료 덕분에 접해볼 수 있었고, 나머지는 회사일이나

개인적으로 공부하며 잠깐씩 다뤄봤던 언어들이 대부분이다.

2017년엔 아마도 다음과 같은 언어들도 필요에 따라 다뤄볼 수 있을 듯 하다.

scala, kotlin, swift

3. 번역

올해 후반기때 새로 시작한게 “번역”인데, 영어를 잘해서 한 건 아니고 spock으로 테스트코드를

만들면서 junit과 비교해 상대적으로 더 쉽고 직관적이라 쓰기가 편했다.

올해 중반엔 최범균님의 DDD START!의 junit 테스트코드를 spock으로 포팅하며 연습했고,

이것이 결국 페이스북에 “테스트코드와 친해지기“라는 그룹을 만드는 계기가 됐다.

물론, java개발자라면 groovy로 테스트코드를 만들어야 하는 spock에 대한 심리적인 저항이

있을 수도 있어서, 그걸 완화시키기 위한 일환으로 번역을 시작했다.

시작 전, spock 공식문서를 번역해도 되냐고 spock github issue로 올렸는데 친절히

내 블로그에 올리는 게 좋겠다는 댓글이 달려서 자신감을 얻을 수 있었다.

%e1%84%89%e1%85%b3%e1%84%8f%e1%85%b3%e1%84%85%e1%85%b5%e1%86%ab%e1%84%89%e1%85%a3%e1%86%ba-2016-12-31-%e1%84%8b%e1%85%a9%e1%84%8c%e1%85%a5%e1%86%ab-10-46-31

아직 3개정도 문서를 더 번역해야 한다.

spock관련해서 다른 포스팅도 있으니 함께 참고하면 좋을 듯 싶다.

추가적으로, 예전에 “토비의 스프링”의 저자 이일민님이 공부해보라고 추천해주신

JUnit A Cook’s Tour을 번역하면서 내용에 나온 junit의 목표와 철학, 그리고 설계에

반영된 다양한 디자인패턴에 대해 알게되었다.

4. 스터디

올해 스터디를 했거나 현재 진행중인 것은 6개다.

특히 지금 안드로이드 스터디를 discord를 통해서 하고 있는데 시간에 제약이 있는 나로선

온라인으로 스터디시 굉장히 유용할 것 같아 내년에 적극 활용해 볼 생각이다.

5. 읽었던 책

올해 읽었던 책은 대략 20~30권 정도 되는 것 같다.

현재 회사의 도서 지원이 빵빵해서 책을 사서 보는 데 아무런 제약이 없는 것이

너무 좋다. 그런데 필요할 때마다 이것저것 봤던 터라 완독률은 많이 떨어졌다.

그리고 새로 생긴 습관이 예전엔 책을 처음부터 끝까지 정독했다면 지금은

책을 보기 전에 목차를 한번 쭉 훑어보고 내가 필요한 부분만 본다는 점이다.

양쪽 모두 장단점이 있지만 일단 현재로선 필요한 때마다 그때그때 보는 것을

선호하게 될 듯 싶다.

내 머리가 좋아서 동시에 여러권을 봐도 스펀지처럼 쫙쫙 흡수해서 실무에

활용하면 좋겠지만, 그건 애초에 불가능하니 집에다 모셔두면서 썩히기보단

차라리 완독률은 떨어져도 다독을 하는 것이 나에게 더 좋을 듯 싶다.

끝으로 올해 읽으면서 재밌던 책을 뽑아봤다.

아! 그러고 보니 올해는 기술관련된 책 이외의 양서는 한권도 보지 못했다(책 편식현상이 ;;)

6. 올해 아쉬운 점과 새로운 도전꺼리들

올해 아쉬운 점은 “개인 프로젝트를 하지 못한 것“과 “오픈소스 contribution“을 도전하지 못한

것이다. 둘다 실행에 옮기지 못한 이유는 아마도 해야될 절실함을 스스로 찾지 못해서이지

않을까 싶다.

지속적으로 책을 보고 인터넷이 널린 다양한 콘텐츠를 코드로 따라쳐가며 공부했지만,

회사일을 제외한 뭔가 나만의 코드가 없다는 공허함은 지울 수가 없다.

%e1%84%89%e1%85%b3%e1%84%8f%e1%85%b3%e1%84%85%e1%85%b5%e1%86%ab%e1%84%89%e1%85%a3%e1%86%ba-2016-12-31-%e1%84%8b%e1%85%a9%e1%84%8c%e1%85%a5%e1%86%ab-11-16-59

그래서 내년엔 위의 github contribution에 내 코드를 꽉꽉 채워넣을 생각이다.

아이템도 있으니 실행에만 옮기면 된다. 지속적으로 동기부여를 원활하게 할 만한 방법도

고민해 볼 것이다.

올해 고무적인 것은 Trello를 회사일과 개인적인 일에 적극 활용했다는 것인데,

특히 개인적으로 쓰는 것보다 회사 프로젝트에 사용했을 때 프로젝트의 진척사항이나

팀원별 할일 관리, 현재 이슈사항 파악등에서 큰 효과를 보았다.

그리고 무엇보다 할 일을 가능한 작게 쪼개서 해보니 생각보다 할일이 더 명확히 보이고

일의 진행도 더 원활하게 됨을 체험할 수 있었다.

내년엔 개인적으로도 더 적극적으로 활용해 볼 생각이다.

일을 완료하고 카드를 옮기는 재미가 생각보다 쏠쏠하다.

마지막으로, 현재 내 관심꺼리는 “데이터처리와 분석을 통한 추천“과 “모바일개발“인데

관심에 비해 실행과 결과물이 많이 없어서 내년엔 지속적인 공부와 더불어

뭐라도 하나 “쓸모있는 물건!“을 만드는 게 가장 큰 목표다.