What is Infinitest?

Infinitest is a continuous JUnit test runner designed to facilitate Test Driven Development. It
helps you get the most out of TDD by reducing your feedback cycle from minutes to seconds.

Infinitest 는 테스트기반의 개발을 하기 위해서 를 용이하게 하기 위해서 지속적으로 JUnit test를 실행하게 설계 되었다. 수분에서 수초까지 피드백 주기를 줄여주는 TDD로 부터 가장 중요한 것을 얻을수 있도록 해준다.

Using Infinitest

When Infinitest first launches, it runs any test that extends from
junit.framework.TestCase or has methods that have the org.junit.Test
annotation. After that, Infinitest will run the test again if it, or one of it's dependencies, changes.
Changes to direct and indirect dependencies will trigger a test run. Only the minimal set of tests
which depend on those changed files will be run.

첫번째로 Infinitest 어플리케이션이 실행될때 junit.framework.TestCase를 상속한 클래스나 org.junit.Test annotation을 찾아서 모두 실행을 한다. 그 이후에는 테스트 또는 그것에 의존하는 것들이 변경되면 테스트를 다시 실행을 한다. 직접,간접적으로 의존하는 것들의 변경되는 경우 테스트를 자동으로 실행을 한다.  변경된 파일에 의존하는 테스트들의 최소한의 셋만 실행이 될 것이다.

Test Driven Development
Infinitest is made with the Test Driven Developer in mind. You'll find yourself keeping your
hands on the keyboard, in the flow, as Infinitest runs your tests for you with each change.
Infinitest will act as a virtual pair programmer...keeping your honest and helping you take small
steps.

Refactoring
Refactoring is easy with Infinitest. The bar should always stay green. If the bar turns red, you
made a mistake! Back up and try again.
Refreshing The Graph
If you find yourself needing to run all of your tests from Infinitest, you can rebuild the
dependency graph by hitting the reload button. This will rebuild the dependency graph and
re-run all of your tests.

Stopping the tests
If you need to temporarily stop Infinitest from running your tests, you can hit the stop
button. This will prevent Infinitest from running tests after changes occur. When you resume
running tests (by hitting the button again), the entire test suite will be run.

Configuring Infinitest

Filtering Tests

You may have tests in your classpath that you don't want Infinitest to run. These tests can be
filtered out by creating a file in the working directory of your project named
infinitest.filters. It should contain one regular expression per line. Any class names
(not file names) that match any regular expressions in that file will not be run. For example:
org\.myproject\.acceptance\..*
.*\$.*
will filter out all the classes in the com.myproject.acceptance package, and any inner
classes (which always contain a $).

ex) 내가 진행하는 프로젝트인 경우는 서버환경에 따라서 테스트가 실패할수 있는 테스트는 IntegrationTest를 suffix에 붙여서 사용한다. 그래서 다음과 같이 파일을 만들어서 넣어주면 Infinitest 어플리케이션이 알아서 exclude를 하고 실행을 한다.

* 파일 이름 : infinitest.filters
* 파일 내용
com\.nhn\.community\..*IgnoreTest
com\.nhn\.community\..*IntegrationTest
com\.naver\.blog\..*IgnoreTest
com\.naver\.blog\..*IntegrationTest
.*\$.*

간단후기~

모든 테스트가 실행될때 보이는 하단에 progressive bar가 중독성있네요.

Infinitest 사용을 해보니 편하고 재미도 있습니다. 코드 개발을 하다가 보면, 테스트 돌리는 것도 귀찬은 작업중의 하나인데, 자동(trigger)으로 해주니 좋습니다요!

테스트 중독

2007/10/19 10:04
나에게는 오지 않을 줄 알았는데, 현재의 증세를 보아하니 테스트 중독에 걸린거 같다.

출근해서 컵을 씻고 자리에 앉아서 모니터를 봤는데...

아래 GNB를 보고, "테스트 성공했구나!"라는 생각을 하고야 말았다.

생각해보면, 여러가지에(술,담배,나태 등등) 빠져들어서 안 좋은 결과를 많이 가져왔지만, 처음으로 나에게 득(?)이 되는 것에 중독이 된거 같다,



User inserted image


PS.
사내에 돌아다니다가 모니터를 봤을 때 푸른색을 보면 마음이 편해지고, 실패한 것을 보면 안타깝다. 테스트가 실패한 자리에 있는 사람은 다시 앉으면 테스트 성공을 위해서 계속 일을 할게 남아 있기때문이다.
언제부턴가 테스트관련 서적과 refactoring 서적을 많이 읽고 있는 나 자신을 발견한다. 개인적으로 관심이 많기도 하고, 개발자로 먹고살려다 보니 반드시 필요하기때문이다.

테스트를 만드는 것과 refactoring을 하는 것은 많은 관련이 있다. refactoring을 하려면, 테스트를 해서 정상적으로 작동을 하는지에 대한 체크가 필요하다. 부지런한 사람(?)은 아마 코드를 고치고, 사용자 테스트로 직접 어플리케이션에 올려서 테스트를 한다. 반복된 행동을 하면서 짜증만 늘어가는 부지런한 사람이 되는 것이다. 겉으로 보기에는 부지런해보일지 모르지만, 속으로는 썩고 있는 성겨파탄자가 될수도 있다. 그래서, 테스트의 자동화는 매우 중요하다.

코드나 설계한 대해서 실행(Runtime)을 통해서 검증을 하는 것 만큼 확실한 방법은 없다. 나는 내가 할수 있는 한 빠르게 실행을 하려고 온갖 정성을 기울인다. 우리가 refactoring을 넘어서서 신규 코드를 작성을 할때도 가장 빠르게 실행환경을 갖출 수 있는 방법은 단위 테스트를 만드는 것이다.

이미 테스트의 중요성은 이미 여러군데에서 다루었기때문에 생략을 하고, 테스트를 잘 작성을 하려면 어떻게 해야 하는지 고려해보자.

단위 테스트를 만들다보면, 잘 안된다. 왜 잘 안될까? 코드를 테스트하기 어렵게 만들기도 하고, 테스트를 고려하지 않고, 작성을 하기때문이다. 특히 여러가지 환경이나, 책임들이 얽혀있는 경우는 더 어렵다. 그래서, 일부 선배 개발자들은 test first를 추천을 한다. 테스트가 가능한 시점부터 코드작성이 완료될 때까지 테스트가 가능한 상태를 유지하기에는 아주 좋은 방법이다. 코드추가후 테스트를 실행하는 것에 대한 interval만 조절을 잘하면, 그다지 많은 시간이 걸리지도 않는다.

실무 개발을 진행중에 테스트하면서 느끼는 어려움, 경험을 좀더 빠르게 알고, 해결방법을 찾고 싶다면 아래 책을 추천한다. 내가 겪었던 고통들을 이미 저자는 몇 가지 용어로 정의를 해서 도움이 될만한 이야기를 해준다.

Xunit Test Patterns: Refactoring Test Code

Gerard Meszaros / Addison-Wesley Professional

조금더 시야를 넓히고 싶다면, 아래 책도 같이 읽어보면 좋다.

나도 테스트를 작성하면서 내가 만든 것을 볼때 불편함을 느끼는 경우가 많지만, 더 관심을 가질 시간을 가지고, 사랑해준다면, 테스트뿐만 아니라 자신의 코드가 점차 아름다워지는 것을 느낄 수 있을 것이다.

Working Effectively With Legacy Code (Paperback)

Feathers, Michael C. / Prentice Hall

우리의 열정적인 스터디원들이 포스팅을 하고 있어서 트랙백을 걸었다. 앞으로 3-4주정도 남았는데, 서로에게 많은 도움이 되었으면 한다.
 
http://javajigi.tistory.com/86
http://blog.naver.com/django44/40043308830
minimalism은 TDD의 철학과도 직결이 된다. 

일단  버그가 생기지 않을 정도로 아주 단순하게 구현을 한 다음 복잡한 기능들과 로직을 추가를 하면서, 계속 테스트를 하는 것이다.

단순한 구현은 버그도 없을 뿐더러 빠르게 테스트를  통과한다.

테스트 통과를 보면서 코드를 붙여나가는 것은 그만큼 안전한 방법이다.

기능 명세서, 설계 구현 등에 대하여 단순성을 강조해야 한다. 개발자들은 대부분 복잡한 것을 좋아한다. 그들은 문제를 간소화하기보다는 더 복잡하게 만드는 경향이 있다. 하지만, 프로젝트 성공의 열쇠는 프로젝트를 한층 단순화하는데 있다. 프로젝트의 목표를 달성하기 위한 지름길은 복잡성을 제거하는 것이다. P.84

더는 덧붙일 것이 없을 때 끝내는 것이 아니라, 더는 뺄것이 없을 때 끝낸다.
- 프랑스 작가 볼테르(Voltaire)

개발자들은 대부분 개발을 해서 돈을 버는 사람은 아니다.

MS는 제품을 출시를 했을때 ship-it이라는 상금을 받는다. MS가 소프트웨어를 개발해서 수익을 내는 것이 아니라 소프트웨어를 출시함으로써 돈을 번다는 사실을 개발자들에게 강조하는 것이다.

릴리즈는 하는 것이 중요하다는 이야기이다.

소프트웨어 프로젝트 생존 전략

스티브 맥코넬 / 인사이트

추상 클래스에 행위에 대해서 테스트가 필요하다면, 방법이 있다.  EP님이 블로그 덧글을 남긴 것을 보고 좀더 구체적으로 직접 물어봐서 좋은 가이드를 얻을 수가 있었다. 추상클래스 단위 테스트 만들기를  보고 이어서 보면 더 이해가 쉬울 것이다.

대부분 hook메소드와 같은 콜백 메소드가 부모의 알고리즘(비즈니스 로직)에 따라서 정상적으로 잘 호출이 되는지를 체크할 수 있는 테스트이다.

아이디어는 이렇다. EP님의 덧글을 참조하자.

여러 개의 메서드를 구현할 것을 기대하고 이를 정해진 순서대로 실행하는 추상 클래스를 테스트할 때에는 로그 찍는 방법도 좋습니다. 다만 로그를 사람이 눈으로 확인하는 것이 아니라 로그를 남기고 기대하는 로그를 단언하면 됩니다. - EP

구현은 이렇게 했다. 메소드가 호출되는 것을 Stack에 로그로 담아두고, 테스트를 원하는 메소드를 호출후에 원하는 메소드들이 호출이 되고, 원하는 상태로 변경이 되어있는지에 대한 테스트를 하는 것이다. 호출된 로그는 Stack에 담아서 pop을 하면서 비교를 하였다.

이정도의 테스트이면,
상태와 행위를 둘다 테스트를 해서 좀더 견고하고 원하는 테스트를 할수 있을 것이다.
public class AbstractBlogTemplateClientTest extends TestCase {
final Stack<String> methodStack = new Stack<String>();
 
 protected void setUp() throws Exception {
  super.setUp();
  leverageDAO = createMock(LeverageDAO.class);
  abstractBlogTemplateClient = new AbstractBlogTemplateClient(){
   @Override
   protected SourceType getSourceType() {
    methodStack.push("getSourceType");
    return SourceType.RECIPE;
   }
   @Override
   protected Leverage getLeverage(Post post) {
    getSourceType();
    methodStack.push("getLeverage");
    return leverage;
   }
   @Override
   protected void resultSyncCallback(Post post, RemoteResult remoteResult) throws Exception {
    methodStack.push("resultSyncCallback");
   }
   @Override
   protected RemoteResult insert(Post post) throws Exception {
    methodStack.push("insert");
    return remoteResult;
   }
   @Override
   protected RemoteResult update(Post post) throws Exception {
    methodStack.push("update");
    return remoteResult;
   }
   @Override
   protected RemoteResult delete(Post post) throws Exception {
    methodStack.push("delete");
    return remoteResult;
   }
  };
  abstractBlogTemplateClient.setLeverageDAO(leverageDAO);
 }
 @Override
 protected void tearDown() throws Exception {
  super.tearDown();
  methodStack.clear();
 }
 public void test_insertToOuter() throws Exception {
  leverageDAO.insertLeverage(leverage);
 
  replay(leverageDAO);
  assertTrue(post.getLeverageSet().size() == 0);
 
  abstractBlogTemplateClient.insertToOuter(post);
 
  verify(leverageDAO);
 
  // state test
  assertTrue(post.getTemplateSync());
  assertFalse(post.getTemplateCopyYn());
  assertNotNull(post.getTemplateSyncdate());
  assertTrue(post.getLeverageSet().size() == 1);
 
  // method call test : behavior
  assertEquals(4, methodStack.size());
  assertEquals("getLeverage", methodStack.pop());
  assertEquals("getSourceType", methodStack.pop());
  assertEquals("resultSyncCallback", methodStack.pop());
  assertEquals("insert", methodStack.pop());
 }
}

메소드가 호출되는 것을 체크할 뿐만 아니라 호출된 순서까지도 체크를 할수가 있다.


easymock은 일단 학습을 하고 나면, 사용하기가 쉽다.
하지만, 처음에 접할 때 조금 난해한 API사용 방법으로 어려움을 겪는 사람들이 많다.
jdk5.0을 사용을 하면, static import가 가능해져서 그나마 과거 easymock보다는 사용하기가 쉽다.

아래 static 메소드들은 다음과 같은 의미가 있다.

createMock : 인터페이스나 클래스의 mock객체를 생성한다.

replay : mock객체의 인터페이스를 호출하는 것을 기록한 것을 다시 처음으로 돌린다.

verify : 기록한 내용의 behavior(행위)들을 다 실행을 했는지 체크를 한다.

reset : createMock으로 생성한 mock을 초기화 한다.

위와 같은 실행을 하는 동안 중간에 assert 메소드들을 넣어서 비즈니스 로직을 확인하면 되겠다.

참고로, easymock extension은 인터페이스가 아니고, Concrete클래스를 mock으로 만들수 있다.

easymock, jmock은 항상 경쟁을 하면서 API 편의성과 refactoring이 용이하다는 서로 간의 장단점떄문에 많은 비교문서가 있지만, 일단 API를 습득을 하고 나면, refactoring이 가능한 easymock을 사용하는 것이 더 편리하다.

jMock도 조만간 refactoring을 쉽게 할수 있도록 구조를 변경한다고 하는데 언제쯤 나올지는 모르겠다.

JUnit Recipes

2007/01/07 14:55
테스트의 중요성은 아무리 말해도 지나치지 않는다.

각자 중요하다고 생각하면서 테스트를 작성을 해봤겠지만, 이게 정말 안심하고 믿을수 있는 케이스인가에 대한 고민은 또 하나의 두려움에 봉착을 하게 된다.

작성한 코드를 refactoring을 통해서 디자인이나 알고리즘을 수정하고 난뒤 테스트 케이스만 실행해서 파란불이 나오면 그 코드를 어느정도 신뢰를 할수 있는것인가에 대한 문제는 케이스를 만들때의 또 하나의 어려움이다.

그 어려움을 극복하고자 JUnit Recipes를 보았다. 책이 두꺼워서 이 책만 보면 많은 영감을 얻을수 있고, 도움이 되겠지라는 생각에 처음부터 읽다가 책이 너무 방대해서 요령껏 다 읽었다. 하지만, 이 책은 너무 다양하게 다루어서 지금 내가 필요없는 부분도 많이 다루었다.

예를 들면 EJB테스트 같은 것은 정말 대충봤다. 테스트가 가능하긴 하구나라는 짧은 생각을 넘어갔다.

난 POJO에 대한 테스트를 더 원하고 있었기때문에 그것 위주로 보았고, 몇 개의 chapter에서는 많은 도움을 받았다. 아마 저자도 그런 생각으로 이 책을 쓰지 않았을까?

책을 보며서 느낀점은 테스트를 하고자 마음만 먹는다면 못 할것도 없다는 것과 테스트가 안된다고 얘기하는 것은 자기 코드를 잘못 짰다고 얘기하는 것 밖에 안되는 것이라는 생각이 들었다.

단위 테스트의 정말 중요한 관점은 깨지지 않아야 하고, 어디서나 실행이 가능해야 하고, 런타임시간이 짧아야 한다. 케이스의 나눌때의 요구사항이 명확해야 더 완벽한 케이스를 만들수가 있다.

Test Code와 Production Code는 하나여야 한다. 테스트할 수 있는 코드를 작성하다가 보면, 좀더 좋은 디자인으로 OOP에 가까운 개발을 할수가 있을 것이다.

더 신뢰성 있는 코드를 작성하기 위해서 이 책으로 부족한 부분을 채우는 것이 어떨까?


 
A test is not a unit test
  • It talk to a database.
  • It communicates across a network.
  • It touches the file system.
  • You have to do special things to your environment.


Unit Test와 Higher-Level Tesing하고 구분을 하자.

refactoring의 시작은 Great한 TestCase를 만드는 것부터...

TestCase부터 refactoring을 해야겠다...-.-


Working Effectively With Legacy Code

Michael Feathers / Prentice Hall





- the Inversion of Control and dependency injection design patterns
- unit testing and TDD
- O/R (object/relational) mapping
- post struts 1.x Web technologies such as JavaServer Faces, Spring Model-View-Controller and Tapestry, and value-add Web technologies such as Apache Beehive
- the rich client space


1. Johnson also told developers to hone their skills that bring leverage, such as frameworks and methodologies, and to "look beyond Java" and work on communications skills and business skills.

2. "One key element to success is framework-oriented development," he said. "You choose a set of technologies, decide which is appropriate and you use it. This is a huge gain for developers. This is something that means the death of the in-house framework."

3. Johnson said TDD is becoming more widespread

Open-Source Leader Highlights Technologies for Developers to Watch
http://www.eweek.com/article2/0,1759,1773154,00.asp

개발자가 알아야 할 기술
http://younghoe.info/71

영회님에 블로그 포스트에 자세한 내용이 나왔는데, 공감가는 부분은 이리로 조금 데려왔다.
자세한 내용은 그쪽에서 보시길!! 영회님의 수고로 한글판도 존재한다..^^
spring을 사용해서 application을 구현하는 사람이라면, 테스트하기 좋은 구조로 코딩을 할수 있다는 것을 알것이다. 하지만, test코드를 작성할때 어쩔수 없는 dependency lookup과정이나 wiring을 코드 안에 넣어서 테스트를 하는 사람이 많다. spring에서 제공하는 API를 이용한다고 가정을 하면, DI를 아래와 같이 손쉽게 할수 있다.

org.springframework.test.AbstractDependencyInjectionSpringContextTests를 이용해서 TestCase를 작성하면 테스트케이스 안에서도 lookup이 아닌 DI를 이용해서 테스트를 작성할 수가 있다.

DAO를 테스트할때 실제로 DB에 값이 들어가서 단위테스트를 작성하는데 어려움이 있다. 아래와 같은 API를 사용하면 테스트를 마친후 모든 데이터를 rollback을 해준다.
org.springframework.test.AbstractTransactionalSpringContextTests

TestCase에 AbstractDependencyInjectionSpringContextTests나 AbstractTransactionalSpringContextTests을 상속을 받으면 다음과 같은 부분을 오버라이딩해서 XML설정파일의 위치를 적어주면 실행시에 자동으로 DI가 된다.

@Override
protected String[] getConfigLocations() {
   return new String[] { "classpath:test/applicationContext-NOTECOMMON.xml","classpath:test/applicationContext-NOTEBBS.xml"};
}


하지만, proxy나 decorator패턴과 같이 같은 인터페이스를 구현해야 하는 클래스가 있다면, 설정을 바꾸어 줘야 한다. config에 상관없이 default값이 type으로 되어 있어서, 아래와 같이 수동으로 wiring타입을 정의를 해주면 원하는 DI를 할수가 있다. 계속 오리지날 파일이 잘못된지 알고 삽질을 했슴...^^

@Override
protected String[] getConfigLocations() {
this.setAutowireMode(AUTOWIRE_BY_NAME);
   return new String[] { "classpath:test/applicationContext-NOTECOMMON.xml","classpath:test/applicationContext-NOTEBBS.xml"};
}

인터페이스 기반으로 코드를 작성하면 테스트가 정말 쉬워진다. 테스트를 많이 한 만큼 더 좋은 품질의 코드를 작성할 수 있는 것은 두말하면 잔소리다.

7월 31일 추가
Spring이 제공하는 테스트 유틸리티 클래스
http://blog.empas.com/ahnyounghoe/15000004

BLOG main image
OOP and Java by ologist

공지사항

카테고리

All (646)
private!! (106)
WEB & IT (138)
Developer (399)