We can work IT out2016. 4. 14. 14:54

이틀 전에 19대 국회의원 의결 성향에 대한 분석을 공개했었습니다. 정당은 빨강과(여) 나머지 파랑/초록/노랑이(야) 위/아래로 갈려있는 반면 의결 성향은 왼쪽/오른쪽으로 나뉘어있음을 보였었죠.

오늘은 어제 20대 국회의원 지역구 당선자를 19대 국회 의결성향에 그려봤습니다. 19대 국회의원들 중 20대에 당선된 사람들의 의결성향이 어느쪽일까 궁금했기 때문입니다. 그 결과 극 왼쪽 일부와 오른쪽 다수가 20대 국회에 재선되었음을 보게 됩니다.

그림에서 흐릿하게 표시된 사람들은 공천을 못 받았거나 20대 국회에서 낙선한 사람들 입니다. 출마자를 대상으로 낙선과 당선을 따지지 않고 단순히 당선자 목록을 19대 국회의원과 교집합 시켰기 때문에 각 당의 공천 성향이 상당 수 반영되었을 수도 있습니다.





Posted by Lyle
We can work IT out2016. 4. 12. 12:04

선거공보물이 도착했습니다. 모든 후보들이 낯설기만 합니다. 제가 19대 국회의원 선거에서 지지하여 당선됐던 후보는 20대 국회의원 선거에 나오질 않았습니다. 입후보한 사람들에 대한 공부를 해야 합니다. 지지하는 정당은 있습니다. 그러나 정당을 지지한다고 무작정 그 정당의 후보를 지역구 국회의원으로 뽑을 수는 없습니다. 무조건 기피하고 싶은 정당이 있지만 가끔 보면 그 정당에도 괜찮은 입후보자가 있을 수 있

고, 반대로 지지하는 정당에도 보기 싫은 정치인이 있기 때문입니다.


그래서 생각하길, 국회의원을 다시 분류해보기로 했습니다. 예를 들어 새누리당 안에서도 친박이 있고 반박이 있다고 하듯 서로 성향이 다르고 그만큼 의정활동도 다르게 했을 것 같습니다. 또한 그런 성향과 의정활동 내용은 같은 정당 내 국회의원들보다 다른 정당의 국회의원과 유사성을 갖을 수도 있겠다는 생각입니다.


19대 국회 의안들 중 표결이 이뤄진 것들을 취합했습니다. 데이터는 국회정보시스템, 참여연대 열려라국회, 그리고 포커 등의 사이트에서 크롤링 혹은 마이닝 했습니다. 수집한 의안 표결 건수는 총 1739건 입니다. 이중 찬성표가 330,489개, 반대표가 172,823개, 기권을 포함한 그외 표가 4,476개 입니다. 4,476개 중에는 표결에 참여한 국회의원의 명시적인 기권표도 있지만 불참 또는 결석 등도 다수 있는데, 모두 기권으로 분류했습니다.


현재 시점에서 국회의원은 292명입니다. 그런데 의안 표결 결과는 항상 292명이 아니고 또 도중에 표결 국회의원이 바뀌기도 합니다. 19대 국회 도중에 의원직을 상실한 사람들도 있고 보궐선거로 중간에 국회에 입성한 사람들도 있기 때문입니다. 이런 사람들의 경우 의정활동을 하지 않은 시점의 표결 결과가 기권으로 분류됐기 때문에 노이즈로 작용합니다. 그러나 전체 중 작은 일부이므로 노이즈를 무시했고, 의원직을 상실했거나 보궐로 당선된 국회의원의 경우 결과에서 그 사연을 감안해서 보면 되겠습니다.


아래 그림은 국회의원별 의안 표결 결과를 찬성, 반대, 기권으로 분류해서 두개의 클러스터로 재분류한 것입니다. 클러스터링 방법은 K-means 를 사용했고 PCA 를 사용해 1739차원의 벡터를 2차원으로 줄여 화면에 표시했습니다. 각 점은 국회의원 한 명을 나타냅니다. 이 점들은 정당과 상관 없이 하나의 의안에 대해 같은 내용의 투표를 많이 할 수록 점은 서로 가까이 표시됩니다. 반대로 어떤 의안에 대해 서로 다른 내용의 투표를 많이 한 점들은 서로 멀리 표시 됩니다.


그림1. 2개로 클러스터링된 19대 국회의원 성향


파란색(cluster0)과 주황색(cluster1)은 각 군집의 중심(센트로이드)에서의 거리를 기준으로 분류된 것 입니다. 군집의 경계에 있는 점들은 분류하기 애매한 경우로 볼 수도 있고 군집의 좌우 양 극단에 있는 경우 훨씬 더 성향이 대조적인 것으로 볼 수 있죠. 그런데 이런 표결 성향은 무조건 여당과 야당으로 나뉘어지지 않는 걸 아래 표에서 볼 수 있습니다. 


정당명국민의당더불어민주당무소속민주당새누리당정의당
cluster#

0 파랑

1584911075
1 주황61920440


위 표는 정당별로 파란색(황cluster0)과 주황색(cluster1)으로 분류된 수를 표시한 것입니다. 파란색 분류(cluster0)에는 다수의 국회의원들이 속해 있습니다. 이 분류 안에 더불어민주당과 새누리당은 잘 협력하고 있는 모습니다. 또한 정의당은 한가지 성향으로만 단결되어 있는데, 이들을 진보성향이라고 레이블링 한다면 나머지 정당들의 국회의원들 상당수가 정의당 의원들처럼 진보적인 표결을 했다고 볼 수도 있겠습니다.


반대로 정의당 의원이 존재하지 않는 주황색 cluster1 에는 새누리당 국희의원 중 1/3에 해당하는 44명이 포진해있습니다. 정의당의 진보적 성향에 반한 이 군집을 보수성향이라고 레이블링 한다면 더불어민주당의 19명 국회의원들도 이들과 비슷한 보수 성향을 갖고 있다고 읽을 수 있겠죠.



그림2. 국회의원 성향 클러스터링 결과에 정당명과 국회의원 이름 레이블링


이번에는 각 점에 해당하는 국회의원의 이름을 표시해봤습니다. 각당의 대표색깔에 맞춰 새누리당은 빨간색, 더불어민주당은 파란색, 국민의당은 녹색, 정의당은 노란색, 그리고 무소속(11명)과 민주당(1명)은 회색입니다. 표결 성향은 좌우로 나뉘어있는데, 그 위에 당의 색깔을 표시해보니 상하로 나뉘어진 것이 흥미롭습니다. 더불어민주당, 국민의당, 정의당 소속 국회의원은 거의 다 아래편에 군집했고 새누리당은 윗편으로 갈리면서 야와 여의 성향이 서로 다름을 확인할 수 있습니다.


혹시 20대 국회의원선거에서 자신의 지역구에 출마한 19대 현직 국회의원이 있다면, 그런데 그 국회의원의 성향이나 의정활동을 잘 알지 못한다면 위 플롯을 약간 참고하셔도 좋겠습니다. 해당 출마자의 이름을 찾고 그 주변에 자신이 타지역구이건 타정당이건 상관 없이 자신이 지지하는 국회의원이 있다면 눈여겨볼만한 가치가 있을지도 모릅니다. 혹은 거꾸로 평소 자신이 지지하던 국회의원의 이름을 먼저 찾고 그 국회의원 주변에 어떤 의원들이 있는지 살펴보는 것도 좋겠습니다.




Posted by Lyle
We can work IT out2014. 4. 25. 03:53



얼마전 사용하던 맥북에어가 부팅이 되질 않았습니다. SSD 가 고장이어서 부품을 사다가 바꿔주니 다시 살아나기는 했는데, 그렇게 복구하기까지 꽤 오랜 시간과 노력이 들었죠. 그사이에 사용할 대체품이 필요했기 때문에 이참에 최신 맥북으로 바꿀까 생각했지만 돈이 아까웠습니다. 09년 맥미니, 10년 맥북을 사용하면서 불편을 끼치는 문제는 메모리지 CPU 가 아니기 때문에 엇비슷한 성능의 CPU를 가지고 디자인이나 디스플레이 개선해가며 나온 이후의 모델들에 그다지 끌리질 않았거든요. 그래서 생각한 것이 더미 터미널(dummy terminal) 처럼 사용할 수 있는 랩탑이었습니다. 그리고 결국 크롬북을 만나게 되었죠.





클라우드 컴퓨팅 환경이 마치 새로운 패러다임인 것처럼 이야기되면서도 아직은 범용성을 갖지 못하고 있는데, 이름이 달라졌을 뿐 유사한 개념의 컴퓨팅 환경은 이미 80년대에서부터 시작되었습니다. 어렴풋이 알기로 그당시 MIT 에서 IBM 과 함께 아테나 프로젝트란 걸 했었는데, 캠퍼스 안에서 교육용 분산 컴퓨팅 환경을 구축하기 위한 프로젝트쯤 되었다고 합니다. 그리고 거기서 시작되어 LDAP, Active Directory, 메신져서비스 등이 만들어졌고 그뿐만 아니라 더미 터미널과 X윈도우도 그때 만들어진 거라고 합니다.



오래전 학교 컴퓨터실에 SPARC Xterminal 이 두어대 있었는데, 아무도 사용하질 않았기 때문에 주로 저와 몇몇 사람들만 사용했던 것 같습니다. 하드디스크는 커녕 디스크 드라이브도 없고, 켜봐야 텍스트 환경이어서 사람들에게 익숙하게 사용되기 어려웠죠. X윈도우를 띄우면 넷스케이프 같은 응용프로그램들도 몇개 쓸 수 있었지만 그역시 서버에서 윈도우 디스플레이를 터미널로 돌리기 위한 환경변수를 설정해줘야 했기 때문에 유닉스 환경에 익숙한 사람들만 사용했던 겁니다. 그덕에 프로그래밍 숙제 마감날짜가 다가오면서 PC 선점하기 위한 경쟁이 벌어질 때마다 스트레스를 받을 필요가 없었죠.


X terminal대충 이것과 비슷하게 생긴 터미널이었습니다. 입출력장치로는 모니터, 키보드, 마우스가 있고, 본체에는 HDD 나 플로피디스크드라이브도 없이 단지 X서버만을 내장해서 리모트데스크탑 같은 기능만을 할 뿐이죠.X윈도우 화면지금은 리눅스에도 화려한 윈도우가 대중적이지만, 당시엔 X11 API 또는 Motif 프레임워크로 윈도우 프로그래밍을 해야 했는데 기본적인 위젯 이외에 꾸밀 수 있는 게 없었죠.


정확히 따지자면 이런 컴퓨팅 환경은 Thin Computing 이지 분산컴퓨팅은(Distributed Computing) 아닙니다. 분산컴퓨팅 환경이란 어떤 커다란 계산을 하거나 혹은 특정 서비스를 제공하기 위해 여러 컴퓨팅 노드들이 활용되는 개념이지 사용자가 그러한 서비스를 이용하기 위해 원격에서 접속하는 것까지를 범위에 넣지는 않습니다. 반면 Thin Computing 이란 다른 노드에 의존적인 클라이언트를 통해 서비스를 이용하는 환경을 의미하기 때문에 위에서 소개한 X terminal 은 분산컴퓨팅이기보다 Thin Computing 환경이었다고 말해야 맞겠죠. 그리고 클라우드 컴퓨팅 환경이란 말도 앞서 둘의 개념들과 교집합이 있을 뿐이어서, 사용자에게 서비스의 인터페이스만을 제공할 뿐 사용자의 연결 방식을 Thin Computing 으로 제한하지도 않고, 서비스 내부적으로 분산환경이라 해도 그것이 사용자에게 제공하는 인터페이스에는 아무런 영향을 미치지 않는 구름과 같은 모호성을 갖는 개념일 뿐입니다. 그래서 클라우드 서비스는 반드시 분산컴퓨팅으로 제공될 필요도 없고 사용자 역시 다양한 노드를 통해 동일한 서비스를 이용하게 되는 데서 결을 달리 하는 개념인 거죠.



사실 제가 경험해본 Thin Computing 은 앞서의 X terminal 보다 더 오래 되었습니다. 하이텔단말기가 그 앞에 있었죠. 하이텔 단말기는 90년대 초부터 PC통신 하이텔의 보급과 함께 한국통신에서(현 KT) 마치 메가패스 ADSL모뎀 대여하듯 보급했던 단말기 입니다. 오로지 하이텔 서비스만 이용할 수 있었고, 깡통이기는 X terminal 과 동일했죠. 짐작이지만 내부 구조나 구동 SW 도 X terminal 과 거의 유사할 겁니다. 다만 랜선 대신 전화선을 연결해서 원격 UNIX 환경에 구동되어있는 하이텔에 접속해서 텍스트 기반의 화면을 보여주는 거죠. 한국의 PC 보급은 인터넷의 보급과 함께 이뤄졌다고 봐야 하기 때문에 모뎀 통신하던 시절에 하이텔 서비스의 이용을 늘리기 위해서 더미 터미널을 보급했던 거지요. 


하이텔단말기하이텔 단말기 출처 : http://zecca.tistory.com/114프랑스 미니텔 단말기프랑스 미니텔 단말기 출처 : https://flic.kr/p/9XLdSE



그런데 사실 하이텔단말기는 프랑스의 미니텔을(minitel) 따라한 겁니다. 1978년부터 시작된 프랑스의 미니텔이야말로 Thin Computing 의 시초가 아닐까 싶은데, 프랑스텔레콤은 장기적인 성장안으로 영국까지 사업영역을 넓힐 계획도 만들었었고 미국에도 퍼뜨릴 생각이었다죠. 아마 그와중에 한국에 영향을 미치게 되었던 거라고 짐작해볼 수 있습니다. 한국에서 90년대에 시작된 하이텔단말기가 존재감이 없었던 것에 비해 미니텔은 90년대 2천5백만명까지 이용자가 증가했었다고 합니다. 더 놀라운 것은 미니텔이 불과 2년전인 2012년에 사업을 접었다는 거죠. 한국에서 하이텔 단말기는 2013년에 방영된 응답하라1994 같은 드라마에서 추억하는 대상인 반면 프랑스에서는 여전히 인터넷에 익숙하지 않은 할머니 할아버지 집에 가면 만날 수 있는 살아있는 전설이었던 거죠. 미니텔의 서비스 종료 당시에도 약 60만명의 사용자가 있었다고 합니다. 



제가 꽤 오랜시간동안 애플의 컴퓨터들만 사용했던 것은 디자인이 좋아서도 성능이 뛰어나서도 아닙니다. 개발용 랩탑이란 말도 있는데 저는 거기에 동의하지도 않습니다. [각주:1] 애플 기기들의 매력은 여러개를 한꺼번에 사용할 때 비로서 경험하게 되는데, 바로 기기들간의 연결성 때문에 저는 애플의 제품들을 애용해왔습니다. 애플의 제품들에는 꽤 오래전부터 어떤 철학처럼 구축되어온 기기들간의 연동 컨셉이 있는데, 그것이 처음엔 개인 클라우드 환경이었다면 mobile me 와 이후 icloud 가 등장하면서 더 큰 범위로 확장되려는 시도가 있은지 벌써 오래 되었죠. 그러다가 스티브 잡스가 죽고 그 철학이 엉망이 되어가고 있음을 하나 둘 느끼기 시작했습니다. 그래서 저는 스티브 잡스의 죽음 이후의 애플을 의심하게 되었고, 메버릭스(OS X 1.9) 업데이트를 하지 않고 마운틴 라이언을 쓰고 있으며 iOS 역시 6버젼에 머물다가 앱들의 지원이 안 따라줘서 불과 얼마전에 어쩔 수 없이 7버젼으로 갈아탔을 정도로 애플이 유지해왔던 컨셉을 좋아했었습니다. 아마도 얼마전 맥북이 고장났을 때 최신의 맥북으로 대체하고 싶은 마음도 안 생겨났던 이유로 한몫을 차지하기도 하겠죠.


HP chromebook 11HP Chromebook 11


그러다가 크롬북을 들여다보게 되었는데, 그야말로 터미널 랩탑이더군요. 어차피 무거운 작업들은 서버에서 처리할 것이므로 랩탑은 큰 저장공간이나 빠른 CPU 도 필요 없습니다. 다만, 이런 Thin Computing 장비는 하이텔이나 미니텔이 그랬듯 특정 서비스 이용을 위한 단말기일 뿐이어서 인터페이스가 폐쇄적입니다. 애플 제품과의 직접적인 연동성까지 기대하지는 않더라도 네트웍드라이브 지원이라도 해준다면 어떻게든 연결해서 사용하겠지만 그야말로 구글드라이브 아니면 USB드라이브만이 저장매체로써의 대안이죠. 그렇게 스토리지 서비스만 아니라 그밖의 각종 컨텐츠 등도 구글의 서비스만을 향하고 있죠. 이런 폐쇄성을 사용자들이 '개선'의 대상으로 여긴다면 그건 착각일 뿐입니다. 하이텔에 접속하기 위해 보급시킨 하이텔단말기로 나우누리나 데이콤에도 접속할 수 있도록 01420, 01433 등 전화번호를 지정할 수 있게 개선해달라고 하는 것과 같은 거죠. 그러니 크롬북이 폐쇄성을 '개선'할 일은 앞으로도 없을 겁니다.



스티브 잡스가 살아있었다면 어쩌면 다음에 나올 랩탑은 크롬북과 같은 것이지 않았을까요? 자꾸 더 작게 만들어서 줄줄이 이름에 "Air" 붙이는 것도, 줄줄이 레티나 디스플레이 달아서 새제품이라고 발표하는 것도, 인텔의 저전력 하스웰 CPU 심고서 롱라이프를 자랑하는 식으로 작금의 애플 같지는 않았을 테니 컨샙을 바꾸는 시도가 분명 있었을 법 하고, 비록 구글이 먼저 시작하긴 했어도 icloud 를 포함한 제품들간의 연계를 극대화시킨 형태의 랩탑과 태블릿의 중간 어디쯤의 하드웨어가 나오잖았을까 상상해봅니다. 저에겐 바로 그런 게 필요한데 애플의 제품들이 조금씩 엉망이 되어가고 있으니 어쩌면 크롬북을 시작으로 애플과 멀어지게 될지도 모르겠다는 생각도 듭니다.




  1. OS X 가 유닉스 커널을 기반으로 하고 있다 해서 그것이 유닉스가 되는 것도 아니기 때문에, 같은 BSD 기반이었다가 나중에 System V 에 기반했던 SunOS 랑 비교해도 다른 점이 무척 많죠. System V 기반인 HP-UX 와 IBM AIX 역시 서로 너무 달라서 SunOS 에서 개발한 프로그램을 HP-UX 또는 AIX 에 포팅하는 게 쉬운 일이 아니었던 걸 생각했을 때 OS X 는 독자적인 개발환경을 갖고 있음에도 리눅스 개발자들에게 환영 받는 것은 그것이 유닉스 기반이기 때문이라고 하는 건 아마 착각하고 있기 때문일 겁니다. [본문으로]
Posted by Lyle
We can work IT out2011. 1. 1. 11:13
새로운 맥이 생겨서 옛날 3.0 버젼의 파이어폭스를 설치하려고 오래된 제 글을 다시 찾아 링크를 클릭해보니 주소가 바뀌었더군요.

http://releases.mozilla.org/pub/mozilla.org/firefox/releases/3.0.19-real-real

그리고 전에는 한글 버젼이 없어서 English Global (en-GB) 을 설치했었는데, 지금은 한글 버젼(ko) 가 있으니 그걸로 설치해도 좋겠습니다.
Posted by Lyle
We can work IT out2010. 1. 31. 14:29
Few days ago, there was a minor version up to 2.8.3 on the CruiseControl since they had released previously latest 2.8.2 almost a year before. They are versioning up once in a while, which mean the CruiseControl is quite stable and also satisfying large number of users as a most populer tool for Continuous Integration. On the other hand, it looks like rather lazy to me because there are some bugs have not been fixed since reported quite long time ago (ex. time zone problem) and a few improvements every Tome, Dick, and Harry can think about.

Fortunately, the CruiseControl is well structured so that a novice programmer, I can fix and improve it for myself. Hope I can afford the time to put effort on it and contribute to the world. And here's the first step, a tiny thing about improving composite builder.

By binding up your builders with composite builder, you can run dozens of builders on single modification event. But in this case, with the composite builder, the second exec builder might not execute when the first ant builder show up an error. There's no way to ignore the error to make next bulider execute. So you should write a script which wraps all the builders and finally, the composite builder became useless cause the script takes the role. That's why I decided to modify the source of composite builder and add "continueOnFailure" option as an attribute of <composite/> element.

Here's a part of "config.xml" file as an example show you how to use.
<modificationset>
    <alwaysbuild/>
</modificationset>
<schedule interval="10">
    <composite continueOnFailure="true">
        <exec command="./error.sh" args="1" workingdir="/home/lyle/cruisecontrol-bin-2.8.3"/>
        <exec command="./error.sh" args="2" workingdir="/home/lyle/cruisecontrol-bin-2.8.3"/>
    </composite>
</schedule>

And the script "error.sh" for a test ran by exec builder is simple.
#!/bin/sh

echo "==============EXIT WITH ERROR==================="
exit $1

There are no differences between 2.8.3 and 2.8.2 on Composite Builder. So just download "CompositeBuilder.class" attached below and update existing archive "cruisecontrol.jar" with the command line shown beneath in your CruiseControl home dir. Of course you should restart your CuirseControl after this.
$ jar -uvf cruisecontrol.jar net/sourceforge/cruisecontrol/CompositeBuilder.class
Posted by Lyle
We can work IT out2009. 12. 1. 13:25
최근에 저는 필요에 따라 GNU make 의 latest 인 3.8.1 의  소스코드를 수정하고 있습니다. 커맨트가 꽤 잘 달려있긴 하지만 GNU 코드 컨벤션을 따르고 있음에도 인덴테이션이 엉망으로 되어있더군요. 윈도우즈, MSDOS, AMIGA, EMX, VMX 등 유닉스가 아닌 환경에 대한 경우와, 유닉스라 해도 POSIX 따위에 대한 라이브러리 존재 여부에 대한 조건 정의들이 많아서 "#ifdef" 같은 코드를 읽기 어렵게 만드는 키워드들이 난무하는데다가 인덴테이션까지 엉망이니 도무지 코드 읽기가 쉽지 않더군요.

조금만 살펴보면 인덴테이션이 인의적으로 엉망이 된 게 아니라 사실은 tabstop 의 문제임을 확인할 수 있습니다. 이런 경우가 자주 발생하는 건 편집기 환경에 따라서는 소스코드의 탭tab과 인덴테이션indentation이 엉망이 돼버리는 경우가 생기기 때문이죠. 다수의 사람이 윈도우즈와 유닉스를 오가며 VIM, Eclipse, UltraEdit 등의 다수 편집기로 편집하다보면 왕왕 발생할 수가 있죠. VIM 하나만 사용한다 해도 사용자 각자의 설정에 따라서는 탭이 4개 또는 8개의 스페이스space 문자로 바뀐다거나 하면서 추가적인 편집에서 귀찮은 일들을 만들곤 합니다.

VIM 사용자라면 이런 귀찮은 일들을 한 방에 해결할 수 있습니다. 설명은 일단 생략하고 VIM 의 커맨드라인 모드에서 다음의 순서로 입력하기만 하면 됩니다.
:set tabstop=8
:set filetype=c
:filetype indent on
:e
gg=G

이제 각 라인에 대한 설명입니다. 위 3번 라인을 실행하기 전에 커맨드라인 모드에서 아래를 실행하면 "filetype detection:ON plugin:ON indent:OFF" 라고 나올 겁니다.


:filetype

물론 .vimrc 파일에 설정에 따라서 다르게 나올 수도 있지만 일단 indent:OFF 상태인 걸 확인합니다. 만약 indent:ON 인 상태면 위 3번 라인을 실행할 필요가 없으니까요. 이제 하나씩 들어가보죠.


:set tabstop=8

tabstop 이 4 로 되어있었다가 특정 편집 환경으로 가면 탭 문자 '\t' 가 스페이스 4개로 바뀌는 경우가 생깁니다. 그리고 또다시 tabstop 이 8 로 된 편집환경에서 탭을 통한 인덴테이션이 추가되면 결국 탭과 스페이스가 마구 섞이는 경우가 만들어지죠. 그래서 일단은 탭을 스페이스 여덟칸으로 표준화 해줘야 합니다. tabstop 이 4 로 되어있을 경우 나머지 과정을 실행하면 탭 두개, 또는 탭 하나와 스페이스 네 개를 한 번은 인덴테이션인 것처럼 출력될 수도 있습니다.


:set filetype=c

인덴테이션할 형식을 C 언어로 합니다. xml, java, ruby 등 필요에 따라 설정하면 됩니다. 지원하는 인덴테이션 형식은 아래 커맨드로 확인할 수 있습니다.

:e $VIMRUNTIME/indent


:filetype indent on
:e

파일 타입에 대한 인덴테이션을 켠 후 바뀐 환경에 대해서 편집할 파일을 다시 읽어들입니다.


gg=G

첫번째 줄(gg) 부터 마지막 줄(G) 까지 인덴테이션 기능(=)을 행별로 실행하라는 의미입니다.
 
Posted by Lyle
We can work IT out2009. 10. 31. 01:39
이 글은 행위 주도 개발(Behaviour-Driven Developement)를 주창한 댄 놀쓰(Dan North)가 그의 블로그에 쓴 글의 번역입니다. (번역: 이홍주)

행위 주도 개발은 밖에서 안으로 향한 "outside-in" 기법이다. 그것은 비지니스적 요구사항을 식별하는 것부터 시작해서 그런 요구사항 각각을  충족시키는 특성들의 묶음에 대해 파고든다. 각각의 특성들은 "스토리"로 기록되는데,  그것은 인수조건들과 함께 특성들의 범위에 대해 정의한다. 이 글은 스토리들과 그것들의 인수조건(acceptance criteria)을 정의하고 식별하는 BDD 접근법에 대해 소개한다.


서론

소프트웨어 출하는 비지니스적 요구사항을 충족시키는 소프트웨어를 만드는 행위다. 쉬워보이지만, 행적적이거나 환경적 요인들이 이런 사실을 떠올리지 못할만큼 혼란스럽게 만든다. 때때로 소프트웨어 출하는 상급자들을 만족시키는 낙관적인 보고서들을 작성하는 일이거나, 단지 월급 받는 고용인들을 바쁘게 만드는 일을 만드는 것 처럼 보여지기도 하는데, 이에 대한 이야기를 하자는 건 아니다.

보통 비지니스적 요구사항들은 너무 조잡해서 곧바로 소프트웨어를 만드는 데 사용될 수 없기 때문에 (만일 "운영 비용을 5% 절감한다" 라는 요구사항이 주어진다면 어디서부터 코딩을 시작해야 할까?) 일을 완수하기 위해서 중간 단계에서 매개하는 요구사항을 작성할 필요가 있다.

모두가 파악할 수 있을 정도로 요구사항이 명확하다면, 행위 주도 개발(Behaviour Driven Developement)은 요구사항에 대한 인식을구현, 검증되어 출하 가능한 상태의 코드가 되도록 하는 역할을 한다. 그러기 위해서 비지니스 이해관련자들, 분석가, 개발자, 그리고 테스터 모두가 그 일의 범위에 대해 공통적인 이해를 하도록 하는 어떤 수단으로 요구사항을 기술하는 일이 필요하다. 그리고 그것으로부터 모두가 공통적으로 "완료" 라는 말에 동의할 수 있게 되고, "그건 내가 바라던 바가 아니야" 라거나 "내가 이걸 말해준다는 걸 깜빡 했네" 같은 이중의 함정으로부터[각주:1] 벗어나게 된다.

동시에 이것이 스토리의 역할이다. 그것은 요구사항과 비지니스적 이득 그리고 우리 모두가 "완료" 라고 인정할 수 있게 하는 조건들에 대한 설명이어야 한다. 이것은 "요구사항 정의서" 또는 "기능 정의서"[각주:2] 등의 여러가지로 기술되는 다른 애자일 방법론들보다 더 정확한 정의다. (일의 범위가 결정되고 평가되고 합의된 상태라면, BDD 스토리로 비함수적인 요구사항을 손쉽게 기술할 수 있다.)


스토리의 형식

BDD 는 스토리에 형식을 입힌다. 이는 필수적인 건 아니어서 다른 스토리 형식을 쓰면서도 BDD 를 수행할 수는 있다. 하지만 다양한 형태와 크기의 많은 프로젝트들을 통해 효과가 있음이 증명되어왔기 때문에 여기 소개하고 있는 것이다. 스토리는 최소한 아래의 양식에 기술된 모든 요소들을 담고 있어야 한다. 

제목 (한 줄로 된 스토리 설명)
전말:
[역할]로 하여금
[이득]을 얻기 위해서
[기능]이 필요하다.

인수조건: (아래 시나리오들로 표시됨)

시나리오 1: 제목
조건 [조건 설명] 그리고 [또다른 조건 설명]...
만일 [발생한 사건 설명]
그러면 [기대 결과] 그리고 [또다른 기대 결과]...

시나리오 2: ...


스토리 전달하기

하나의 스토리는 몇몇 사람들간의 의사소통의 결과물이어야 한다. 어떤 비지니스 분석가가 비지니스 이해관계자[각주:3]와 기능 또는 요구사항에 대해 이야기하므로써 스토리의 전말(narrative)을 구조화 하는데 도움이 될 수 있다. 그런 후 테스터는 어떤 시나리오들이 중요하고 어떤 게 덜 유용한지 결정하므로써 인수조건의 형태로 스토리에 대한 범위를 정의한다. 기술분야 대표로 하여금 스토리에 포함된 일의 분량에 대한 대체적인 견적 파악을 가능하게 하고 대안을 제시하게 된다. 애초부터 요구사항을 낸 사람들만큼, 시스템에 대한 좋은 아이디어들이 요구사항을 구현하는 사람들로부터 많이 나온다.

이는 일련의 반복적인 과정(iterative process)일 수 있다. 이해관계자들은 그들이 원하는 것에 대해 알지언정 대게 얼마나 많은 노력이 드는지는 모르거나 혹은 어떻게 일이 배분되어야 하는지 모를 것이다. 기술전문가들과 테스트 전문가들의 도움으로 그들은 각각의 시나리오에서 비용과 이득의 타협점(trade-off)을 이해하고 그들이 그것을 원하는지에 대한 결정을 내릴 수 있다. 물론 역시 이는 다른 요구사항들과도 균형을 이뤄야 한다. 즉, 이 스토리를 더 구체화 할 것인지 혹은 다른 스토리로 넘어갈 것인지 말이다.

때때로 개발팀은 대략적인 파악조차 할 수 없을 때가 있다. 이런 경우 그들은 요구사항에 대한 이해를 위해 "spike" 라고 알려진 조사작업을 수행한다. (언젠가 이에 대해 더 자세히 다룰 계획이다.)


적절한 스토리의 특성

Introducing BDD 에서 들었던 것과 같은 예시를 통해서 ATM 에서 현금을 인출하는 경우에 대한 요구사항을 살펴보자.

스토리: 예금자 고객의 현금 인출

(역할) 예금자 고객으로 하여금
(이득) 은행이 문을 닫았을 때 돈을 얻을 수 있게 하기 위해
(기능) ATM 에서 현금을 인출이 필요하다.

시나리오 1: 계좌에 잔고가 있는 경우
(조건) 계좌 잔고가 $100 이다.
그리고 카드가 유효하다.
그리고 출금기에 현금이 충분하다.
(만일) 예금자가 $20 출금을 요청한다.
(그러면) ATM 은 $20 를 출력해야 한다.
그리고 계좌 잔고는 $80 이어야 한다.
그리고 카드가 반환되어야 한다.

시나리오 2: 계좌에 잔고가 부족한 경우
(조건) 계좌에 잔고가 $10 이다.
그리고 카드가 유효하다.
그리고 출금기에 현금이 충분하다.
(만일) 예금자가 $20 출금을 요청한다.
(그러면) ATM 은 현금을 출력하지 말아야 한다.
그리고 ATM 은 잔고가 부족함을 알려야 한다.
그리고 계좌 잔고는 $20 여야 한다.
그리고 카드가 반환되어야 한다.

시나리오 3: 사용 불능인 카드인 경우
(조건) 카드가 사용 불능이다.
(만일) 예금자가 $20 를 요청한다.
(그러면) ATM 은 카드를 반환해야 한다.
그리고 ATM 은 카드가 폐기되지 않고 존속되어왔음을 알려야 한다.

시나리오 4: ATM 에 현금이 불충분한 경우
...

이처럼 계좌 잔고나 카드 상태와 ATM 자체와 관련된 몇가지 시나리오들이 나온다. 이제 위의 스토리가 적절한지 아닌지를 판단하기 위해 파고들어가보자.



제목은 어떤 행위에 대한 것이어야 한다.


스토리의 제목, "예금자 고객의 현금 인출" 은 예금자가 수행하길 원하는 행동을 기술한다. 이 기능을 구현하기 전엔 예금자는 ATM 에서 현금을 출금할 수 없을 것이다. 반대로 그런 기능을 구현하여 적용해주면 출금이 가능해진다. 이는 우리에게 "완료"된 상태가 어떠해야 하는가를 판단하는데 명확한 출발점이 된다. 

만약 제목이 "계좌 관리자" 또는 "ATM 의 동작" 따위었다면 어느 시점이 "완료" 상태인가 불분명해지고 그것과 관련된 것들은 더욱 더 모호해질 꺼다. 일예로, "계좌 관리자"는 대출 계약에 가입시킬지도 모르고, "ATM의 동작"은 현금 카드의 PIN 번호를 바꿔버릴지도 모른다. 그래서 스토리의 제목은 항상 그 시스템을 사용하는 사용자 입장에서 바라본 실질적인 행위로 기술되어야 한다.



전말(narrative)은 역할, 기능, 이득을 포함해야 한다.


"[역할]로 하여금 [이득]을 얻기 위하여 [기능]이 필요하다" 는 템플릿에는 몇가지 장점이 있다. 전말 안에 역할을 구체화 하므로써 기능에 대해 누구와 이야기 해야할지 알게 된다. 이득을 구체화할 때는 스토리 작성자로 하여금 왜 그들이 그 기능을 필요로 하는지에 대해 생각하도록 한다.

그 기능이 실질적인 이득을 가져오지 못한다는 걸 알게 될 경우도 있다. 그런 때 대게는 어떤 스토리를 빠뜨렸음을 의미한다. 현재의 기능에 대한 하나의 스토리가 있을 때, 그 스토리가 전혀 다른 이득을 만들어 내는 상태라면 (그래서 여전히 쓸모 있긴 하지만) 본래의 이득을 위한 전혀 다른 기능에 대한 감춰진 스토리가 필요하다.

위의 예시는 적용될 기능 대해 직접적으로 관련된 예금자가 있기 때문에 그 기능이 어떤 동작을 하는지 조사하는 시작점을 보여준다.


시나리오 제목은 차이점을 나타내야 한다.

스토리를 작성하면서 시나리오들을 나란히 나열할 수 있어야 하고, 그 제목만으로만 각각이 어떻게 다른지가 나타나야 한다. 위 예시에 보면 시나리오의 설명에는 단지 각각의 시나리오가 어떻게 다른지만을 기술하고 있다. "예금자가 잔고가 불충분한 계좌에서 출금하려 할 때 트랜잭션을 처리할 수 없음을 알려야한다" 라는 식으로 장황하게 쓸 필요가 없다. 다른 것들과 비교했을 때 그것의 제목을 통해서 그것에 관심을 기울여야 할지가 명확해진다.


시나리오는 조건, 만일, 그러면[각주:4]의 용어로 기술되어야 한다.

여러 팀들의 경우를 봤을 때 BDD 를 적용하기에 이런 방식은(조건/만일/그러면) 가장 적합하고 유용한 수단이다. 단지 비지니스 사용자들, 분석가들, 테스터들 그리고 개발자들에게 "조건/만일/그러면" 의 어휘들을 적용하므로써 그들은 세상의 모호함에서 벗어나게 되었다.

모든 시나리오들이 단순하지만은 않다. 어떤 것들은 "조건 [기본 조건 내용] 만일 [발생한 사건 상황] 그러면 [기대 결과] 만일 [또다른 사건 상황] 그러면 [새로운 기대 결과] 등등"의 순으로 연속적인 상황들로 표현되는 게 최선이다. 예를 들면 마법사 방식의 웹사이트가 그런데, 복잡한 데이터 모델을 구성하기 위해서 연속적인 화면을 단계적으로 밟게 된다. "조건/만일/그러면" 의 단어들에 익숙해졌다면 사건과 결과를 연속적으로 혼합하는 것이 완벽하게 들어맞는다.

한가지 발생할 수 있는 행태로 의사소통이 필요한만큼 되지 못한 경우가 있다. 그럴 때는 추정된 조건("잔고 이상 출금된상태도 고려해야죠" 라는 말)을 빠뜨렸거나 기대 결과를 ("그런 경우 당연히 예금자는 카드를 돌려받게 됩니다." 라는 식으로) 검증하는 걸 잊었다는 걸 곧바로 알게 될 것이다. 나는 이런 경우를, 개발팀 리더가 분석가들과 개발자들이 서로 동문서답하고 있다고 느꼈지만 그들에게 그렇다는 걸 증명해낼 방법이 없었다고 했던 팀에서 목격한 적이 있다. "조건/만일/그러면" 어휘를 소개한지 며칠만에 그들간의 상호작용이 월등히 좋아졌음을 그들은 확인할 수 있었다.


조건들은 요구된 내용만큼만 빠짐없이 정의되어야 한다.

중도에 추가된 조건들은 혼란을 초래하는데, 그 스토리를 처음 보는 사람에게, 그가 기술진이건 비지니스적 입장에 서있건, 무얼 알고 있어야 하는지를 이해하기 어렵게 한다. 역으로 조건들 중 빠진 게 있다면 그것들은 사실이 아닌 억측이 된다. 주어진 조건으로부터 엉뚱한 결과를 만들게 됐다면 그건 무언가 빠졌기 때문일 거다.

위 예시에서 첫번째 시나리오는 계좌의 잔고, 카드 그리고 ATM 자체에 대한 이야기었다. 그 시나리오를 완성하기 위해 이 모든 것들이 필요하다. 세번째 시나리오의 경우 계좌의 잔고나 ATM 에 현금 보유 여부는 다뤄지지 않았다. 기계가 계좌 잔고나 ATM 의 상태와 상관 없이 카드를 먹은 채로 있을 꺼란 걸 암시한다.


사건(event)[각주:5]은 그것의 특징을 묘사해야 한다.

사건 자체는 매우 간결해야 하며 전형적으로 기술된 시나리오에 오직 한 번 사용 된다. 위에서 언급한 것처럼 어떤 시나리오들은 이보다 더 복잡하기도 하지만, 스토리를 이루는 대부분의 시나리오들은 하나의 사건에 초점을 맞추게 된다. 사건들은 정황(조건들)에 따라 다를 것이고 그에 상응하는 결과가 따르게 된다.


스토리는 한 번의 반복작업(iteration)이 될만큼 작아야 한다.

설명할 수 있는 단위로 쪼개지 않는 한 그렇게 할 수 있는 손쉽고 빠른 방법은 없다. 일반적으로 대여섯개 이상의 시나리오들이 있다면 그 스토리는 서로 비슷한 시나리오들끼리 묶어서 쪼개질 수도 있을 거다.

이글의 ATM 관련 예시로써는 이 스토리를 위해 얼마나 더 많은 시나리오들이 있는지 가늠할 수 없지만 몇 개는 더 나올 수 있을 걸로 짐작한다. 본래 이 스토리에는 세가지 "부품들(moving parts)"이 있는데, 이름하여 계좌 잔고, 현금 카드의 상태 그리고 ATM 의 상태 등이다. 여기서 현금카드에 대해서 좀 더 세분화 될 수 있다. 즉, 카드의 유효기간이 지난 경우 그 카드로 현금을 인출할 수는 없지만 ATM 은 그 카드를 사용자에게 돌려줄 것인가? 만약 ATM 이 트랜잭션을 처리하는 도중에 고장이 난다면? 카드에 당좌 대월 기능이 있는 경우라면?

그래서 아래와 같이 몇몇의 더 작은 단위의 스토리로 쪼개는 것이 좋을 것이다.

  • 예금주가 현금을 인출한다 (가정: ATM 이 정상이고 카드가 유효하다)
  • 예금주가 정지된 카드로 현금을 인출한다 (가정: ATM 이 정상이다)
  • 예금주가 고장난 ATM 으로 현금을 인출한다 (가정: 카드는 유효하다)

이것이 인위적으로 보일 수 있지만, 큰 단위의 스토리의 진행을 비지니스적 용어로 설명해주고 데이터를 근거로한 추적을 가능하게 한다. 여기서 중요한 것은 항상 시나리오들을 통해서 (가정들을 명백히 하면서) 기술적인 흐름보다 (예를 들어, 반복작업에서 데이터베이스 처리나 다음 반복작업에서의 GUI 등의 처리) 비지니스적 흐름을 따라 그 스토리를 풀어내야 한다는 것이다. 이로써 비지니스는 기술적인 용어들을 사용하기보다 그 자체의 용어들을 통해서 그 과정이 설명될 수 있다.


그래서 이것이 Use Cases 와 다른 점은 뭘까?

유즈 케이스란 것에 대해 생각해보자. 나는 앨리스태어 코번(Alistair Cockburn)이 설명하는 (내가 RUP-as-waterfall 프로젝트[각주:6]들에서 맞닥드렸던 Over-engineering 이 발생하는 형태와 대립적인) Use Cases 에 광적인 팬이다. 나는 Use Case 를 바탕으로 한 프로젝트를 수행한 경험이 별로 없기 때문에 그와 비교하는 설명을 할 자신은 없다.

물론 나는 (결과나 목표의) 낮은 정밀도에서 시작해서 높은 정밀도를 향해 일을 진행하면서 그 과정에 더 많은 시나리오들을 받아들이는 그의 프로세스에 동의한다. BDD 관점에서 이것은 비지니스적 결과에서 시작하는 것을 의미하며 고차원적 기능 영역들에 대해 인수조건에 따른 구체적인 스토리들을 파고드는 것이다.

실제로는 어떤 종류의 프로세스가 요구사항을 식별하고 구체화 하는지는 문제되지 않는다. 만일 그것이 생각들을 체계화하는 데 도움이 된다면 요구사항 문서들을 작성하는 것도 괜찮다. 그러나 그 문서들이 모든 생각들을 실질적으로 요약하고 있을 것으로 착각하면서 여기저기 전달해서는 안된다. 그대신 요구사항 문서 또는 Use Case 모음을 한쪽에 놓고서, 머리 속의 모든 해답들을 쏟아낸 지식 만큼, 혹은 최소한 지금 시점에서 업무의 개요를 파악하기 충분할만큼, 비지니스적 결과로부터 스토리들을 정의하는 일에 착수해야 한다.


요약
 
행위 주도 개발은 최소 기능 단위로써, 나아가 출하의 단위로써 스토리를 사용한다. 인수조건들은 스토리의 본질적인 부분이다. 그것들은 스토리의 행위 범위를 정의하는 데 영향을 미치고 또한 스토리가 "완료" 된 상황에 대해 공통적으로 동의할 수 있도록 한다. 뿐만 아니라 계획 단계에서 견적을 추정하는 데 기본 단위로 사용되기도 한다.

가장 중요한 것은 그런 스토리들은 프로젝트의 이해관계자들, 비지니스 분석가들, 테스터들 그리고 개발자들간의 의사소통의 결과물이란 점이다. BDD 는 그것이 개발 프로세스의 산출물인만큼 프로젝트와 관계된 다양한 역할의 사람들 사이에 상호작용에 대한 것이기도 하다.



  1. 역자 주: gumption traps; 어떤 사건 또는 사고방식으로써 프로젝트를 시작하거나 계속 하도록 하는 열의를 잃게 하고 포기하도록 하는 어떤 사건 또는 사고방식을 말한다. [본문으로]
  2. 역자 주: 본래는 promise of conversation, description of a feature 지만 여러가지 개발 방법론에서 말하는 계획,분석 단계 산출물이면 무엇이든 문장의 의미를 해치지 않고 사용될 수 있을 것 같아서 다른 말로 대체함. [본문으로]
  3. 필자 주: 해당 기능에 대해서 실질적인 관심을 보이는 사람이어야 한다. 즉, 스토리에 따라서 운영자, 법무 담당 또는 보안 담당자 등이 된다. [본문으로]
  4. 역자주: 이글은 BDD 방식의 스토리 정의에 대한 것으로 특정 플랫폼의 어떤 도구에 대한 설명은 아니다. 그러나 가장 보편적으로 사용되고 있는 cucumber 에서 "given-when-then" 의 템플릿이 "조건-만일-그러면" 으로 한글화되어 사용되고 있기 때문에 이를 번역에 사용했다. "given-when-then" 은 영어 자연어에서 잘 이해되지만 이를 "조건-만일-그러면" 으로 번역한 후에는 자연어스럽지 않게 되는 문제는 있다. [본문으로]
  5. 역자주: "조건-만일-그러면" 템플릿에 "만일" 에 기술되는 사건을 의미한다. [본문으로]
  6. 역자주: 산출물 중심의 개발 방법론이 적용된 프로젝트로 이해하면 된다. [본문으로]
Posted by Lyle
We can work IT out2009. 9. 13. 18:03
이 글은 댄 놀쓰(Dan North)가 2006년 3월에 Better Software 에 기고한 Introducing BDD 의 번역입니다. (번역 : 이홍주)

문제가 있었다면, 서로 다른 환경의 프로젝트에 대해 애자일 방법론인 테스트 주도 개발(test-driven development; 이하 TDD)을 수행하도록 하거나 가르칠 때마다 동일한 혼란 또는 오해들과 마주치곤 했다는 거다. 개발자들은 어디서부터 시작할지, 어떤 것을 테스트하고 어떤 것은 하지 않을지, 한번에 얼마만큼을 테스트할지, 테스트에 어떤 이름을 붙일지 그리고 테스트가 왜 실패했는지 등을 고민했다.

TDD 라는 계곡에 깊히 들어갈 수록 내 스스로의 여정이 점점 더 어두운 계곡의 연속이었던 것에 비해, 반복 수련을[각주:1]를 사용할 때 유용하다.


의미심장한 테스트 이름은 테스트가 실패했을 때 도움이 된다.

얼마 후, 코드를 변경한 것이 테스트 실패로 이어졌을 때 테스트 메서드 이름을 보는 걸로 그 코드에 의도된(부여된) 행위를 확인할 수 있다는 걸 알았다. 대체로 아래 셋 중 하나의 경우였다.

  • 내가 버그를 만들었다. 내가 나빠. 해결책: 버그 고치기.
  • 의도된 그 행위는 여전히 적절하지만 다른 곳으로 옮겨졌다. 해결책: 테스트를 옮기고 필요하면 내용도 고친다.
  • 시스템의 조건이 바뀌거나 해서 그 행위는 더이상 맞지 않다. 해결책: 테스트를 삭제한다.

애자일 프로젝트에선 그것에 대한 이해가 진전된 만큼 후자쪽의 경우가 더 많이 발생한다. 불행히도, TDD 초보자들은 테스트 코드들을 삭제하는 일이 마치 그들의 코드 품질을 조금이라도 낮추는 일이 될 것처럼 선천적인 두려움을 갖는다.

형태 변화인 will 또는 shall 과 비교하면 should 란 단어에 대한 애매함이 명확해진다. should 는 해당 테스트의 조건에 대해 "진짜 그래야 하나?" ("Should it? Really?") 라는 암시적인 질문을 하도록 한다. 이는 테스트 실패가 우리가 만든 버그에서 비롯된 것인지 아니면 단지 시스템의 행위에 대한 과거의 가정들이 현재에는 맞지 않기 때문인지를 쉽게 판별하도록 해준다.


"Behaviour" 는 "test" 보다 더 유용한 단어다.

이제 "test" 단어를 지울 수 있는 도구(agiledox)와 테스트 메서드마다 적용할 탬플릿이 생겼다. 문득 TDD 에 대한 사람들의 오해들이 거의 대부분 "test" 란 단어에서 시작됐다는 게 떠올랐다.

테스팅을 TDD 의 본질이 아니라고는 할 수 없지만, 작성한 테스트 메서드 집합은 우리가 코딩 작업을 확실히 하도록 보장하는 효과적인 방법이다. 그럼에도 메서드들이 시스템의 행위를 완전하게 기술하고있지 못하면 결국 그것들은 우리들을 방심하게 만든다.


나는 TDD 를 하면서 "test" 대신 "behaviour" 라는 말을 쓰기 시작했고 그것은 단지 적합할뿐만아니라 훈련과정에서 발생하는 모든 영역의 의문들을 풀어주기도 했다. 이제 나는 그런 TDD 관련 질문들에 대한 답을 얻었다. 당신의 테스트를 뭐라고 부를지는 쉽다. 당신이 관계된 다음 행위를 기술하는 문장이면 된다. 얼마나 테스트를 해야 하는지는 고민할 필요도 없다. 그냥 그만큼의 행위들을 하나의 문장으로 기술하면 된다. 테스트가 실패했을 때 어떻게 할지는 단순히 위에서 언급한 단계를 따라하면 된다. 버그를 만났거나 행위가 바뀌거나 혹은 테스트가 더이상 적절하지 않은 경우 등에도 말이다.

테스트에 관한 사유를 행위에 대한 사유로 바꾸는 것에 심각해지면서 나는 테스트 주도 개발(TDD)를 행위 주도 개발(BDD)로 부르기 시작했다.


JBehave 는 테스트에 있어 행위를 강조한다.

2003년 말에 내가 설파해온 것에 대해 돈 또는 시간을 투자하기로 했다. JUnit 을 대체할 JBehave 를 만들기 시작했는데, 테스트에 관한 것들을 행위를 검증하는 내용을 중심으로 한 용어들로 대체했다. 나의 새로운 행위 주도적 주문(呪文)들을 엄격하게 고수했을 때 그런 프레임워크가 어떻게 진화하는지 확인하기 위함이었다. 또한 그것이 태스트 주도적인 용어들로 인한 소동들을 배제한 채 TDD 와 BDD 를 소개하는 데 유용한 교육용 도구가 될 걸로 기대했다.

위에서 예시한 가상의 CustomerLookup 클래스의 행위를 정의한다면 CustomerLookupBehaviour 라는 클래스를 작성할 수 있겠고, 이는 "should" 로 시작되는 메서드들을 포함하게 된다. JUnit 이 테스트를 수행할 때 처럼, 특정 행위가 수행될 때 행위 클래스를 초기화하고 각각의 행위에 대한 메서드들이 차례로 호출되는 식이다. 그 수행되는 진행상황이 리포트될 거고 종국에 요약된 내용이 출력되게 된다.

나의 첫번째 이정표는 JBehave 에 자체 검증 기능을 만드는 것이었다. 그러기 위해 단지 스스로를 실행할 수 있도록 하는 행위를 추가했다. JUnit 의 모든 테스트들을 JBehave 의 행위들로 옮기고 JUnit 과 동일하게 즉각적인 응답을 얻을 수 있었다.


그 다음으로 가장 중요한 행위를 판단한다.

그런 후에 비지니스적 가치에 대한 개념을 확정했다. 물론 내가 소프트웨어를 만드는 목적에 대해 언제나 알고 있었지만 지금 당장 코딩하고 있는 것에 대한 가치에 대해 심각하게 생각해본 적은 없었다. 또한명의 동료인 비지니스 아날리스트 크리스 매츠(Chris Matts)는 행위 주도 개발의 맥락에서의 비지니스적 가치에 대해 생각해보도록 유도했다.

JBehave 가 그것에 대해서도 재귀적(self-hosting)으로 사용될 수 있도록 만들려고 작정한 상태에서, 그것에 계속 집중할 수 있도록 하는데 "이 시스템이 하지 않는 가장 중요한 것은 무엇인가?" 라고 질문하는 것이 유용하단 걸 알게 됐다.

이 질문은 우리가 아직 구현하지 않은 기능의 가치를 확인하고 우선순위를 메기도록 한다. 또한 행위에 관한 메서드 이름을 체계적으로 나타내는 데도 유용하다. 해당 시스템이 어떤 의미 있는 행위인 X 를 하지 않는 상태에서, 그 X 가 중요하여 행위하도록 해야 한다면, 그 다음의 행위 메서드는 단순히 아래와 같이 된다.
public void shouldDoX() {
    // ...
}

이로써 TDD 에 대한 또다른 질문, 어디서 시작할 것인가에 대한 답을 얻었다.


요구사항 또한 행위이다.

이제 나는 TDD가 어떻게 수행되는지 내가 맞닥드린 함정들을 피할 수 있는 접근법을 이해함은 물론 그것을 설명할 수 있는 프레임워크를 갖게 됐다.


2004년이 끝나갈 무렵 나의 이런 새로운 발견인 행위 주도적 용어들을 맷츠에게 설명하는 중에 그는 "그건 그냥 분석 같은데?" 라는 말을 했다. 긴 침묵이 흐른 뒤에 우리는 모든 행위 주도적 생각을 요구사항 정의에 적용해보기로 했다. 만약 우리가 분석가들, 테스터들, 개발자들과 비지니스를 위한 일관적인 용어를 만들 수 있다면, 기술진이 비지니스 관련자들과 이야기할 때 발생하는 모호함과 소통 오류를 상당수 없앨 수 있는 방법이 될 것 같았다.



BDD는 분석가들에게 "공통언어(ubiquitous language)" 가 된다.

근래에 에릭 에반스(Eric Evans)는 그의 베스트셀러가 된 Domain-Driven Design 이란 책을 출간했다. 그 책에서 그는 모델링의 개념을 비지니스 도메인을 기반으로한 공통언어를 사용하는 시스템으로 기술하므로써 프로그램 코드의 근저에 비지니스 용어가 배어들도록 했다.

크리스와 나는 우리가 분석 과정 그 자체에 대한 공통언어를 정의하려고 시도하고 있다는 걸 실감했다. 우리는 훌륭한 출발점을 얻었다. 조직 안에서 공통적으로 사용되는 아래와 같은 스토리 템플릿이 이미 있었다.


[X] 로 하여금 (As a)
[Z] 를 얻게 하기 위하여 (so that)
[Y] 가 필요하다 (I want)


여기서 Y 는 어떤 기능이고, Z 는 그 기능에서 얻어지는 이득이거나 가치고, X 는 득을 보게 되는 사람이거나 역할이다. 이것들의 장점은 그것을 처음 정의할 때에 스토리를 배포하는 것의 가치를 명확히 하도록 강제한다는 데 있다. 어떤 스토리에 실질적인 비지니스적 가치가 없을 때 그것은 "... [단지 내가 하기] 때문에 나는 [그 기능]이 필요해"[각주:2] 템플릿을 적용하면 위 예시에서 두 가지 시나리오는 아래와 같이 표현될 수 있다.

시나리오 1: 계좌에 잔고가 있음
조건 계좌에 잔고가 있다
그리고 카드가 유효하다
그리고 출금기에 현금이 들어있다
만일 고객이 현금을 요청한다
그러면 계좌에 출금액을 기입한다
그리고 현금을 출금한다
그리고 카드가 반환한다

"그리고"는 다수의 기본 조건이나 다수의 결과를 상호 연결하기 위해 씌었다.

시나리오 2: 계좌가 당좌 대월 한도를 넘어 당좌 차월 상태

조건 계좌가 당좌 차월 상태다
그리고 카드가 유효하다
만일 고객이 현금 요청한다
그러면 출금거절 메시지가 출력된다
그리고 현금이 출금되지 않는다
그리고 카드가 반환된다

두 시나리도 모두 똑같은 이벤트 발생을 바탕으로 하고 일부 기본 조건들과 결과들 조차도 보편적인 것들이다. 기본 조건들과, 이벤트 발생, 그리고 그결과들이란 템플릿을 재사용하여 활용하길 바란다.


인수조건은 실행 가능해야 한다

이 시나리오의 요소들(조건, 사건, 결과)은 잘 다듬어져있어서 코드에 직접 재연될 수 있다. JBehave 는 시나리오의 요소들을 자바 클래스들로 곧바로 사상할 수 있는 객체 모델을 정의한다.

각각의 조건들을 재연하는 클래스를 작성해보자.

public class AccountIsInCredit implements Given {
    public void setup(World world) {
        ...
    }
}
public class CardIsValid implements Given {
    public void setup(World world) {
        ...
    }
}

그리고 사건 발생을 위한 클래스를 작성한다.
public class CustomerRequestCash implements Event {
    public void occurIn(World world) {
        ...
    }
}

그리고 결과들에 대해서도 마찬가지로 적용한다.  JBehave 는 이 모든 것들을 연결하시켜 실행한다. 이는 말하자면 하나의 "세상"을 창조하는 식으로, 객체들을 저장하는 어딘가가 되기도 하면서 그것들을 각각의 조건에 순차적으로 전달하므로써 투명한 상태로 세상을 번창시킬 수 있도록 한다. 그런다음 JBehave는 "사건 발생"을 세상에 알리고, 그로써 그 시나리오의 실질적인 행위를 수행한다. 결국 그것은 우리가 정의한 스토리의 결과들로 제어된다.

각각의 요소들을 대변하는 클래스가 있다면 다른 시나리오들 또는 스토리들에 재사용할 수도 있다. 첫번째로 그런 요소들은 계좌에 잔고가 있거나 혹은 카드가 유효한 것으로 설정되어 가상으로 구현된다. 이것들은 행위를 구현하는 출발점을 형성한다. 응용프로그램을 구현하면서 특정 조건들과 결과들은 구현된 실상의 클래스들이 사용되도록 대체되는데, 그리하여 시간이 지남에 따라 해당 시나리오가 완성되고, 구석구석의 기능들을 적절하게 테스트하기에 적합해지는 것이다.


BDD의 현재와 미래

잠깐동안의 중단이 있었지만, JBehave 다시 활발한 개발이 이뤄졌다.  그것의 핵심부는 꽤 견고하게 완성된 상태다. 다음 단계는 IntelliJ IDEA 와 Eclipse 와 같은 보편적인 Java IDE 와 통합하는 작업이다.

데이브 아스텔스(Dave Astels)는 BDD 에 활발하게 진척시켜왔다. 그의 블로그와 다양한 기고들은 질풍같은 활동을 자극해왔는데, 특히 rspec 프로젝트가 루비 언어로 BDD 프레임워크를 만들도록 하는 데서 그랬다. 나는 JBehave 를 루비로 구현하는 rbehave 작업에 착수했다.

내 몇몇 동료들은 그들이 수행하고 있는 다양한 프로젝트들에 BDD 를 적용하고 있고 그것들은 매우 성공적이었다. JBehave 의 (인수조건을 검증하는) 스토리 실행기능은 개발이 활발히 진행중이다.

목표는 분석가들과 테스터들이 일반적인 문서편집기로 스토리들을 포착할 수 있도록 하기 위해, 비지니스 영역의 모든 언어들로 행위 클래스들에 대한 토막들을 생성할 수 있는 통합 에디터를 만드는 것이다. BDD 는 많은 사람들의 도움으로 발전했고 나는 그들 모두에게 크게 감사한다.




  1. [/footnote] 통한 점진적 숙달은 별로 없었다는 느낌이 들었다. 돌아보건데 내가 "와, 문이 열렸구다." 라고 앞으로를 생각했던 것에 비해 "누군가 그걸 말해줬더라면 지금 같진 않았을텐데!" 하며 뒤만 돌아보는 경우가 더 많았다. 그래서 나는 TDD 가 좋은 일들로 연결되면서 함정들을 피해가는 방법으로써 보여질 수 있도록 해야겠다고 생각했다.

내가 찾은 해답은 행위 주도 개발(behaviour-driven development; 이하 BDD)이었다. BDD는 기정 애자일 방법론들로부터 진화한 것이면서 그런 방법론들이 애자일적인 소프트웨어의 출하를 처음 접하는 팀들에게 접근하기 쉽고 효율적이도록 디자인 되었다. 시간이 지남에 따라 BDD 는 테스팅에 대한 애자일적 분석과 자동화된 합격판정 등의 더 넓은 영역을 포함하도록 확장되어왔다.


테스트 메서드 이름은 문장이 되어야 한다.

나의 첫번째 깨달음의 순간은 내 동료인 크리스 스티븐슨(Chris Stevenson)이 개발한 agiledox 라는 믿을 수 없이 단순한 도구를 보았을 때였다. 그것은 JUnit 의 테스트 클래스를 사용하여 메서드 이름들을 평범한 문장들로 출력하는데, 예를 들어 하나의 테스트 케이스는 아래와 같은 모양을 하고 있다. 
public class CustomerLookupTest extends TestCase {
    testFindsCustomerById() { ... }
    testFailsForDuplicateCustomers() { ... }
    ...
}
그리고 이를 아래와 같이 풀어서 표현한다.

CustomerLookup
- finds customer by id
- fails for duplicate customers
- ...

클래스 이름과 메서드 이름들에 쓰인 "test" 란 단어는 벗겨 없어졌고, 캐멀 케이스(camel-case) 메서드 이름은 일반적인 구문으로 바뀌었다. 이것이 전부지만 그 영향은 놀라울 정도다.

개발자들은 이것이 적어도 그들의 문서화 작업에 유용하겠다는 생각으로 테스트 메서드의 이름들을 실상의 문장들처럼 작성하게 됐다. 더욱이 메서드 이름에 비지니스 용어를 사용한다면 생성된 문서가 비지니스 사용자들이나 분석가들 그리고 테스터들까지 이해할 수 있게 된다는 것도 알게 됐다.


간단한 문장 템플릿이 테스트 메서드에 집중하게 한다.

그래서 나는 테스트 메서드 이름이 "should" 로 시작되도록 하는 관습을 만들고 따르기 시작했다. "이 클래스가 어떤 행위를 해야 한다(should do something)" 라는 식의 문장 템플릿은 현재 다루는 클래스에 대해서만 정의될 수 있음을 의미한다. 이때문에 집중하게 되고, 만약 이런 템플릿에 어울리지 않는 테스트 코드를 작성하고 있다면 그것이 엉뚱한 행위란 걸 알게 한다.

실례로, 내가 화면에서 입력받은 내용에 대한 유효성 검사(validation)를 수행하는 클래스를 작성할 때, 모든 필드들은 이름과 성 따위의 일반적인 고객에 대한 상세정보였는데, 거기엔 생일 필드와 나이 필드도 함께 있었다. 나는 ClientDetailsValidatorTest 라는 클래스를 작성하면서 testShouldFailForMissingSurname 과 testShouldFailForMissingTitle 등의 메서드들을 명명했다.

그런 후에 나이를 계산하는 코드를 짜면서 판단하기 묘한 문제에 직면했는데 이런 것들이다. 나이와 생일이 빠짐 없이 있다 해도 서로 맞지 않으면? 생일 값이 오늘 날짜를 나타내고 있다면? 연도 없이 월일만 있어서 나이를 계산하지 못하는 경우라면? 이런 문제들이 생각날 때마다 그런 행위들에 대해 성가신 테스트 메서드 이름들 추가해야 했기 때문에 손 떼고 다르게 해보기로 마음먹었다. 결국 AgeCalculatorTest 라는 테스트 클래스를 동반한 새로운 클래스 AgeCalculator 를 작성하게 됐다. 나이를 계산하기 위한 모든 행위들은 나이 계산기 역할을 하는 새로운 클래스로 옮겨갔고, 결국 유효성 검사에서 클래스의 나이 계산이란 행위에 대해서는 새로운 클래스와 함께 올바로 작용하는지만을 테스하면 되게 되었다.

만약 어떤 클래스가 하나 이상의 행위를 하고 있다면, 보통 내가 다른 클래스들을 만들어서 그것들을 처리하도록 해야 한다는 징조라고 본다. 그럴 땐 무엇을 한다 를 기술하는 인터페이스로써 새로운 서비스를 정의한 다음 그 서비스를 클래스의 생성자에 전달한다.
public class ClientDetailsValidator {
    private final AgeCalculator ageCalc;
    public ClientDetailsValidator(AgeCalculator ageCalc) {
        this.ageCalc = ageCalc;
    }
}
객체들을 서로 연결하는, dependency injection 으로 알려진 이러한 방법은 특히 시뮬레이션을 위한 모의 객체[footnote] [본문으로]
  • [/footnote] 라는 식으로 귀착되곤 한다. 이런 접근은 일부 더 난해한 요구사항들에 대한 유효범위를 판단하기 쉽게 해주기도 한다.

    그런 출발점에서부터 매츠와 나는 모든 애자일 테스터들이 이미 알고 있는 것을 발견하기 시작했다. 어떤 스토리의 행위는 단순히 그것의 인수조건(acceptance criteria)이며, 그 시스템이 모든 인수조건을 만족할 때 그것은 정확히 동작하고 있으며 그렇지 않을 경우는 그 반대라는 것이다. 그래서 우리는 스토리의 인수조건을 포착한 하나의 템플릿을 만들어냈다.

    이 템플릿은 어느정도 자유로워서 분석가들에게 인위적이거나 강제적이지 않으면서도 충분히 구조화 되어서 그 스토리의 구성 요소들에 접근하여 자동화할 수 있어야 했다. 우리는 시나리오라는 명목으로 그런 인수조건의 작성에 착수했는데 아래와 같은 형식을 채택했다.


    조건이 주어짐, (Given)
    만일 사건이 발생했을 때, (When)
    그러면 어떤 일이 수행된다. (Then)


    이를 설명하기 위해서 고전적인 ATM 기계의 예시를 들어보자. 스토리 카드들 중 하나는 아래와 같을 것이다.

    제목: 고객이 현금을 출금한다
    고객으로 하여금,
    은행에서 줄을 서지 않아도 되기 위해,
    ATM을 통한 현금을 출금이 필요하다.

    이제 우리는 이 스토리가 어느 시점에 배포되어야 할지를 어떻게 알 수 있을까? 몇가지 시나리오들을 고려해볼 수 있다. 계좌에 잔고가 있을 수도 있고, 잔고 이상의 당좌 차월 상태지만 당좌 대월 한도를 넘지 않은 경우, 그리고 당좌 대월 한도보다 더 당좌 차월이 발생한 경우 등이다. 물론 그밖의 경우, 말하자면 계좌에 잔고는 있으나 출금 금액으로인해 당좌 차월이 된다거나, 출금기에 충분한 현금이 들어있지 않은 등의 상황도 있을 수 있다.

    위의 조건-만일-그러면(given-when-then)[footnote] [본문으로]
  • Posted by Lyle
    We can work IT out2009. 9. 7. 11:56
    Recently I needed to program some module in ruby that parses excel file. So I googled to find some solutions. As we all know, there are some open-source projects that provides us libraries for parsing and creating excel file and those were the results of my googling. PARSEEXCEL, SPREADSHEET, ROO are such examples.


    1. Pure Ruby Libraries : ParseExcel, Spreadsheet, ROO

    As a brief introduction, PARSEEXCEL is an translated module from Perl thing (I don't know the original name of perl module but easly can guess that has a the same name 'parseexcel') and only reads informations from excel file but cannot create or modify the file. No more updates happening after the translation had done few years ago, and has very poor documentation (but it does not needed as long as we have the perl document). Most of all, it has god damn slow performance on reading large size of excel. Don't waste your time on trying this.

    Comparing to parseexcel, SPREADSHEET is very fast on reading large size and supports to create new excel file. And It's still on going project, which is pretty fast updating and can get API documentations on http://spreadsheet.rubyforge.org . In case of the ROO, it takes the most advantages from spreadsheet because it's based on that and offers common interface not only for XLS format but also for other various formats, for examples, OpenOffice, XLSX (new format of Excel 2007), and Google Spreadsheet.

    But all those three tools has fatal weak point which those are not supported with Excel files including folumlars. Do you think that the excel file is useful when it doesn't have any formular? Absolutly not! Without formulars, what's difference between excel and tables on other MS Office document (ex. PowerPoint, Word) or HTML page! Oh yeah, I know the roo can handle some formulars, but has exception on XLS format which is commonly used as we call Excel.

    My work was handling large sized XLS file which has plenty of formulas. So the three tools I had tried was useless. So far, this was the sad story about my attempts to solve excel problem with 'pure' ruby.


    2. Unpure Solutions : WIN32OLE, ugly POI Ruby Binding

    I'm not the kind of person who doesn't know the word "give up". But it's the work and I need to earn my salery from it. So I went to the second stage finding un-pure solution. Seems limitless things can be done with WIN32OLE, but has windows platform dependency and I love platform indendence.

    And here's another sad story on the second stage that I failed again though I spent much time with it. It's about POI, very famous commonly used Java libaray which definitly supports formulars. And few years ago it had released Ruby binding. POI Ruby Binding looks quite messy, it compiles POI Java code to machine code (not to a bytecode sounds suit to Java) by using GCJ as a compiler, and then wrap it to Ruby interface by SIWG . POI project released only a shared library 'poi4r.so' compiled on GCC 3.4.3 but it does not working. Object file poi4r.so had compiled with very old GCC so it fails to link dynamically with my libgcj library on GCC 4.2.4. I'm not a such a idiot to downgrade my GCC version to 3.4.3 to get old version of libgcj library with no sure that it's successfully running on that version. So I tried to compile my own poi4r.so and spent a whole day to do so running over the several hurdles called compile errors. And I finally gave up.

    You people involved in POI project! Make smart configuration or build script, or release updated binary with current GCC version. Or just stop your cons by dropping POI Ruby binding on your project web site which is totally useless.

    3. Utilize POI or JExcelAPI with RJB

    Then I went to the third stage. It's a success story. I was wandering about why POI hustlers didn't bind there Java POI code to Ruby directly. They had made a D-tour using SWIG and GCJ. The answer was so clear that there was no way to bind Java code to Ruby at the time of releasing POI ruby binding library. But now we have RJB (Ruby Java Bridge) that makes possbile to bind Java class as a Ruby class. This is my happy solution. We can use POI or JExcelAPI (This is pretty much better than POI, I think) libraries without wrapping it and usage of RJB is quit simple. An example is here.

    #!/usr/bin/env ruby
    
    require 'rubygems'
    require 'rjb'
    
    Rjb::load("./jxl.jar") # I prefer JExcelAPI rather than POI, but you can substitute poi.jar for jxl.jar.
    
    # import java.io.File;
    file_class = Rjb::import("java.io.File")
    # import jxl.Workbook;
    workbook_class = Rjb::import("jxl.Workbook")
    
    workbook = workbook_class.getWorkbook(file_class.new("ExcelFile.xls"))
    sheet = workbook.getSheet("First Sheet")
    puts sheet.getCell(7,7).getContents() # the cell has data calculated by some formular
    
    Posted by Lyle