스노보드를 타다가...

2009/12/13 11:24
  올 시즌들어 처음으로 스키장에왔다. 오는 길에 정말로 많은 걱정을 하였는데 그 이유는 올초에 당한 큰 사고(손목 자뼈 노뼈 골절) 때문에 내가 과연 탈 수 있을까하는 걱정에서였다.
지금까지 총 7번 중급과 상급 슬로프를 내려오면서 한번도 넘어지지 않았다. 걱정에서 쏟은 노력이 아까우리만치....
 올해로 3년째 스키장을 다니고 있는데(부상으로 2년간은 띄엄띄엄) 어떻게 내가 이만큼 탈 수 있었을지 생각해보았다. 아마도 좋은 장비 때문은 아닐 것이다. 내가 가진 장비는 20만원이 채 안되는 가격에 데크, 바인딩, 부츠가 한세트이다.
 그 답은 바로 모든 넘어지는 법을 익혀서가 아닐까한다. 난 정말 지독히도 운동신경이 없어 다른 사람의 100배라고 해도 좋을만큼 넘어지고 또 넘어졌다. 그렇게 수련하면서 더 이상 넘어질 수단이 없을만큼 익힌것이다.
 이러한 경험은 비단 스노보드에서만은 아니다. 소프트웨어 개발에서도 우린 단단한 코드를 만들 수 있는 경지까지 이르려면 수많은 실패의 반복이 필수이다.
 TDD는 계속된 실패속에 답을 찾는 과정이다. 실패하는 코드를 먼저 작성하고 그것을 바르게 동작하도록 만드는 반복. 그 속에서 코드는 점점 견고해지는 것이고, 버그 없는 우수한 품질의 코드가 나오는 것이다. 다행히 TDD를 하는데는 스노보드를 연마하는 것처럼 고통을 수반하지는 않는다.
 아직 TDD를 시작하지 않았다면 이제라도 달리 생각해보자. 코드를 작성해나갈 수록 스노보드를 실수없이 타는 것만큼의 상쾌함을 느낄 수 있을 것이다.

- 곤지암리조트 슬로프 아래에서





iPhone 에서 작성된 글입니다.
TAG TDD

댓글을 달아 주세요

  1. 또깽이부인 2009/12/13 11:44  댓글주소  수정/삭제  댓글쓰기

    재미있어? 자기? ㅋㅋ 빨리와~~ 김치 찌개 끓여놨어~~~

  2. 지나가던학생 2009/12/13 21:39  댓글주소  수정/삭제  댓글쓰기

    프로그래머가 되기까지~ 부탁드려요 흑흑흑

  3. 프로채터 2009/12/23 19:26  댓글주소  수정/삭제  댓글쓰기

    더 살이 쪄서 부상방지살이 생긴것 아냐?

최근 Service 개발에서 반복적인 Account 생성이 필요한 테스트 케이스가 있어서 작업 중 Remote Server에서 주민등록번호 Validation 을 하는 바람에 급히 검색하여 다음과 같은 내용을 알아내었다.

예를 들어 640713-1018433 이 주민번호를 예로 들어보죠
우선 주민등록번호 마지막자리수만 제외하고,
각각의 자리수마다 다음과 같은 수를 곱하여 전체를 더한다.

6 4 0 7 1 3 1 0 1 8 4 3
x x x x x x x x x x x x
2 3 4 5 6 7 8 9 2 3 4 5
-----------------------
+ + + + + + + + + + + +

즉, (6*2)+(4*3)+(0*4)+(7*5)+(1*6)+(3*7)+(1*8)+(0*9)+(1*2)
+(8*3)+(4*4)+(3*5) = 151

그러면 151 이란 수가 나온다. 이 151을 매직키인 11로 나누어 나머지만 취한다.

151 / 11 = 몫: 13 <-- 버림

나머지: 8

마지막 단계로 매직키인 11에서 나머지 8을 빼면 3이란 수가 나오
는데, 이숫자가 주민등록번호 마지막 자리의 숫자와 일치하면 대한민국 국민이다.

11 - 8 = 3 --> 정상적인 주민등록번호임 
출처 : http://blog.naver.com/foenix/40040223161

이 내용을 다음과 같은 메서드로 만들어 보았다.


	public static String getSSN() {
		Random rand = new Random();
		Calendar cal = Calendar.getInstance();
		cal.setTimeInMillis(rand.nextLong());
		String s1 = new SimpleDateFormat("yyMMdd").format(cal.getTime());
		String s2 = null;
		
		while(s2 == null || s2.length() < 6) {
			s2 = Integer.toString(rand.nextInt(299999));
		}
		int sum = 0;
		for (int i = 0; i < s1.length(); i++) {
			sum += Integer.parseInt(String.valueOf(s1.charAt(i))) * (i + 2);
			int j = i < 2 ? i + 8 : i;
			sum += Integer.parseInt(String.valueOf(s2.charAt(i))) * j;
		}
		int bit = 11 - (sum % 11);

		return s1 + "-" + s2 + (bit == 10 ? 0 : bit);
	}
TAG java, TDD

댓글을 달아 주세요

  1. 벤또사마 2009/06/17 09:46  댓글주소  수정/삭제  댓글쓰기

    2000년생 이후도 동작하면 대박.

  2. 프로채터 2009/06/19 19:01  댓글주소  수정/삭제  댓글쓰기

    3번 4번 있고 외국인은 5번 6번 있는데... 쩝...

  3. 강성희 2009/08/31 17:41  댓글주소  수정/삭제  댓글쓰기

    비슷한 문제인 신용카드 인증 문제가 문득 기억나네요.
    2차 P-camp때 김창준님이 자신의 세션에서 신용카드 문제를 TDD로 해결하는 방법을 보여주셨는데, 당시 TDD 초보였던지라 너무 감동을 받았던-_- 기억이...

Tomcat 등의 Servlet Container를 사용하여, 웹애플리케이션을 개발할 때, DataSource를 Servlet Container의 Context 에 JNDI Resource로 bind 하여 사용하는 경우가 많다.

test case를 실행하기 위해 JNDI remote provider 를 제공하는 application server (glassfish, jboss 등)를 사용할 수도 있으나, 이러한 것은 test의 원자성을 저해하기 때문에 test 환경 자체에서 JNDI resource를 생성할 수 있는 방법을 생각해 보았다.

Sun에서 제공하는 File System Service Provider를 이용하는 것이다. 사용법은 매우 간단하다. 단순히 naming context의 initail context factory 를 com.sun.jndi.fscontext.RefFSContextFactory 로 지정하기만 하면 된다.

다음은 간단히 만들어 본 class 이다. 이 class는 context xml 파일을 읽어서 JNDI resource를 loading해 주는 역할을 한다. test를 수행하기 전에 JNDILoader.init()을 실행 해주면 다른 작업 없이 test case를 실행시킬 수 있을 것이다.

package com.fguy.test;

import java.io.File;
import java.io.IOException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.log4j.Logger;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class JNDILoader {
	private static final Logger LOG = Logger.getLogger(JNDILoader.class);

	private static final boolean initialized = false;
	private static final String CONTEXT_XML = "WebContent/META-INF/context.xml";

	private JNDILoader() {

	}

	public static final void init() throws ParserConfigurationException,
			SAXException, IOException, DOMException, NamingException {

		if (!initialized) {
			Context context = getNamingContext();

			List resourceNames = new ArrayList();
			Map> references = new HashMap>();

			NodeList contextResourceNodeList = getContextResourceNodeList();
			for (int i = 0; i < contextResourceNodeList.getLength(); i++) {
				Node item = contextResourceNodeList.item(i);
				// print comment;
				if (LOG.isDebugEnabled()
						&& item.getNodeType() == Node.COMMENT_NODE
						&& item != null) {
					LOG.debug(item.getNodeValue());
				}

				// register to JNDI
				if ("resource".equalsIgnoreCase(item.getNodeName())
						&& "javax.sql.DataSource".equals(item.getAttributes()
								.getNamedItem("type").getNodeValue())) {
					NamedNodeMap attributes = item.getAttributes();
					String name = attributes.getNamedItem("name")
							.getNodeValue();
					Map reference = references.get(name);

					if (reference == null) {
						reference = new HashMap();
					}

					for (int j = 0; j < attributes.getLength(); j++) {
						String n = attributes.item(j).getNodeName();
						String v = attributes.item(j).getNodeValue();
						if (!"name".equals(n)) {
							reference.put(n, v);
						}
						LOG.debug(attributes.item(j));
					}

					resourceNames.add(name);
					references.put(name, reference);
				}

				if ("resourceparams".equalsIgnoreCase(item.getNodeName())) {
					NodeList parameters = item.getChildNodes();
					String name = item.getAttributes().getNamedItem("name")
							.getNodeValue();
					Map reference = references.get(name);

					if (reference == null) {
						reference = new HashMap();
					}

					for (int j = 0; j < parameters.getLength(); j++) {
						Node paramItem = parameters.item(j);
						if ("parameter".equalsIgnoreCase(paramItem
								.getNodeName())) {
							NodeList children = paramItem.getChildNodes();
							String n = null;
							String v = null;
							for (int k = 0; k < children.getLength(); k++) {
								Node child = children.item(k);
								if ("name"
										.equalsIgnoreCase(child.getNodeName())) {
									n = child.getNodeValue();
								} else if ("value".equalsIgnoreCase(child
										.getNodeName())) {
									v = child.getNodeValue();
								}
								LOG.debug(child);
							}
							reference.put(n, v);
						}
					}
					references.put(name, reference);
				}
			}
			for (String name : resourceNames) {
				Reference reference = new Reference("javax.sql.DataSource",
						"org.apache.commons.dbcp.BasicDataSourceFactory", null);
				Map referenceMap = references.get(name);

				for (String key : referenceMap.keySet()) {
					reference
							.add(new StringRefAddr(key, referenceMap.get(key)));
				}

				context.rebind("java:comp/env/".concat(name), reference);
			}
		}
	}

	private static final NodeList getContextResourceNodeList()
			throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
		Document doc = docBuilder.parse(new File(CONTEXT_XML));
		return doc.getFirstChild().getChildNodes();
	}

	private static final Context getNamingContext() throws NamingException {
		System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
				"com.sun.jndi.fscontext.RefFSContextFactory");
		System.setProperty(Context.PROVIDER_URL, "file:///tmp");

		return new InitialContext();
	}
}

댓글을 달아 주세요

원문 : http://codebetter.com/blogs/jeremy.miller/archive/2006/05/25/145450.aspx

나는 스스로 다른 포스트에서 저장 프로시저에 관해서는 다루지 않겠노라고 했지만, sproc에 관한 에릭의 포스트가 내 뚜껑을 열리게 하고 말았어. 애자일 이전 시기인 4년 전에, VB6/ASP 코딩을 하던 나는 에릭의 pro-sproc 입장에 열렬히 동의했으며, PL/SQL을 가득 채워서 작성했지만, 요즘 sproc 질문에 관한 나의 대답은 확고하게 "필요없음" 또는 최소한 "결백을 입증하기 전까지는 유죄" 라는 입장이야. 게다가 나는 sql server 가게의 Oracle 사나이이고, T-SQL을 경멸해.

첫째, 몇가지 근거들. 나는 1998년 경, ASP 코드에 adhoc SQL을 사용했던 사실은 모두가 경멸할 것이라고 생각해. CRUD에 sproc을 사용해서 큰 문제는 없었지만, O/R 매핑에 의한 도메인 모델 접근법은 모든 중요한 개발시간의 항목들에서 좀 더 효율적이며, 도메인 모델 접근법은 좀 더 나은 코드를 도출한다고 생각해. 우리 애플리케이션의 새로운 부분은 간단히 NHibernate 매핑을 변경함으로서 데이터베이스 변경사항을 처리하지. sproc은 더 이상 필요하지 않아.

에릭의 포스트는 이러한 점을 먼저 지적해.

TDD 진영에서 저장 프로시저와 뷰를 배척하는 것을 목격했을 때 나는 무척이나 혼란스러웠다.

그 답은 무척간단하다고, 에릭. 저장 프로시저는 TDD를 느리게 만들고, 비생산적이야. 저장 프로시저의 업무 로직은 도메인 모델 클래스에 대응하는 로직보다 테스트 하기 위해 더 많은 일을 하도록 하지. 참조 무결성은 단순히 테스트에 필요한 데이터를 삽입하기 위해 다수의 다른 데이터를 준비하도록 종종 당신을 속박해(우리처럼 아무런 외래키가 없는 낡은 데이터베이스 환경에서 일하는 경우는 제외하고 ;)). 저장 프로시저는 태생적으로 절차적이고, 따라서, 고립된 테스트들을 만들기 어렵고, 코드 중복(중복된 "where"절은 중복된 코드이고 중복은 나쁜 것이지)을 만들기 일쑤야. 또 다른 고려사항으론, 데이터베이스를 사용하는 어떠한 자동화된 테스트는 애플리케이션 영역 내부에서 동작하는 테스트보다 느리다는 사실은, 대규모 애플리케이션에선 아주 훌륭한 거래라는 거야. 느린 테스트는 긴 피드백 사이클을 유도하지. 이것 하나만은 나를 믿어. 20분의 CI 빌드와 5분의 빌드시간 중 어느 것이 팀을 더 느리게 하는지.....

내기 하나 하지. 나는 내가 최근 6개월 내에 ADO.Net을 직접 조작하는 코드를 100줄 이상 작성한 적이 없다에 걸겠어. 물론 이 기간동안 기능상 새로운 것들이 다수 포함되었어. 난 그리고 그동안 아주 작지만 사소하지 않은 SQL을 작성했지. 우리가 개발한 작은 OO 쿼리 엔진과 NHibernate 둘 다 데이터 접근 혼란에 시간을 낭비할 필요가 없도록 해줬어. 제프 애투드는 저장 프로시저는 데이터베이스의 어셈블리어라고 이야기 했지. 난 앞으로 sproc을 성능상 이득이 필요할때 사용하겠지만 성급한 최적화이기 때문에 아마 그럴 일은 없을거야.

내 생각에 sproc에 관한 생각의 격차는 애플리케이션에서 데이터베이스의 역할을 어떻게 보느냐에 있는 것 같아. 데이터 베이스가 하나의 지속(persistence) 매커니즘으로서 또는 단지 .Net 코드로서 UI는 앞으로 보내고 데이터를 뒷단으로 가져오기 위한 방법인지 이야기 해볼까?  내가 빌드한 애플리케이션들은 업무(business) 규칙에 기반하지 단지 리포팅을 위한 것만은 아니야. 나에게 데이터베이스는 단순히 우리 도메인 객체들과 메시지들을 위한 지속 메커니즘일 뿐이야. 이렇게 하면, 테스트용이성과 거의 같은 말인, 유지용이성이 아주 끝내줘. 수용(acceptance)과 단위 테스트 수준에서 크고 포괄적인 자동화된 테스트들의 핵심은, 적어도 내 생각으론, 이것들을 수용하는 가장 좋은 방법은, 바로 코드를 변경하는 것이야.

다시 에릭의 포스트로 돌아가서, 이 부분이 정말 날 열받게 만들었어 (이건 에릭 당신에게 직접적인건 아냐)

당신네 DBA 팀은 프로그래밍 팀과 완벽하게 독립적으로 일할 수 있고, 요구사항이 무엇이든 수행하고, 애플리케이션은 신경쓰지 않아!

제길, 아니야 아니야 아니야!!!!!  DBA는 거의 확실히 프로그래밍팀과 독립적으로 일하지 않는다고. 저장 프로시저는 코드이고, 그렇기 때문에 잠재적으로 코드를 파괴할 수 있어. 만약에 저장 프로시저를 변경하면 제품화 되기 전에 *반드시* 저장 프로시저에 대응하는 애플리케이션을 통합하고 테스트 해야 해. 난 이것 땜에 열받은 적이 한두번이 아니야. 저장 프로시저 코드는 절대적으로 CI 빌드로 빌드되고 버전화되어야 해. 코드와 동기화 되지 않은 sproc으로 인한 버그 때문에 괴로워. DBA 또는 유지보수 개발자의 생각은 각각 다른 버전의 sproc을 데이터베이스에 심자라는 것인데 이건 진짜 나쁘고 위험한 방식이야.

추가 설정 관리라는 부담은 대부분의 애자일 팀들이 저장 프로시저로부터 떠나게 된 하나의 이유야. 코드에서 NHibernate 매핑이나 파라미터화된 SQL에 의존하면 SQL은 C# 코드와 함께 자동으로 빌드되고 버전화 될 거야. sproc과 C# 코드의 불일치로 일어나는 손실을 훌륭하게 최소화하지. 우리 C#은 CruiseControl.Net 빌드 번호로 표시되어 조립되고 그것으로 간단하게 불일치를 줄일 수 있지. 저장프로시저들은 음..... 자..... 어.... 꽤나 솔직히 말하자면 테스트 된 대응코드와 그게 같은 버전인지 알 수가 없어 :(

가장 최악인 건, sproc과 미들티어 코드사이의 기능 분리라는 이 사실이 끔찍하게도  마이크로소프트 개발 세계의 상식이라는 것. 부르르르르... 당신은 단순히 코드를 망가 뜨리고 시스템을 이해하는데 지적 부하를 가중시킬 뿐이야.

댓글을 달아 주세요

  1. duecorda 2008/12/09 18:34  댓글주소  수정/삭제  댓글쓰기

    우연히 들린 블로그인데, 앞뒤는 모르지만 2006년 아티클을 번역해서 올리신데는 나름의 사정이 있으시겠죠.

    개인적으로 아직까지도 비슷한 논쟁이 주위에 남아있으므로 "제레미씨"에게 한표 던지고 갑니다.

  2. 비상구 2009/02/25 18:41  댓글주소  수정/삭제  댓글쓰기

    풉..Enterprise 환경에서 3 tier 분산 아키텍처 모델을 적용해 본적이 있으면 이런 소린 하지 못할텐데요. 우연히 서핑 중에 들렸다가 웃고 갑니다. ^^;;

제2차 P-Camp 참석 후기

2007/10/11 12:10
P-Camp 라는 행사를 우연히 (아직도 어떻게 알게 되었는지는 기억나지 않는다.) 알게 되었는데, 행사의 주제가 평소에 지대한 관심이 있던 소프트웨어 테스팅이라 참가신청하게 되었다.

시작 시간이 오후 6시라 평소보다 일찍 퇴근을 하고, 삼성동으로 향했다. 행사장에 생각보다 많은 사람이 있어 놀랐는데, 알고보니 ASTA에 참석한 사람들이었고, 시계를 보니 내가 30분일찍 온 것이어서 간단한 식사후 행사장에 다시 입장하였다.

간단한 행사 소개후,  Agile 보급꾼 김창준님께서 "Ontogeny Unit Tests in Test Driven Development" 라는 주제로 강의를 들려주셨다.
강의 시작전 화면의 "Ontogeny"라는 낯선 단어를 보고 당황해 했는데, 알고보니 나 뿐 아니라 대부분의 사람들이 알 지 못했다는 사실에 안심하였다. 단어의 뜻은 "개체 발생" 이란다.

디자인 패턴의 창시자로 유명한 크리스토퍼 알렉산더의 다른 저서인 Nature of Order 라는 책에서 착안한 Living Structure 라는 개념을 프로그래밍에 접목한 내용이었는데, code(건축물) 가 살아있는 느낌을 갖도록 하는 15가지 property 들 (http://www.cmcrossroads.com/bradapp/docs/NoNoO.html 참조)이 신선한 자극이 되었다.

마티스 그림의 예에서 이것이 코드 뿐 아니라 일상 생활과 우리의 인생에서도 충분히 적용할 만한 철학적인 관념일 수도 있다라 느꼈고, Luhn Algorithm Code를 리팩토링하는 과정을 접했을때는 이것이 관념적이지 않고 쉽게 구체화될 수 있구나라고 생각했다.

행사 종료 후, 창준님께서 강의 소감을 물으셔서 오늘 듣게 된 TDD에서의 Ontogeny 개념이 TDD를 실행하려고 하는 지인들에게 쉽게 다가설 수 있도록 하는 계기가 될 것이라는 의견과 미디어아트가 대단히 인상적이었으며, 강의내용 이해에 큰 도움이 되었다고 말씀드렸다.

토론 트랙에서는 OST가 아닌 미리 정해진 여러 주제를 가지고  조를 나눠  진행하는 방식이었는데,  내가 선택한 주제는  "웹 환경에서의 테스팅" 이었다.
프로그래머가 대부분일 거라는 나의 예상과는 달리 QA, 보안관리자등 다양한 사람들이 토론에 참가하였다.  나는 당연히 오늘의 주제에 대한 이야기가 TDD를 중심으로 이루어 질 것이라 생각했는데, 프로그래머 이외의 role을 가진 사람들은 V모델에서의 테스트팀에 의한 테스팅, 즉 test to fail에 관심이 있던 사람이어서 TDD외의 이야기도 많이 나누게 되었다.

다른 사람들은 어떻게 TDD를 익히고 적용하고 있는지, 다양한 환경에서의 경험을 공유하고 싶었는데 생각보다 TDD를 사용하는 사람은 적었다. 아니, 현재 사용하고 있는 사람은 나 밖에 없었다. 그리하여 어쩌다 보니 토론 중반에는 내가 토론 참가자들에게 TDD를 설파하는 모습이 되어있었다.  내가 TDD로부터 많은 것을 얻었기에, 모든 개발자들이 나와 같은 즐거움을 느꼈으면 하는 의도에서 부족한 말솜씨지만, 많은 이야기를 전하게 되었다.

이외에 QA영역의 테스트에 관한 이야기도 많았지만, 지식이 많지 않아, 다른 사람들의 의견을 경청하였다. 특이했던 것은 Whitebox test만 진행하는 우리 회사의 QA process 와 달리 "옥션"에서는 Code 나 DB차원의 Blackbox test도 포함된다는 것이 이채로웠다.
TAG ontogeny, TDD

댓글을 달아 주세요