Eclipse에서 테스트 코드 작성을 편하게 하는 설정

Static Import 설정

Organize import (Ctrl + Shift + O) 해도 static import의 *가 풀리지 않도록 하는 설정. Junit4 이후의 assert나, mock library, matcher 등을 사용할 때 편리하다.

  • Windows-Preference-Orgnize importNumber of static imports…​ 를 1로

organize-imports.jpg

Test 메소드와 Import에 대한 Template 설정

Window-Preference-Java-Editors-Template 에 자주 쓰는 템플릿을 추가한다.

각각의 항목의 의미는 다음과 같다

  • name : 축약해서 사용할 문자

  • context : 해당 템플릿을 사용하는 에디터 종류. 여기서는 Java코드를 입력하므로 'Java’로 선택한다.

  • pattern : 템플릿 내용

즉, Java에디터에서 'name' 으로 정한 문자열을 치고 Ctrl + Space를 누르면 'pattern’의 내용이 자동입력 된다.

templates.jpg

Test 메소드 추가

테스트 메소드를 추가할 때 필요한 library import로 메소드 선언을 한꺼번에 해주는 템플릿이다.이름을 spec으로 지정해서 쓰고 있다.

@${testType:newType(org.junit.Test)}

public void ${specDescription}() {
  // given ${cursor}

  // when
  // then
  ${staticImport:importStatic('org.junit.Assert.*', 'org.mockito.BDDMockito.*', 'org.hamcrest.CoreMatchers.*')}
}

Test 라이브러리 import

위의 메소드 추가 template에서 해주는 일 중 import를 하는 부분만 수행해준다. ti라는 이름으로 지정해서 쓰고 있다. (http://wiki.kwonnam.pe.kr/java/junit/staticimports 에서 참조했습니다.)

${is1:importStatic('org.hamcrest.CoreMatchers.*')}${is2:importStatic('org.junit.Assert.*')}${is5:importStatic('org.mockito.Mockito.*')}

Spring test 지원 Annotation 추가

Junit4에서 Spring의 Application context를 올리는 테스트를 할 때 필요한 Annotation과 import 선언을 추가해주는 Template이다. 'springtest’라는 이름으로 지정해서 쓰고 있다.

${:import('org.junit.runner.RunWith','org.springframework.test.context.ContextConfiguration','org.springframework.test.context.junit4.SpringJUnit4ClassRunner')}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext.xml"})

Mockito 관련 템플릿 추가

JUnit에서 Mocito의 annotation을 편하게 쓸 수 있게 해주는 선언이다. 'mockrun’이라는 이름으로 지정해서 쓰고 있다.

${:import('org.mockito.runners.MockitoJUnitRunner')}
@RunWith(MockitoJUnitRunner.class)

Favorite 설정

자주 쓰는 static import 등록할 수 있음. 여기에 등록하면 미리 해당 라이브러리를 static import하지 않아도 Conentnt assist(Ctrl + Space)에서 에서 나오게 됨

  • Windows-Preference-…​…​- Favorites

favorites.jpg

Favorites에 등록을 추천하는 클래스 * org.junit.Assert. * org.hamcrest.CoreMatchers. * org.mockito.Mockito. * org.mockito.BDDMockito. * org.mockto.Matchers.*

자동생성되는 메소드에 UnsupportedOperationException 던지기 설정

자동생성 되어서 아직 구현되지 않은 메소드를 test fail로 인식하게 함 ( http://toby.epril.com/?p=706 참고)

  • Preference – Java – Code Style – Code Templates 안에 Code/Method Body에 아래 코드추가

throws new UnsupportedOperationException();

unsupported-operation-exception.jpg

테스트 코드 짤 때 자주 쓰는 단축키

코드 생성

  • Ctrl+ 1 : Quick fix

  • Ctrl + Shift + O : import 절에 없는 클래스를 추가하거나 정리

  • Ctrl + Shirt + M : import 추가. 클래스 import를 static import로 전환할 수 있음.

테스트 실행

  • Alt + Shift + X, T : JUnit으로 실행

  • Ctrl + F11 : Run (JUnit 실행 가능)

코드 사이를 이동

  • Ctrl + J : 테스트 코드와 실제 코드 사이를 이동 (moreUnit이 설치되어 있을 때)

  • Ctrl + Q : 가장 마지막에 편집한 코드가 있는 곳으로 돌아가기

  • Ctrl + T : 인터페이스에서 구현 클래스 찾을 때

  • Ctrl + Shift + 위아래 방향키 : method 단위로 커서 이동(method 하나만 test 실행할 때 사용 하기 좋음

리팩토링

  • Alt + Shift + R : 리팩토링 이름 바꾸기

  • Alt + Shift + V : 리팩토링 – 이동

  • Alt + Shift + M : 리팩토링 – 메소드 추출

  • Alt + Shift + I : 리팩토링 - 메서드 인라이닝 (추출의 반대)

  • Alt + Shift+ L : 리팩토링 local 변수 추출

More Unit

테스트 코드와 실제코드 사이를 왔다갔다 할 수 있게 하는 Eclipse plugin. TDD의 리듬 유지에 도움이 됨

Maven을 쓰고 있다면 설치후 Window-Preference-More Unit에서 아래 설정을 추가하는 것이 좋다.

  • Directory for testcases : src/test/java

  • Test Suffixes : Test

more-unit.jpg

Eclemma

Eclipse 내에서 Code Coverage 측정. http://blog.benelog.net/2212119 참조

STS를 쓴다면 Dashboard-Extensions에서 선택해서 설치해도 됨

private 메소드를 어떻게 테스트해야 할까요?

private 메소드를 어떻게 테스트해야 할지에 대한 질문은 테스트 코드 작성에 대한 자주 나오는 질문 중에 하나입니다. 이에 대한 답변을 간단히 정리해봅니다.

  • 많은 경우에 private method는 public 메소드에서 extract method 되어서 나온 것이므로 public을 통해서 간접적으로 테스트를 하는 것이 자연스럽습니다.

  • 그래도 private 영역만 따로 테스트를 해야지 더욱 다양한 테스트 케이스를 편하게 작성할 수 있다거나 디버깅이 쉬워진다면 설계를 개선하라는 신호로 해석할만만합니다. private 메소드가 하는 일이 크다는 신호로 해석하고 별도의 클래스로 분리하거나, 하위 클래스에서 상속을 해서 대체할 수 있는 가능성을 고려해서 protected로 해두는 것도 고려해 볼만합니다.

그럼에도 불구하고 private 메서드를 테스트해야한다면

설계 개선 등을 바로 할 수 상황이라 당장 private 메서드를 테스트해야할때 쓸수 있는 방법을 이어서 소개합니다.

  • 테스트만을 해당 메소드를 package private(default 접근자)나 protected로 바꾸어서 테스트해볼 수도 있습니다. 일반적으로 테스트를 위해서 production 코드의 접근 범위를 넓히는 것은 클래스의 노출 범위를 커지게 하므로 바람직하지 않을 수도 있습니다.

  • Reflection을 이용하면 강제적으로 private 메소드를 호출할 수 있습니다. 다만 이렇게 하면 메소드이름 부분이 String값으로 넘겨지게 되므로, compile time에 메소드명의 오타가 검증되지 못하고, refactoring으로 메소드명을 바꾸어도 자동으로 String으로 적힌 부분은 바뀌지 않는 단점이 있습니다. 부작용을 감수하고서라도 쓰겠다고 각오가 된 곳에 제한적으로 사용하기를 권장드립니다.

Reflection으로 private 메서드를 호출하는 방법들은 아래와 같습니다.

(1) PowerMock에서 Whitebox.invokeMethod(..) 메소드 활용

(3) 직접 Reflection 활용

package edu.tdd;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Test;

public class ReflectionCallUtilsTest {

    @Test
    public void testCallPrivate() throws SecurityException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        UnderTest ut = new UnderTest();
        String name = "jsh";
        String address = "서울시 마포구";
        invoke(ut, "print",name, address);
        assertThat(ut.isCalled(),is(true));
    }

    @Test
    public void testCallWithPrimitiveType() throws SecurityException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        UnderTest ut = new UnderTest();
        String name = "jsh";
        int age = 35;
        boolean printed = (Boolean)invoke(ut, "print", new Class<?>[]\{String.class, int.class}, name,age);
        assertThat(printed,is(true));
        assertThat(ut.isCalled(),is(true));
    }
    private Object invoke(Object ut, String methodName, Class<?>[] argTypes, Object ... args) throws SecurityException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = ut.getClass().getDeclaredMethod(methodName, argTypes);
        method.setAccessible(true);
        return method.invoke(ut, args);
    }

    private Object invoke(Object ut, String methodName, Object ... args) throws SecurityException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        int argSize = args.length;
        Class<?>[] argTypes = new Class<?>[argSize];
        for(int i=0;i< argSize;i++){
            argTypes[i] = args[i].getClass();
        }
        return invoke(ut,methodName, argTypes, args);
    }

    static class UnderTest {
        private boolean called = false;
        public boolean isCalled(){
            return called;
        }
        private void print(String name, String address) {
            System.out.println(name);
            System.out.println(address);
            called = true;
        }
        private boolean print(String name, int age){
            System.out.println(name);
            System.out.println(age);
            called = true;
            return true;
        }
    }
}

필요하다면 test 코드 안에서 쓰이고 있는 invoke메소드를 따로 Util 클래스로 분리할 수도 있습니다.

위의 코드에서 Object .. args 넘어가는 부분이 primitive type이 포함되면 Object[] 로 바뀌는 과정에서 Wrapper class로 바뀌는 auto-boxing이 발생하게 됩니다. 그래서 매개변수에 primitive type이 있을 때는 invoke(Object ut, String methodName, Object …​ args) 사용하면 NoSuchMethodException이 발생하게 됩니다. 그럴 때는 type을 정확히 명시해 주는 invoke(Object ut, String methodName, Class<?>[] argTypes, Object …​ args) 을 사용하면 됩니다.

참고자료

로또 번호 생성기 파이썬으로 만들어보기

오래 전에 블로그에 Java를 이용한 로또 번호 생성기(http://blog.benelog.net/1646013 )코드를 올린 적이 있었습니다.

갑자기 생각이 나서, 이번에는 Python으로 코딩해봤습니다.

문제

  • 1부터 45까지의 숫자 중에 6개를 뽑는다.6개의 값이 다 달라야 한다.

  • 출력시 작은 숫자부터 순서대로 출력

풀이

import random
balls = range(1,46)
random.shuffle(balls)
print sorted(balls[0:6])