반응형
반응형

본 포스트는 JUnit에 대한 기초적인 지식이 있다는 가정하에 작성되었습니다.

 

spring test에서 지원하는 MockMVC의 경우,

테스트를 돌릴 때마다, application Context를 로딩하는 시간때문에 사전 대기 시간이 걸린다는 게 좀 아쉬웠습니다.

(그래봐야 몇 초 안되지만... 우리들은 한국인)

 

그러던 중, spring mvc구조에서 매우 쉽게 도입할 수 있는 Mockito라는 녀석을 찾게 되었습니다.

 

매뉴얼도 한국어군요.. 매우 훌륭합니다. 

https://code.google.com/p/mockito/wiki/MockitoFeaturesInKorean

 

JUnit과의 연동도 매우 훌륭합니다. (거의 할게 없을 정도..)

@RunWith(MockitoJUnitRunner.class) 이거 하나만 테스트가 작성될 클래스에 붙여주면 됩니다.

 

Mocking하는 과정도 매우 간단합니다.

만약에 LoginDao라는 객체를 Bean으로 만들어서 쓰는 구조라면..

아래와 같이 @Mock만 붙여주면 끝납니다.

※ Mocking : 예를들어 DB통신하는 객체가 있다면, DB통신 없이도 동일하게 행동하도록 다른 객체로 바꿈. 

 

@Mock  

LoginDao loginDao;

 

Mocking하려는 객체가 참조하는 다른 Bean이 있는경우..

만약에 LoginService가 LoginDao를 포함하는 구조라고 합시다.

아래와 같이 써주면, 포함된 Bean들도 모두 Mock객체로 만들어줍니다.

 

@InjectMocks

LoginService loginService;

 

여기까지 Mock객체 생성법이었구요.

 

이제 Stubbing에 대해 알아보도록 하죠.

Mock객체가 실제로 DB통신등 네트워크 통신을 하지 않고 메모리상에서만 돌아가기 때문에,  

대규모 프로젝트에서는 이게 좀 귀찮은 작업이 될 수 있습니다.

 

loginDao의 login(int a, String b)이라는 메소드가 있다고 치고, 리턴형이 int라고 가정합시다.

그리고 해당 메소드에서 무조건 10이라는 값이 나오게 하고 싶습니다.

그럴 경우, 아래와 같이 작성할 수 있습니다. 

 

when(loginDao.login(anyInt(), anyString())).thenReturn(10);

 - argument에 정확한 값을 넣어도 되지만, 보통은 어떤 값이 들어올지 모르기 때문에, 아무값이나~ 라는 anyInt(), anyString()등의 기능도 지원을 해줍니다.  

 

이후에 JUnit에서 지원하는 Assert관련 메소드등을 이용해서 값 검증을 하시고,

Mockito에서 지원하는 verify 관련 기능들을 이용하시면, 더더욱 강력한 테스트를 만들 수 있습니다.

반응형
,
반응형

자체적으로 돌아가는 클라이언트 테스트 (시나리오 테스트)를 만들기 위해,

JUnit으로 Flow를 쭉 짜놓고, 잘 돌아가는 것을 확인하였는데,

이것을 여러번 돌리고 싶었다.

 

처음에는 for(int i=0; i<count; ++i) 를 이용해서 돌렸는데..  

로직이 테스트 한개에 들어가 있는 것이니.. 

예상대로 count만큼 돌았을 때에 대한 최종 응답시간만 확인을 할 수 있었다.

 

그러던 중 JUnit에서 제공하는 기능인 Parameterized에 대해 알게 되었다.

 

적용방법은 매우 간단하다.

 

 

1. 테스트가 작성된 클래스 상단에 @RunWith(Parameterized.class)를 붙여준다.

 

2. 파라미터가 세팅될 Collection을 구현한다. 


@Parameters  
    public static Collection<Object[]> generateParams() {  
         List<Object[]> params = new ArrayList<Object[]>();  
         for(int i=0; i<TEST_BOT_COUNT; ++i) {  
              params.add(new Object[] {i});  
         }  
         return params;  
    }

 

 

3. 돌려보면, 지정한 개수만큼 JUnit Report가 발생한다.

반응형
,
반응형

Mockito로는 private Method나 field값 변경을 하려면, reflection등을 이용하여 구현을 해야 하고,

제대로 동작하는 것을 만들기 위해서는 Mockito를 뜯어서 내부 구조까지 파악을 해야한다.

 

처음에 조금 시도해보다가 때려치고..

PowerMock이란 것이 Mockito API를 지원해준다는 것을 확인하고, 도입을 해보았다.

https://code.google.com/p/powermock/

 

PowerMock은 private field / method의 mock을 static method등을 이용하여, 편리하게 만들 수 있도록 제공해준다.

또한, 가끔 Mockito의 @InjectMocks로 mocking이 되지 않는 객체들도, 이걸 이용해서 만들면 어느정도 해결이 된다.

 

대부분의 기능은 Mockito로 해결이 가능하며, private field에 값을 세팅하는 예제만 작성해본다. 

 

A라는 클래스에 abc라는 private 객체에 값을 세팅하는 경우 아래와 같이 작성 

 

MemberModifier.field(A.class, "abc").set(aObject, "값");

 

기존에 private method를 다 public으로 바꾸거나, private field의 경우 setter, getter등을 이용하였는데,

이걸 사용하고나서, 다 없애버렸다. 

반응형
,
반응형

RestTemplate의 경우 Mockito기준으로 @Mock RestTemplate restTemplate; 만으로 mock객체가 생성되지 않고,

계속 NullPointerException이 나오는 현상을 겪을 수 있다.

 

이유는, 내부적으로 messageConverter를 참조하고 있고, 의존적이기 때문이니까

messageConverter를 직접 생성하여 mock 객체에 옵션으로 넣어주면 된다.

 

매우 간단한거지만... 이거 가지고 전날에 3~4시간 삽질을 했기 때문에, 글로 남겨둔다.

(다음날 아침에 오자마자 10분만에 해결;; 역시 사람은 아침에 일을 해야 하는듯..) 

반응형
,
반응형

JUnit 4.7 이전 버전에서는 Exception Test를 할 때 @Test(expected=Exception.class) 와 같은 형식으로 테스트를 통과 시킨다.

하지만, 이 방법은 Exception이 발생하는지 여부만 확인이 가능하고, Exception 내부에 들어있는 값들은 검증하지 못한다.


그래서, JUnit 4.7 이후 버전부터 @Rule이라는 기능을 지원을 하는데,

기본적으로 아래와 같이 선언 한 후, 

@Rule

ExpectedException expectedException;



각 Unit Test에서 expect, expectMessage를 이용하여 검증을 한다.

expectedException.expect(Exception.class);

expectedException.expectMessage("exception message");


이 방법도 어떤 Exception인지, Exception안에 들어가 있는 Message만 확인이 가능하기 때문에,

Customizing한 Exception에 들어있는 값들은 확인할 수가 없다.


이런 경우 MethodRule Interface를 상속받아서, 직접 Rule을 작성해주고 ExpectedException 대신에 새롭게 만든 Rule을 넣어주면

원하는대로 체크가 가능하다.


기존에 정의한 Exception이 아래와 같은 형태라고 가정하고, errorCode에 들어있는 값을 체크하고 싶다.


public class GameException extends RuntimeException {

    private static final long serialVersionUID = 7214480405095839046L;

    private int errorCode = -1;
    public GameException(int errorCode) {
        this.errorCode = errorCode;
    }
    
    public int getErrorCode() {
        return this.errorCode;
    }
}


Message보다는 ErrorCode를 체크해야 겠기에, 기존 ExpectedException에 있는 코드를 그대로 가져와서,

expectMessage는 없애고, expectedErrorCode 구문을 추가하였다.


public class GameExceptionRule implements MethodRule {
    /**
     * @return a Rule that expects no exception to be thrown
     * (identical to behavior without this Rule)
     */
    public static GameExceptionRule none() {
        return new GameExceptionRule();
    }

    private Matcher<Object> fMatcher= null;

    private GameExceptionRule() {
        
    }
    
    @Override
    public Statement apply(Statement base, FrameworkMethod method, Object target) {
        return new GameExceptionStatement(base);
    }

    /**
     * Adds {@code matcher} to the list of requirements for any thrown exception.
     */
    // Should be able to remove this suppression in some brave new hamcrest world.
    @SuppressWarnings("unchecked")
    public void expect(Matcher<?> matcher) {
        if (fMatcher == null)
            fMatcher= (Matcher<Object>) matcher;
        else
            fMatcher= both(fMatcher).and((Matcher<? super Object>) matcher);
    }

    /**
     * Adds to the list of requirements for any thrown exception that it
     * should be an instance of {@code type}
     */
    public void expect(Class<? extends Throwable> type) {
        expect(instanceOf(type));
    }
    
    public void expectErrorCode(int errorCode) {
        expect(hasErrorCode(is(errorCode)));
    }

    private class GameExceptionStatement extends Statement {
        private final Statement fNext;

        public GameExceptionStatement(Statement base) {
            fNext= base;
        }

        @Override
        public void evaluate() throws Throwable {
            try {
                fNext.evaluate();
            } catch (Throwable e) {
                if (fMatcher == null)
                    throw e;
                Assert.assertThat(e, fMatcher);
                return;
            }
            if (fMatcher != null)
                throw new AssertionError("Expected test to throw "
                        + StringDescription.toString(fMatcher));
        }
    }

    private Matcher<Throwable> hasErrorCode(final Matcher<Integer> matcher) {
        return new TypeSafeMatcher<Throwable>() {
            public void describeTo(Description description) {
                description.appendText("exception with errorCode ");
                description.appendDescriptionOf(matcher);
            }
        
            @Override
            public boolean matchesSafely(Throwable item) {
                return matcher.matches(((GameException)item).getErrorCode());
            }
        };
    }



유닛 테스트에서의 사용은 아래와 같으며, 아래 작성된 테스트는 gameService의 test메소드에서

GameException이 발생하고, 에러코드가 100이 나와야 성공하는 테스트이다.


@Rule
GameExceptionRule gameExceptionRule = GameExceptionRule.none();

@Test
public void ruleTest() {
        // 반드시 로직 수행 전에 명시해 주어야한다.
        gameExceptionRule.expect(GameException.class);
       gameExceptionRule.expectErrorCode(100);

        // 로직 수행
        gameService.test();
}



이로써, 사용자가 정의한 Exception의 경우에도 Exception 내부의 값을 체크할 수 있게 되었다.

반응형

'개발 > Testing' 카테고리의 다른 글

[Mockito] Spring MVC Mockito  (0) 2016.06.11
[JUnit] Parameterized  (0) 2016.06.11
[PowerMock] private mock  (0) 2016.06.11
[Mockito] Spring RestTemplate Mocking  (0) 2016.06.11
[BDD] JBeHave를 이용한 Behavior Driven Development  (0) 2016.06.11
,
반응형

Mockito 를 이용해서 TDD 환경을 잡아놓고,

Ecl Emma 를 설치하여 로직의 coverage를 맞추는 방식으로 개발을 진행하고 있었다.


그런데, remote환경에 RDB가 있다보니, 예상치 못한 버그들이 발생을 하여,

실제로 세팅된 remote환경에 있는 API를 검증해야되겠다고 생각을 하던중, JBeHave라는 BDD Framework를 발견하게 되었다.

(http://jbehave.org/)


BDD는 기본적으로 3단계를 가지게 된다.

Given : 실제 행동 전 준비단계

When : 행동

Then : 검증


본인의 경우 restful API서버에 대한 테스팅을 해야되기 때문에,

Given 단계에서 파라미터 세팅을 하고, When에서 remote API call, Then에서는 API에서 받은 결과값에 대한 검증을 하였다.


간단하게 세팅 방법을 정리해본다.

IntelliJ 에서는 JBehave 플러그인을 제공하기 때문에, 좀 더 편리하게 세팅을 할 수 있었다.

메이븐 의존성 주입을 해주고, 아래 단계에 맞게 세팅한다. (현재 최신 stable버전은 3.9.5)

<dependency>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-core</artifactId>
<version>3.9.5</version>
</dependency>


1. story 파일을 만든다. 


2. Given, When, Then 시나리오를 작성한다.

ex)

Given I set parameter a is 1, b is 2, c is 3
When I do call home/index
Then I should get successful home/index response

3. story파일에 선언한대로 Annotation을 붙여서, java 코드로 구현한다. (story파일은 test/resources 경로에 두어야 runner가 잘 찾는 것 같다.)

@Given("I set parameter a is $a, b is $b, c is $c")
public void setHomeIndexParams(@Named("a") int a,
@Named("b") int b,
@Named("c") int c) {
}

@When("I do call $path")
public void reqApiCall(String path) throws IOException {

}
@Then("I should get successful home/index response")
public void resHomeIndex() {

}


4. Runner 작성

@RunWith(AnnotatedEmbedderRunner.class)
@Configure(storyLoader = JBehaveTestRunner.StoryLoader.class,
storyReporterBuilder = JBehaveTestRunner.ReportBuilder.class)
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = false,
ignoreFailureInView = true, verboseFailures = true)
@UsingSteps(instances = { NormalGameFlow.class }) // 3번에서 작성한 클래스명 넣어준다.
public class JBehaveTestRunner extends InjectableEmbedder {

@Test
public void run() {
List<String> storyPaths = new StoryFinder().findPaths(codeLocationFromClass(this.getClass()), "**/*.story", "");
injectedEmbedder().runStoriesAsPaths(storyPaths);
}

public static class StoryLoader extends LoadFromClasspath {

public StoryLoader() {
super(JBehaveTestRunner.class.getClassLoader());
}
}

public static class ReportBuilder extends StoryReporterBuilder {

public ReportBuilder() {
this.withFormats(org.jbehave.core.reporters.Format.CONSOLE,
org.jbehave.core.reporters.Format.HTML).withDefaultFormats();
}
}
}


5. JUnit으로 run 하면, 잘 돌아가는 것을 볼 수 있다.


이걸로, 기존에 지저분하게 시나리오 테스트 만들었던 것을, 깔끔하게 정리 완료!

반응형
,
반응형