Outsider's Dev Story: Scala 카테고리 글 목록https://blog.outsider.ne.kr/Stay Hungry. Stay Foolish. Don't Be Satisfied.2024-03-15T15:04:14+09:00Textcube 1.10.7 : Tempo primoSpringCamp 2013 with Scala에서 발표한 "Spring Scala : 스프링이 스칼라를 만났을 때" 발표자료Outsiderhttps://blog.outsider.ne.kr/9882013-10-15T01:17:08+09:002013-10-15T00:52:44+09:00<p><a href="http://ksug.org/">KSUG</a>에서 오랫동안 스프링 컨퍼런스를 기획하고 준비했지만 이런 저런 일로 계속 미뤄지다가 올해 처음으로 <a href="http://springcamp.ksug.org/">SpringCamp 2013 with Scala</a>라는 이름으로 열렸고 이번에는 <a href="https://groups.google.com/forum/#!forum/scala-korea">라 스칼라 코딩단</a>도 함께 했다. 난 사실 양쪽다 소속되어 있지만 KSUG 보다는 라 스칼라 코딩단에서 더 많이 활동하기에 이번에는 라 스칼라 코딩단입장에서 참석을 했다. 처음에는 일부 세션 혹은 한 트랙정도로 준비를 하려다가 진행되다보니 여력이 좀 더 되서 라 스칼라 코딩단에서 7개 세션을 진행하게 되었고 그 중에 하나의 세션을 맡아서 발표를 했다.</p>
<p>사실 이 다음날 <a href="http://deview.kr/2013/">Deview</a>에서 발표가 예정되어 있었기 때문에 원래는 발표를 할 계획이 없었다가 컨퍼런스 준비를 하는 과정에서 스프링과 스칼라가 함께하는 행사이니 만큼 <a href="https://github.com/spring-projects/spring-scala">Spring Scala</a>를 발표하는 것도 괜찮다는 의견이 나왔고 나도 수긍을 했다. 부담되서 고민을 좀 하다가 이 주제를 내가 맡아서 발표를 진행했다.</p>
<p>끝나고 나서 하는 말이지만 이번 발표는 준비하는데 정말 힘들었다. ㅠㅠ 발표는 보통 2가지로 나눌 수 있는데 잘 알고 있는 것에 대한 지식이나 경험을 공유하거나 아니면 이슈가 되는 기술을 공부해서 알려주는(?) 형식이 될 수 있다. 전자가 훨씬 좋다고는 생각하지만 상황에 따라서는 둘 다 유효하긴 하다. 이번에는 후자의 경우였다. 작년에 <a href="http://blog.outsider.ne.kr/863">Dtrace</a>에 대한 발표를 했을 때 나는 이런 접근으로는 발표하지 말아야지 했었는데 이번에 한번 더 하고는 고생을 많이했다. 공부하는 것 자체가 어려운 것보다는 공부하고나니 내 생각처럼 발표의 방향을 정하기가 쉽지 않았기 때문이다.</p>
<p>스프링 스칼라는 커미터들의 의도때문인지 아직 초반이기 때문인지 기능이 솔직히 좀 빈약하다. 그래서 기능소개로는 발표시간을 채우기도 어려웠고 그정도 수준에서 발표한다는 것이 청중들에게 얼마나 의미가 있을것 같지도 않았다.(스프링원 같은 대형 컨퍼런스에서 이런 주제만으로 발표했다는게 의심스러울 정도...) 그래서 고민끝에 스프링 스칼라만 설명하는 것이 아니라 스칼라로 스프링을 사용하는 과정을 전체적으로 담으면(사실 스프링 스칼라에 관심있다는 것은 최종목표가 이것이므로) 좀 낫겠다 싶어서 <a href="https://github.com/spring-projects/spring-petclinic/">스프링의 Pet Clinic</a>예제를 스칼라로 변환하기 시작했다. 이 예제를 작성하는 동안 고생도 많이하고 사실 스트레스도 엄청 받았는데 어쨌든 다음 발표자료의 흐름을 만들게 되었다.(이것도 좀처럼 쉽지는 않았지만...)</p>
<iframe src="http://www.slideshare.net/slideshow/embed_code/27165023" width="512" height="421" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC;border-width:1px 1px 0;margin-bottom:5px" allowfullscreen> </iframe>
<div style="margin-bottom:5px"> <strong> <a href="https://www.slideshare.net/rockdoli/springcamp2013-springscala" title="Spring Scala : 스프링이 스칼라를 만났을 때" target="_blank">Spring Scala : 스프링이 스칼라를 만났을 때</a> </strong> from <strong><a href="http://www.slideshare.net/rockdoli" target="_blank">Outsider Byun</a></strong> </div>
<p>실제로 들으신 분들이 어떻게 들으셨는지는 정확히 모르지만 삽질기(?)를 공유하는 것도 어느 정도는 의미있다고 본다. 누군가 스칼라로 스프링을 사용해 보고자 하는 사람들에게는 의미를 줄 수 있다고 생각하고 그에 대한 고민을 하고 있는 사람들에게도 도움이 될 듯 하다.(미리 삽질을 해봤으니..) 개인적으로도 스칼라로 스프링을 사용하는 과정을 경험해 봤다는 면에서도 의미는 있었다. Deview와 날짜가 이어져서 발표준비등으로 많이 스트레스를 받았지만 큰 사고없이 발표를 마쳐서 다행이다.</p>
<p>추가적으로 좀 빈약했던 내용이었지만 커뮤니티를 통해서 리뷰를 3번이나 받았기 때문에 퀄리티가 꽤 올라갔다. 항상 느끼지만 좀 힘들어도 리뷰를 받으면 발표퀄리티를 확실히 높힐 수 있는것 같다. 이 자리를 빌어서 행사를 준비해 주신 KSUG와 라 스칼라 코딩단의 자원봉사자분들과 리뷰하는데 도움을 주신 라스칼라 코딩단 분들때 감사를 드린다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/988?commentInput=true#entry988WriteComment">댓글 쓰기</a></strong></p>Programming in Scala 스터디 후기Outsiderhttps://blog.outsider.ne.kr/9622013-07-21T23:49:00+09:002013-07-21T23:49:00+09:00<p>한국 스칼라 유저그룹인 <a href="https://groups.google.com/forum/#!forum/scala-korea">라 스칼라 코딩단</a>에서는 계속해서 스터디를 진행하고 있었었는데 후기를 남기는건 이번이 처음인듯 하다.(스터디도 후기를 계속 남기려고 하지만 이상하게 잘 안되더라는...) 스터디를 시즌으로 구분해서 진행하는데 벌써 5시즌이다. 지난 3,4시즌에서도 <a href="http://www.artima.com/shop/programming_in_scala_2ed">Programming in Scala</a>를 진행했었는데 5시즌에서도 <a href="http://www.artima.com/shop/programming_in_scala_2ed">Programming in Scala</a>(이하 PiS)로 진행을 했다. 두껍다는 것 외에는 굳이 다른 스칼라 책을 볼 필요가 없다고 느낄 정도로 좋은 책이니까...</p>
<p>라 스칼라 코딩단은 오랫동안 초기 멤버만 활동을 했기 때문에 스터디도 대부분 우리끼리 정하고 우리끼리만 모여서 진행을 하곤 했다.(물론 공지는 했지만 아무도 오지 않았다.) 그러다가 이유는 알 수 없지만 작년 후반기부터 새로운 멤버들이 많이 참여를 하게 되면서 스터디에 대한 요구가 높아졌지만 진행은 잘 되지 않았다. 일단 바로 얼마전에 PiS를 스터디 했기 때문에 또 하고 싶진 않은 마음이 있었기 때문일 것이다. 그래서 신규멤버 중심으로 스터디가 진행되었으면 하는 마음에 쓰레드가 자주 오갔지만 실제적으로 진행이 되지 않았고 스터디 주도는 잘 안하는 편이긴 하지만 PiS를 봤어도 1년 넘게 봤기 때문에 앞에는 다 까먹었고 다른 교재가 있는 것도 아니라서 이번엔 총대를 메고 진행을 하기로 했다.</p>
<p>그래도 그룹 스터디는 여러번 했기 때문에 이번에는 몇가지 시도를 해봤다.</p>
<ul>
<li><code>장소 마련을 위한 회비를 걷는다.</code> 예전에는 무료로 이용할 수 있는 장소를 찾아서 점프하면서 진행했었는데 이제는 그것도 어려워서 돈으로 해결하기로 했다. 그래서 Toz같은 곳을 이용하기로 했고 매달 첫 모임때 돈을 2만원 정도씩 걷기로 했다. 매달 걷기로 한 이유는 모임이 길어질 것이므로 초기에 너무 많은 부담을 주지 않으려는 의도였고 스터디가 진행되면서 인원이 계속 변하기 때문에 매달 첫 모임때만 걷기로 했다. 누군 내고 누군 안내고 체크하는건 귀찮은 일이기 때문에 첫주 안나오고 두번째 주에 나온다고 쫓아가서 수금하진 않는다. 의도적으로 그렇게까지 하는걸 막기 위해서 관리하느니 관리비용을 줄이겠다는 의도였지만 중간에 <a href="http://www.nipa.kr/">NIPA</a>의 커뮤니티 지원을 알게되어 2-3번 정도의 모임후부터는 NIPA의 지원을 받게 되었다. 이건 무척 좋은 일인데 돈을 더이상 걷지 않아도 되는건 둘째치고 장소 예약을 위해서 매주 이번주 예상 인원을 파악해서 예약하는게 보통일이 아니기 때문에(이 예상이 실패하면 금액에 대한 리스크가 운영하는 입장에선 무척 크다.) NIPA 덕에 무척 편하게 이용을 했다.</li>
<li><code>발표중심으로 하지만 스터디를 하지만 발표자를 미리 선정하지 않는다.</code> <strong>스터디를 꽤 해봤지만 한 명이 이번주 진도에 대한 내용을 발표하고 나머지는 들으면서 질문하거나 토론하는 등의 방식이 제일 무난하다.</strong> 다른 방법들도 있지만 모두가 적극적으로 참여하지 않는다면 좀처럼 쉽지 않다. 하지만 <strong>이 방식의 문제는 발표자는 책임이 있으니 열심히 공부하게 되지만 나머지는 시간이 지날수록 손놓게 된다는 점이다. 그리고 개인적인 일이 생겨서 발표하기로 된 주에 못나오게 된 경우이 공백도 너무 커서 대처하기가 어렵다. 그래서 택한 방법이 발표자(우린 진행자라고 불렀다.)를 스터디 시작할때 사다리타서 정한다는 것이었다.</strong> 공부는 모두 해오고 한명이 발표하는 것이 아니라 한명이 진도에 따라서 진행을 하면서(진행자이므로 다른 사람 시키거나 해도 되지만 아무도 그렇게 하진 않음.) 다같이 보자는 의도였다. 아주 잘 됐는지는 모르겠지만 스터디가 진행되는데 큰 무리는 없었기에 만족하고 있다.</li>
<li><code>공부한 내용을 [위키](https://github.com/codeport/scala/wiki/Programming-in-scala)에 정리한다.</code> 앞의 방식으로 진행자를 현장에서 뽑아서 진행하려면 미처 공부 못해온 분도 진행을 할 수 있도록 어떤 정리된 내용이 필요했다. 이미 3,4시즌에 대충이나마 정리한 위키가 있었기에 거기에 내용을 채우는 방식으로 진행을 했다. <strong>누가 진행을 하게 될지 모르므로 다같이 정리를 하도록 하는 의도였고 이렇게 정리된 내용이 있으면 다른 사람의 공유는 부차적이고 일단 공부한 사람들이 나중에 잊어버린 내용을 다시 공부할 때 참고하기 좋다고 본다.</strong> 물론 뒤로 갈수록 위키 정리양은 약해지긴 했지만 주업도 아닌 스터디에 너무 큰 부담을 주는건 아니라고 보므로 괜찮다고 생각한다.</li>
<li><code>PiS 스터디를 두번으로 나누어서 진행한다.</code> 경험상 한 스터디가 6개월이 넘어가면(매주하던 격주로 하던) 엄청 힘들다. 개개인의 피로감이 커지고 진행도 힘들고 참여율도 많이 떨어지게 된다. 더군다나 PiS는 책이 꽤 두껍기 때문에 매주 한장씩 나가도 6개월 이상이 필요하고 중간에 이런 저런 일을 생각하면 1년은 걸릴것 같았기에 두번으로 나누기로 했고 뒷부분은 스칼라 내부 내용이라 빼고 20장정도까지만 나가는 걸로 해서 10장씩 나누어서 진행하기로 했다.(실제로는 12장까지 진도를 뺐다.) 초반에 고려한 타이밍은 아니지만 마침 목표한 일정이 휴가시즌을 앞두고 끝나서 쉬기 적절한 타이밍인것 같다.(사람들도 약간씩 지친것 같고..)</li>
<li><code>실습과 이론을 병행한다.</code> 스칼라 스터디를 하면서 매번 반복되는 이슈 중 하나가 책을 보다보니 실제 코드를 잘 못짜겠고(업무에서 쓰는게 아니라) 코드를 짜는 스터디를 하면 이론을 몰라서 또 못짠다. 그래서 PiS 스터디와 실습을 병행해서 하고 싶었고 실습을 <a href="http://euler.synap.co.kr/">오일러 프로젝트</a>로 하는 걸 제안했다. 다같이 프로그램을 하나 짜는건 좀 어렵고(중간에 끼기도 힘들고) <strong>알고리즘 풀이는 스칼라 작성보다는 알고리즘을 고민하는데 시간을 다 보내게 되서 오일러를 선택했다. 오일러는 대부분이 익숙한 자바로 된 풀이가 이미 존재하고 있기 때문에(물론 다른 언어도 거의 다 있다.) 기본 정책은 자바로 짜여진 코드를 스칼라로 포팅하는 수준으로 잡았다. 알고리즘 고민 대신에 스칼라로 어떻게 작성하기를 바랬고 같은 코드를 보고 다른 사람들은 어떻게 작성하는지 비교하면서 볼 수 있기를 바랬다.</strong> 뒤로 갈수록 사람들이 각자의 알고리즘으로 풀어오면서 재미도 꽤 있었다. 2차 스터디에는 어떻게 할지 아직 안정했지만 나쁘지는 않았다고 본다. 오일러 문제를 푸는 주간에는 참여율이 낮아진다는 가설은 결국 해결되지 않았지만....</li>
</ul>
<p>이번 스터디는 유부남들이 많아서인지 일요일 아침 스터디를 원하는 사람이 많아서 일요일 아침 10시에 스터디를 했다. 보통 주말에는 오후까지 자는게 일상인 나는 특히 아침에는 쥐약인 편이라 무척 힘들었고 뒤로 갈수록 지각이 많았졌다.(거기에 두번이나 제꼈다.) 그래도 운영진으로 자원해주신 이승우님의 백업(무한 감사를..)으로 스터디는 원활하게 진행이 됐다.(운영자가 게을러서 힘들;;;)</p>
<p>그래도 첫 모임때는 20명이 좀 넘었었는데 마지막까지 남은 사람이 10명이 약간 넘으니까 이정도면 엄청난 참여율이라고 생각된다. 짧은 스터디도 아니고 4월부터 7월까지 4달동안 진행한 스터디인데 50%나 놀라울 정도다. 다들 관심도 많고 적극적으로 해주다 보니 매끄럽지 못한 운영(?)도 잘 넘어간 것 같다. 특히 초반에 진도빼고 남는 시간을 통해서 자발적으로 발표했던 내용들은 재미있었다. 아침에 일찍 일어나니 알찬 주말을 계속 보내긴 했지만 그래도 좀 쉬자.. ㅎ</p>
<p><strong><a href="https://blog.outsider.ne.kr/962?commentInput=true#entry962WriteComment">댓글 쓰기</a></strong></p>Scala의 partially applied function과 partial function 이해하기Outsiderhttps://blog.outsider.ne.kr/9532013-06-19T02:41:23+09:002013-06-19T02:41:23+09:00<p>이 글은 Szabolcs Andrási가 작성한 <a href="http://sandrasi-sw.blogspot.kr/2012/03/understanding-scalas-partially-applied.html">Understanding Scala's partially applied functions and partial functions</a>를 번역한 글이다. 스칼라 스터디에서 정대원님이 partial function을 설명하는 걸 듣고 관심이 생겨서 찾아보다가 번역을 하게 되었다. 참고로 번역은 원 글을 쓴 Szabolcs Andrási의 허락을 받고 올린다.(항상 그렇듯이 번역품질은 보장 못하니 영어되시는 분들은 원문을....) 그리고 이 글에서는 번역이 애매한 용어는(partial function같은 ) 그냥 원문을 그대로 사용했다.</p>
<p><br><br />
비슷해 보이는 <strong>partially applied function</strong>과 <strong>partial function</strong> 이 두 용어는 함수형 프로그래밍 언어를 처음 사용하는 사람들에게는 꽤 헷갈리는 용어이다. 스칼라가 비슷한 이름의 두 함수 타입의 차이점을 이해하는데 도움이 주지 않으므로 꽤 어렵다. 예제를 통해서 두 함수의 차임점을 설명해 보고자 한다.</p>
<p>먼저 몇가지 용어를 정의하고 시작하자.<br />
<br></p>
<ul>
<li>스칼라에서 메서드는 자바처럼 클래스(또는 객체)의 일부분이고 완전한 시그니처(이름, 인자, 반환값, 추상메서드가 아니면 메서드를 구현하는 바디)를 가진다. 예를 들면 다음과 같다.</li>
</ul>
<pre class="line-numbers"><code class="language-scala">class Math {
def add(a: Int, b: Int): Int = a + b
}
</code></pre>
<p>편의성을 위해서 반환값은 생략할 수 있으므로 스칼라는 메서드의 마지막 표현식에서 타입을 추론한다.<br />
<br></p>
<ul>
<li>반면 스칼라 함수(Scala function)는 사실 클래스다. 스칼라에는 여러가지 갯수의 인자를 갖는 함수를 나타내는 트레이트(trait)들이 있다. 파라미터가 없으면 <code>Function0</code>이고 파라미터가 하나면 <code>Function1</code>, 파라미터가 둘이면 <code>Function2</code>같은 방식으로 <code>Function22</code>까지 있다. 이러한 트레이트로 함수는 트레이트(실제 파라미터 수에 따락 적합한 트레이트)를 믹스인한 클래스로 메서드를 가진다. 여기서 가장 중요한 메서드는 함수바디의 구현이 있는 <code>apply</code>메서드다. <code>apply</code>메서드의 인자 갯수는 믹스인한 트레이트 이름의 수와 완전히 같다. 즉, <code>Function0</code>이면 0개이고 <code>Function1</code>이면 1개, <code>Function2</code>이면 2개 등이다. 예를 들면 다음과 같다.</li>
</ul>
<pre class="line-numbers"><code class="language-scala">object add extends Function2[Int, Int, Int] {
override def apply(a: Int, b: Int): Int = a + b
}
</code></pre>
<p>스칼라는 여러 가지 편의문법을 제공하는데 특수한 <code>apply</code>문법도 편의문법 중 하나이다. 심볼이름 뒤에 괄호와 함께 인자 목록을 작성하면 스칼라는 해당 이름을 가진 객체의 <code>apply</code> 메서드를 호출하도록 변환한다. 위 예제에서 <code>add.apply(1, 2)</code> 대신 <code>add(1, 2)</code>로 작성해서 같은 결과를 얻을 수 있다.<br />
<br></p>
<ul>
<li>함수 리터럴이나 익명 함수는 함수를 정의하는 대체 문법이다. <code>FunctionN</code> 트레이트를 믹스인하고 <code>apply</code> 메서드를 오버라이딩하는 새로운 클래스를 생성하는 대신 괄호안에 함수 파라미터명의 목록을 작성하고 우측방향의 화살표를 쓴 다음 함수의 바디를 쓸 수 있다. 예를 들면 다음과 같다.</li>
</ul>
<pre class="line-numbers"><code class="language-scala">(a: Int, b: Int) => a + b
</code></pre>
<p>이러한 함수 리터럴에서 스칼라 컴파일러는 <code>FunctionN</code> 트레이트 중 하나를 믹스인한 <code>function</code> 객체를 생성한다. <code>=></code>의 좌측부분은 파라미터 목록이 되고 우측부분은 <code>apply</code> 메서드의 구현부가 된다. 여기서 보듯이 <code>function</code>의 단축형식일 뿐이다.<br />
<br></p>
<ul>
<li>함수값(function value)은 <code>FunctionN</code> 트레이트를 확장하는 클래스의 인스턴스다. 예를 들어 함수 리터럴은 일단 <code>FunctionN</code> 트레이트를 믹스인하는 클래스로 컴파일되고 런타임에서 인스턴스화된다. 여기서 생성된 인스턴스는 함수값이다. 함수값이 객체기 때문에 변수에 저장할 수 있지만 동시에 함수이기도 해서 괄호를 사용하는 함수호출 표기법으로 호출할 수 있다.(사실 변수에 할당된 함수클래스 인스턴스의 <code>apply</code> 메서드를 호출하도록 변환될 것이다.) 예를 들면 다음과 같다.</li>
</ul>
<pre class="line-numbers"><code class="language-scala">val add = (a: Int, b: Int) => a + b
add(1, 2) // returns 3
</code></pre>
<p><br></p>
<h2>Partially applied function</h2>
<p>이제 용어에 익숙해졌으므로 <strong>partially applied function</strong>을 얘기할 차례이다. 지금부터는 메서드와 함수라는 용어를 같은 의미로 바꿔가면서 사용할 수 있다. 엄밀히 말하자면 같은 의미가 아니지만 <strong>partially applied functions</strong>를 설명할 때는 그리 중요하지 않으므로 여기서는 같은 의미로 사용할 것이다.</p>
<p>일반적으로 메서드나 함수를 호출할 때는 필요한 인자를 전부 전달하고 함수는 받은 인자를 가지고 값을 계산한다. 하지만 스칼라에서는 반드시 인자를 전부 전달해야 하는 것은 아니고 인자 중에서 일부만 전달하거나 인자를 전혀 전달하지 않을 수도 있다. 이러한 경우에 누락된 인자가 있으므로 스칼라는 값을 계산하지 않고 대신 제공된 인자로 <strong>partially applied function</strong>를 자동적으로 생성하고 <strong>partially applied function</strong>에서 <strong>function value</strong>를 생성한다. 생성한 <strong>partially applied function</strong>은 <code>FunctionN</code> 트레이트를 믹스인하고 여기서 <code>N</code>은 누락된 함수 인자의 갯수이다. 생성된 함수의 <code>apply</code>메서드 바디는 <strong>partially applied function</strong>의 <code>apply</code>메서드에 전달된 인자와 원래 제공된 인자로 인자를 모두 채워서 원래 함수의 <code>apply</code> 메서드를 호출한다. 꽤 복잡해 보이기는 하지만 예제로 보면 별로 복잡하지 않다.</p>
<pre class="line-numbers"><code class="language-scala">val add = (a: Int, b: Int) => a + b
val inc = add(_: Int, 1)
inc(10) // returns 11
</code></pre>
<p>이 예제에서는 다음과 같은 과정이 진행된다. <code>Int</code>인자는 제공하지 않고 다른 인자는 숫자 1로 지정해서 <code>add(Int, Int)</code> 함수를 호출한다. 여기서 누락한 인자는 언더스코어(_)로 대치했다. 인자 중에서 일부만 제공했기 때문에 <code>add(Int, Int)</code>를 실행할 수 없어서 <code>inc</code>는 함수의 결과를 저장하는 것이 아니라 생성된 <strong>partially applied function</strong>에서 인스턴스화된 <strong>function value</strong>에 대한 참조를 저장할 것이다. 여기서 생성된 함수의 타입은 무엇일까? 누락된 인자의 갯수가 하나뿐이므로 타입은 <code>Function1</code>이 될 것이다. <code>Function1</code>은 두가지 타입의 파라미터를 가지는데 하나는 함수의 입력 파라미터이고 다른 하나는 결과 타입이다. <code>add</code>의 누락된 인자 타입은 <code>Int</code>이고 결과 타입도 <code>Int</code>이므로 두 타입 파라미터는 모두 <code>Int</code>가 된다. 그리고 이 함수의 <code>apply(Int)</code> 메서드는 정확히 무엇을 할까? <code>apply</code>에 전달한 파라미터와 고정된 인자값 <code>1</code>로 원래의 <code>add</code>함수를 호출한다. 이제 모든 내용을 알았으므로 직접 <strong>partially applied function</strong>를 구현할 수도 있지만 스칼라 컴파일러가 이 작업을 해주므로 직접 할 필요는 없다.</p>
<pre class="line-numbers"><code class="language-scala">object inc extends Function1[Int, Int] {
override def apply(v: Int): Int = add(v, 1)
}
inc(10) // returns 11
</code></pre>
<p>또 다른 경우는 인자를 전혀 제공하지 않은 경우이다. 누락된 인자를 언더스코어로 나타낼 수 있지만 스칼라는 더 간단한 형식을 지원하고 있다. 전체 파라미터 목록 대신에 함수 이름뒤에 언더스코어 문자를 하나 붙힐 수 있다. 즉, <code>add _</code>처럼 작성할 수 있다.(함수 이름과 언더스코어 사이에 공백이 있음에 주의해야 한다! 이 공백 문자는 반드시 넣어주어야 하고 공백이 없으면 스칼라 컴파일러가 <code>add_</code>를 호출하는 함수로 생각할 것이다.) 언더스코어문자까지도 없애서 더 간단하게 사용할 수도 있는데 코드가 다른 함수를 인자로 받는 <strong>higer order function</strong>인 경우이다. 예를 들어 <code>Array(1, 2, 3).foreach(println)</code>처럼 <code>GenTraversableOnce</code> 트레이트가 정의한 <code>foreach</code> 메서드는 파라미터로 함수를 받는다. 여기서 (<code>scala.Predef</code> 객체에 정의된) <code>println(Any)</code> 메서드를 배열의 <code>foreach</code> 메서드에 전달한다. <code>println</code> 전달하는 인자가 없고 <code>foreach</code>가 함수를 받으므로 <code>println</code>뒤에 언더스코어를 붙히지 않아도 된다. 스칼라 컴파일러는 <code>println</code> 메서드에서 딱 하나의 인자를 받는 <strong>partially applied function</strong>를 생성한다. <code>foreach</code> 메서드가 배열을 순회하면서 각 요소를 <strong>partially applied가 적용된 println</strong>에 인자로 전달하므로 배열의 각 요소가 콘솔에 출력된다.</p>
<h2>Partial function</h2>
<p>이름은 비슷하지만 <strong>partial function</strong>은 <strong>partially applied function</strong>와 전혀 관계가 없다. 하지만 수학을 공부했다면 여기서 <strong>partial function</strong>이 의미하는 것을 알 것이다. <strong>X</strong>에서 <strong>Y</strong>로의 <strong>partial function</strong>은 함수 <strong>ƒ: X' → Y</strong>이고 여기서 <strong>X'</strong>는 <strong>X</strong>의 서브셋이다. 즉, 함수는 <strong>X</strong>의 모든 요소에 대해 정의된 것이 아니다. 가장 명확한 예제가 나눗셈 함수인데 나눗셈 함수는 숫자를 다른 숫자로 메핑하지만 0에는 매핑된 숫자가 없다. 더 형식적으로 적으면 <strong>ƒ(x, y) = x / y</strong>이고 <strong>x, y ∈ ℝ</strong>이고 <strong>y ≠ 0</strong>이다.</p>
<p>스칼라의 <strong>partial function</strong>은 이와 동일하다. 즉, 가능한 입력 인자의 서브셋에 대해서만 정의된 함수이다. 실제로 어떻게 구현하는 지 보기 전에 패턴매칭을 먼저 보자. 패턴매칭은 이 글의 범위를 벗어나기 때문에 여기서는 변수 정의부터 패턴매칭의 표현식까지 스칼라가 패턴을 많이 사용하고 있다는 것을 아는 것만으로도 충분하다. 매턴매칭에서 <code>match</code> 표현식의 <code>case</code> 문으로 다수의 경우의 수 중에서 선택할 수 있다.</p>
<pre class="line-numbers"><code class="language-scala">args(0) match {
case "foo" => println("bar")
case _ => println("?")
}
</code></pre>
<p>다양한 종류의 패턴이 존재하지만 간단히 말해서 <code>case</code> 키워드 뒤에 오는 것은 무엇이든지 간에 모두 패턴이라고 할 수 있고 표현식이 해당 패턴에 일치한다면 화살표의 우측부분을 실행한다. <code>case</code>문(case sequence)을 사용할 수 있는 곳에만 <code>match</code>표현식을 사용할 수 있는 것은 아니다. 사실 함수리터럴을 사용할 수 있는 곳에는 모두 중괄호안의 <code>case</code>문을 사용할 수 있다. 그 이유는 이 생성과정이 함수리터럴이기 때문이다. 하지만 좀 더 일반적으로 이야기하자면 일반적인 함수나 메서드가 해당 파라미터 목록으로 딱 하나의 진입점만을 갖는 반면에 <code>case</code>문은 여러 진입점을 가지면서 각 진입점은 각자의 파라미터(패턴으로 정의한)를 가진다. 또 다른 차이점은 하나의 함수는 딱 하나의 함수 바디를 가지지만 <code>case</code>문은 <code>case</code>형식만큼의 다양한 함수 바디를 가진다는 것이다. 즉, 각 <code>case</code>문(또는 진입점)의 우측부분이 함수바디가 된다. 이러한 일반화를 통해 <code>case</code>문이 인자의 서브셋(어떤 패턴매칭이든)에서만 해석하고 그외의 다른 경우에는 정의되지 않으므로 <strong>partial function</strong>이 된다.</p>
<pre class="line-numbers"><code class="language-scala">val div: (Double, Double) => Double = {
case (x, y) if y != 0 => x / y
}
</code></pre>
<p>위 예제는 앞에서 얘기한 나눗셈 함수의 구현이다. 다양한 사례를 보여주지는 않지만 <code>(x, y) if y != 0</code>은 <strong>partial function</strong>을 설명하기에 충분하다. 0으로 나누는 경우를 제외하고는 모든 숫자가 쌍으로 정의되어 있다. <code>div(1, 0)</code>를 호출하면 무엇을 출력할까? 이 함수에 0으로 나누는 경우에 대한 패턴을 정의하지 않았으므로 <code>MatchError</code>이 발생하면서 실패할 것이다.</p>
<p>하지만 이 방법으로 <code>case</code>문을 정의하는 데는 두가지 작은 문제점이 존재한다. 첫번째 문제는 입력인자가 유효한 값의 서브셋이라면 테스트할 방법이 없다는 것이다.(대신 함수 자체를 호출한다.) 예외를 피하려면 함수호출을 <code>try-catch</code> 블럭으로 감싸야 하지만 코드가 지저분해진다. 두번째 문제는 <code>case</code>문에서 사용한 패턴에서 가능한 값을 스칼라 컴파일러가 전부 알고 있다면 모든 경우에 대해서 정의하지 않을 경우 경고 메시지를 보여줄 것이다. 이러한 경우에 누락된 패턴값에서는 런타임 예외가 발생하므로 컴파일러의 경고를 들어야 한다. 하지만 <strong>partial function</strong>에서는 전부가 아닌 일부만 구현하기를 원할 것이다.</p>
<p>다행히 스칼라는 이 두가지 문제를 해결하는 우아한 방법을 제공한다. <strong>partial function</strong>를 생성하고자 한다고 컴파일러에게 알려주면 합수가 입력인자에 정의되었는지 검사할 뿐만 아니라 누락된 패턴을 경고하지 않을 것이다. 그러면 <strong>partial function</strong>를 원한다고 어떻게 컴파일러에게 알려줄 수 있는가? 아주 간단하다. 그냥 <code>PartialFunction</code> 타입으로 함수를 정의하기만 하면 된다. <code>div</code>함수를 다시 보자. 이번에는 실제로 <strong>partial function</strong>이다.</p>
<pre class="line-numbers"><code class="language-scala">val div: PartialFunction[(Double, Double), Double] = {
case (x, y) if y != 0 => x /y
}
</code></pre>
<p>기본적으로 <code>PartialFunction</code>은 특별한 <code>Function1</code>이다. <code>PartialFunction</code>은 <code>Function1</code>가 가진 모든 메서드를 가지고 그외 몇가지 편리한 메서드를 더 가지고 있다. 예를 들어 함수가 입력인자에 정의되었는지 검사할 수 있는 <code>isDefinedAt()</code>가 있다.</p>
<pre class="line-numbers"><code class="language-scala">div.isDefinedAt(1, 0) // returns false
</code></pre>
<p>이 메서드를 사용해서 <code>try-catch</code>블럭을 피할수도 있고 <code>MatchError</code> 예외를 잡는것보다 더 표현력이 좋다.</p>
<p>스칼라에 익숙해 질수록 <strong>partially applied function</strong>과 <strong>partial function</strong>이 많은 곳에서 사용되고 있다는 것을 알게 될 것이다. 예를 들어 <strong>higher order function</strong>을 사용할 때 실제로는 <strong>partially applied function</strong>으로 동작한다. 또는 <code>try-catch</code> 블락에서 <code>catch</code>블락이 본질적으로는 <code>partial function</code>이라는 것을 알게될 것이다. 이 글이 <strong>partially applied function</strong>과 <strong>partial function</strong>에 대한 속설을 명확하게 하는데 도움이 되기를 바하고 이제 자신의 함수를 직접 작성할 수 있다.(그리고 사용할 수 있는 곳을 찾아야 할 것이다.)</p>
<p><strong><a href="https://blog.outsider.ne.kr/953?commentInput=true#entry953WriteComment">댓글 쓰기</a></strong></p>Slick 유닛테스트에서 Session관련 중복코드 제거하기Outsiderhttps://blog.outsider.ne.kr/9512013-06-07T02:12:42+09:002013-06-07T02:12:42+09:00<p><a href="http://blog.outsider.ne.kr/950">이전 포스팅</a>에서 <a href="http://www.playframework.com/">Play 2.1</a>에 <a href="http://slick.typesafe.com/">Slick</a>을 연동하는 방법을 설명했는데 이때 작성한 테스트 코드를 다시 보자.</p>
<pre class="line-numbers"><code class="language-scala">package models
import org.scalatest.FunSpec
import org.scalatest.matchers.ShouldMatchers
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
class UserSpec extends FunSpec with ShouldMatchers {
describe("example") {
it("사용자를 추가하고 조회한다") {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
// given
Users.ddl.create
Users.add(new User("outsider", "Outsider", "example@gamil.com"))
// when
val results = Users.findAll
// then
results.size should equal(1)
}
}
it("사용자를 추가하고 조회한다 2") {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
// given
Users.ddl.create
Users.add(new User("outsider", "Outsider", "example@gamil.com"))
// when
val results = Users.findAll
// then
results.size should equal(1)
}
}
}
}
</code></pre>
<p>테스트를 하나 더 추가했는데 여기서 보듯이 Slick을 돌리기 위해서 <code>Session</code>이 필요하기 때문에 디비 설정을 위한 세션을 유닛테스트마다 추가해 주어야 한다. 즉, 다음과 같은 코드를 모든 유닛테스트마다 추가해 주어야 한다.</p>
<pre class="line-numbers"><code class="language-clike">Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
Users.ddl.create
}
</code></pre>
<p>보통 하나의 스펙에서는 같은 디비설정을 사용할 것이고 DDL 정의도 동일할 것이므로 이는 상당한 중복을 발생시키고 불필요한 중복코드로 테스트코드를 읽는데도 방해가 된다. 이 문제를 어떻게 해결해야 할지 알 수 없어서 <a href="http://stackoverflow.com/questions/16758228/how-can-i-remove-codes-creating-session-in-unit-test-for-play-framework-and-slic">스택오버플로우에 질문</a> 올렸더니 Slick의 메인개발자인 <a href="https://github.com/szeiger">Stefan Zeiger</a>가 직접 대답을 해주었다. 덕분에 유닛테스트를 다음과 같이 수정할 수 있다.</p>
<pre class="line-numbers"><code class="language-scala">package models
import org.scalatest.{BeforeAndAfter, FunSpec}
import org.scalatest.matchers.ShouldMatchers
import scala.slick.driver.H2Driver.simple._
class UserSpec extends FunSpec with BeforeAndAfter with ShouldMatchers {
implicit var session: Session = _
before {
session = Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver").createSession()
Users.ddl.create
}
after {
session.close()
}
describe("example") {
it("사용자를 추가하고 조회한다") {
// given
Users.add(new User("outsider", "Outsider", "example@gamil.com"))
// when
val results = Users.findAll
// then
results.size should equal(1)
}
it("사용자를 추가하고 조회한다 2") {
// given
Users.add(new User("outsider", "Outsider", "example@gamil.com"))
// when
val results = Users.findAll
// then
results.size should equal(1)
}
}
}
</code></pre>
<p>일단 임포트문에서 <code>threadLocalSession</code>을 제거해주고 유닛테스트 앞뒤로 실행할 <code>before</code>와 <code>after</code>를 사용하기 위해서 ScalaTest의 <code>BeforeAndAfter</code> 트레이트를 믹스인한다. 테스트에서 사용할 <code>Session</code> 변수를 선언하고 <code>before</code>에서 세션을 생성하고 DDL 문을 생성하고 <code>after</code>에서 세션을 닫아준다. 이렇게 작성하면 각 유닛테스트에는 테스트에 필요한 코드만 넣을 수 있고 마찬가지로 각 테스트는 독립적인 세션하에서 실행할 수 있다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/951?commentInput=true#entry951WriteComment">댓글 쓰기</a></strong></p>Play 2.1에 Slick 연동하기Outsiderhttps://blog.outsider.ne.kr/9502013-06-07T00:02:27+09:002013-06-06T23:59:29+09:00<p>최근 scala 학습을 위해서 개인 프로젝트에 scala를 사용하면서 <a href="http://www.playframework.com/">play</a>를 사용하기로 했다. play는 1.0때 사용하고 2.0은 거의 안써봤는데 scala에서는 웹프레임워크의 선택권도 많지 않고 Play 2.0이 <a href="http://typesafe.com/">Typesafe</a> 스택이기도 해서 선택했다. Play 2.0에 대한 생각도 여러가지로 들긴 하는데 이건 좀 더 써보고 하기로 하고 Play는 데이터접근 계층에 <a href="http://www.playframework.com/modules/scala-0.9.1/anorm">Anorm</a>을 사용하는데 Typesafe 스택이기도 하면서 요즘 좀더 대세인 <a href="http://slick.typesafe.com/">Slick</a>을 사용하기로 했다. Slick에 대한 자세한 얘기는 내용이 많아 다음으로 미루고 Play 2.1에서 Slick을 연동하는 방법을 을 살펴보자.</p>
<h2>Play Framework와 Slick 연동</h2>
<p>Slick을 통합하면서 원하는 봐는 간단했다. <strong>데이터접근 계층에 Slick을 사용하고 데이터접근 계층만 별도로 테스트케이스를 작성할 수 있어야 한다. 그리고 어플리케이션에서는 사용하는 RDBMS를 사용하고 테스트케이스에서는 메모리디비등으로 교체해서 테스트할 수 있어야 한다.</strong> 당연한 요구사항이지만 문서도 많지 않고 관련글이 많지 않아서 이 설정만 하는데도 꽤 어려웠다. 검색해보면 Play2에 Slick을 통합하는 몇가지 플러그인이 있기는 한데 Play 2.0이 자세히 파악안된 상태라서 이 문제를 모듈로 해결하는게 맞는 접근인지 약간 애매했고 어느 모듈이 잘 유지가 되는지 잘 몰랐기에 직접 구현하는 방법을 선택했다. 나중에 <a href="https://twitter.com/ducky_h">홍덕기</a>님이 알려주셔서 보니 <a href="https://github.com/freekh/play-slick">play-slick</a> 모듈이 괜찮아 보이고 개발자가 Typesafe직원이므로 유지도 잘 될듯 하다.</p>
<p>검색을 해보면 Slick과 관련한 글이 많지 않은데(Slick은 정말 많지 않으므로 ScalaQuery로 검색해 보는 것도 도움이 된다.) 아주 기본적인 사용법을 설명할 글 정도밖에 없다. 하지만 어플리케이션을 작성한다고 생각하면 당연히 모델클래스가 다수 존재하게 되므로 한 파일에서 상단에서 디비연결을 하고 하단에서 Slick을 사용하는 방식으로는 코드를 작성하지 않는다. 당연히 디비를 관리하는 클래스나 객체가 별도로 있고 여기서 커넥션을 받아서 각 모델클래스가 사용하는 것이 자연스럽고 테스트코드를 작성할 때는 이 디비 관리 클래스의 커넥션 정보를 바꿔서 테스트할 수 있어야 하는데 대다수의 예제는 그냥 하나의 파일에서 디비설정하고 Slick 사용법을 알려주는 정도가 전부였다.</p>
<p>그나마 찾은 글이 <a href="http://www.blogeek.com.ar/2012/11/24/play-framework-2-2-1-scala-with-slick-made-easy-with-example/">Play! Framework 2 (2.1) Scala with Slick made easy (With example)</a>라는 글이었고 이 글은 <a href="https://github.com/slick/slick-examples">Github에 예제</a>도 올라와 있어서 참고하기에 괜찮았다. 이 예제는 <a href="http://korean-nerdism.blogspot.kr/2013/02/cake-pattern.html">Cake 패턴</a>을 이용하고 있고 나에겐 약간 어색하게 느껴지는 Dal(Data Access Layer)라는 클래스를 사용해서 디비연결을 관리하고 있었고 테스트하는 방법까지 깔끔하게 설명하고 있었다. Play 2.0도 많이 모르고 Cake패턴도 잘 모르는지라 소스구조를 자세히 이해할 수 있지만 잘 돌아갔는데 막상 실제 개발을 하려고 보니 첫번재 테스트케이스는 잘 돌아갔지만 두번째 테스트케이스를 작성하니 세션이 종료되었다는 오류가 발생했다.(예제를 위한 예제일 뿐인가. ㅠㅠ) 그리고 이 예제에서는 테스트케이스마다 <code>FakeApplication(additionalConfiguration = inMemoryDatabase())</code>를 사용하고 있는데(이는 Play가 제공하는 기능인듯하다.) 이 <code>FakeApplication</code>이 내부에서 어떻게 동작하든지 상관없이 데이터접근 계층을 테스트하는데 어플리케이션을 실행한다는 게 나로써는 좀 이상해 보였다.</p>
<p>그러다가 <a href="http://www.jroller.com/ouertani/entry/getting_started_with_play_2">Getting Started with Play 2.1 , Scala 2.10 and Slick 0.11.1</a>라는 글을 찾아서 여기에서 제시하는 방법으로 변경했고 지금은 잘 동작하고 있다.</p>
<h2>Slick 의존성 추가</h2>
<p>Play 자체를 설명하기 위한 글은 아니므로 <a href="http://blog.outsider.ne.kr/944">지난번에 소개한 Activator</a>의 <code>hello-play</code>로 Play 프로젝트를 구성한다. 다음은 프로젝트의 sbt설정인 <code>project/Build.scala</code>파일이다.</p>
<pre class="line-numbers"><code class="language-scala">import sbt._
import Keys._
import play.Project._
object ApplicationBuild extends Build {
val appName = "hello-play"
val appVersion = "1.0-SNAPSHOT"
val appDependencies = Seq(
// Select Play modules
jdbc, // The JDBC connection pool and the play.api.db API
//anorm, // Scala RDBMS Library
//javaJdbc, // Java database API
//javaEbean, // Java Ebean plugin
//javaJpa, // Java JPA plugin
//filters, // A set of built-in filters
javaCore, // The core Java API
// WebJars pull in client-side web libraries
"org.webjars" % "webjars-play" % "2.1.0",
"org.webjars" % "bootstrap" % "2.3.1",
// for slick
"com.typesafe.slick" %% "slick" % "1.0.0",
"postgresql" % "postgresql" % "9.1-901-1.jdbc4",
"org.scalatest" % "scalatest_2.10" % "1.9.1" % "test"
// Add your own project dependencies in the form:
// "group" % "artifact" % "version"
)
val main = play.Project(appName, appVersion, appDependencies).settings(
scalaVersion := "2.10.1"
// Add your own project settings here
)
}
</code></pre>
<p>JDBC를 사용해야 하므로 의존성에서 jdbc의 주석을 풀어주고(기본으로는 주석처리가 되어 있다.) <code>for slick</code>부분에 의존성을 추가한다. slick을 추가하고 사용할 RDBMS의 jdbc 드라이버를 추가했다.(여기서는 PostgreSQL), 그리고 Play의 기본 테스트프레임워크인 <a href="http://etorreborre.github.io/specs2/">spec2</a> 대신 <a href="http://www.scalatest.org/">ScalaTest</a>를 사용하기 위해서 ScalaTest도 추가했다.</p>
<h2>모델 작성</h2>
<p><code>model</code> 패키지를 추가하고 다음과 같은 <code>User.scala</code>를 작성한다.</p>
<pre class="line-numbers"><code class="language-scala">package models
import slick.driver.PostgresDriver.simple._
case class User(
id: String,
name: String,
email: String
)
object Users extends Table[User]("users") {
def id = column[String]("id")
def name = column[String]("name")
def email = column[String]("email")
def * = id ~ name ~ email <> (User, User.unapply _)
}
</code></pre>
<p>이는 일반적인 Slick의 모델 클래스이고 <code>User</code> 모델은 <code>id</code>, <code>name</code>, <code>email</code> 컬럼을 가지고 디비에서는 <code>users</code>라는 테이블명을 가진다. <code>*</code> 메서드는 모델 객체를 매핑해주는 함수인데 여기서는 크게 신경쓰지 않아도 된다. 그리고 PostgreSQL을 사용할 것이므로 Slick의 <code>PostgresDriver</code>를 임포트했다. 이제 <code>app</code> 디렉토리 아래에 <code>Global</code>이라는 파일을 만들어서 다음의 내용을 추가한다.</p>
<pre class="line-numbers"><code class="language-scala">import play.api.db.DB
import play.api.GlobalSettings
import slick.driver.PostgresDriver.simple._
import Database.threadLocalSession
import play.api.Application
import play.api.Play.current
import models.Users
object Global extends GlobalSettings {
override def onStart(app: Application) {
lazy val database = Database.forDataSource(DB.getDataSource())
database .withSession {
Users.ddl.create
}
}
}
</code></pre>
<p>Play 2.0을 이제 막 접해서 자세히는 모르지만 Global은 플레이의 전역 설정을 하는 부분으로 보이고 여기서 어플리케이션을 시작할 때 데이터베이스를 설정하게 된다. 그래서 데이터베이스 연결을 위한 객체를 생성하고 최초 연결시 DDL, 즉 테이블을 정의에 따라 생성한다. 앞에서 <code>User</code> 모델에 대한 객체만 생성하였지만 Slick이 자동으로 DDL 문을 생성하고 여기서 디비에 테이블을 생성한다.</p>
<h2>테스트 작성</h2>
<p>테스트를 작성하기 전에 앞에서 작성했던 <code>models/User.scala</code>파일의 <code>Users</code> 객체에 다음과 같이 <code>add</code>와 <code>findAll</code> 메서드를 추가한다.</p>
<pre class="line-numbers"><code class="language-scala">object Users extends Table[User]("users") {
def id = column[String]("id")
def name = column[String]("name")
def email = column[String]("email")
def * = id ~ name ~ email <> (User, User.unapply _)
def add(user: User)(implicit session: Session) = {
Users.insert(user)
}
def findAll()(implicit session: Session) = {
(for {
user <- Users
} yield user).list
}
}
</code></pre>
<p>슬릭은 아직 파악중이라 자세히 설명하기는 어렵지만 위와 같이 <code>Session</code>이 필요하므로 함수마다 <code>implicit</code> 파라미터가 필요하다. <code>add</code> 함수는 <code>User</code> 모델의 객체를 받아서 <code>users</code>테이블에 추가하고 <code>findAll</code>함수에서는 <code>users</code>테이블의 데이터를 모두 조회해서 <code>List</code>로 변환해서 반환한다. Slick 자체를 설명하는 글은 아니지만 위처럼 디비를 조회할 때 스칼라의 컬렉션을 다루듯이 for comprehension같은 문법으로 SELECT 문을 작성한다.(참고로 여기서 사용한 방법은 Slick의 lifted embedding을 사용한 방법이다.)</p>
<p>테스트 폴더에 <code>models</code> 패키지를 추가하고 <code>UserSpec.scala</code> 파일을 다음의 내용으로 추가한다.</p>
<pre class="line-numbers"><code class="language-scala">package models
import org.scalatest.FunSpec
import org.scalatest.matchers.ShouldMatchers
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
class UserSpec extends FunSpec with ShouldMatchers {
describe("example") {
it("사용자를 추가하고 조회한다") {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
// given
Users.ddl.create
Users.add(new User("outsider", "Outsider", "example@gamil.com"))
// when
val results = Users.findAll
// then
results.size should equal(1)
}
}
}
}
</code></pre>
<p>앞에서 얘기했듯이 <a href="http://www.scalatest.org/">ScalaTest</a>로 작성한 유닛테스트고 사용자를 추가한뒤에 다시 가져오는 테스트케이스이다. 그리고 코드에서 보듯이 어플리케이션은 PostgreSQL을 사용하지만 유닛테스트에서는 <a href="http://www.h2database.com/">H2</a> 데이터베이스를 사용하고 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/7421112702.gif" width="550" height="112" alt="SBT에서 실행한 테스트가 성공한 화면" title="" /></p>
<p>SBT에서 테스트를 실행하면 위와 같이 정상적으로 실행되는 것을 볼 수 있다. 다만 hello-play 템플릿을 생성하면 기본으로 생기는 통합테스트가 몇가지 있는데 어플리케이션을 실행하는 다른 테스트와 충돌이 일어나므로 실행이 제대로 안되면 다른 어플리케이션 테스트는 제거한 뒤에 모델을 테스트해야 한다.(이 문제에 대한 해결은 나중에...)</p>
<h2>Play 2.1의 디비설정</h2>
<p>Play 어플리케이션에서 데이터베이스를 사용하려면 <code>conf/application.conf</code>에서 데이터베이스 설정부분에 주석을 풀고 다음과 같이 수정해 주어야 한다.</p>
<pre class="line-numbers"><code class="language-clike">db.default.driver=org.postgresql.Driver
db.default.url="jdbc:postgresql://localhost:5432/example"
db.default.user=sa
db.default.password="PASSWORD"
</code></pre>
<p>여기서는 PostgreSQL을 사용했지만 다른 데이터베이스를 사용한다면 그에 맞게 설정해 주어야 한다.(물론 각 모델에서 임포트하는 드라이버도 수정해 주어야 한다.)</p>
<p><strong><a href="https://blog.outsider.ne.kr/950?commentInput=true#entry950WriteComment">댓글 쓰기</a></strong></p>