반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

ColumnFixture Tutorial (Fitnesse)

2013. 8. 12. 10:38 | Posted by 솔웅


반응형

Fitnesse에 대해서 공부하려고 하는데 검색을 해 봐도 별로 맘에 드는 블로그를 못 찾겠네요. 그래서 Fitnesse 홈페이지에 있는 Tutorial을 한번 쪽 훑어 보겠습니다.

기초적인 Research는 된 것 같아서 Fixture에 대한 부분을 살펴 봅니다.

여기로 가시면 Tutorial 원문을 보실 수 있습니다.

ColumnFixture


ColumnFixture 는 table 의 컬럼들을 곧바로 fixture class의 프로퍼티와 메소드 그리고 필드들로 매핑합니다. 이는 같은 테스트가 다양한 입력 argument들에 대해 반복적으로 사용되어야 할 때 반복적으로 사용되는 변수들을 만들 때 아주 유용합니다.

Table Format


테이블의 첫번째 줄(row)는 테스트 클래스의 이름입니다. 두번째 row의 컬럼 이름들은 이 컬럼과 관계된 필드나 메소드를 가리킵니다. Output 값들은 반드시 필드나 메소드 이름 다음에 ? 나 ()를 를 붙여야 합니다. 그 이후의 row들은 input argument들과 기대되는 결과값(output arguments)들의 combination list들 입니다.

!|info.fitnesse.fixturegallery.ColumnFixtureTest|
|firstPart|secondPart|together?|totalLength?|
|Hello|World|Hello, World|10|
|Houston|We Have a Problem|Houston, We Have a Problem|24|

Fixture class


fixture 클래스는 fit.ColumnFixture를 extend 해야 합니다. 그리고 테이블의 두번째 줄과 매치되는 public field들과 메소드들을 선언합니다.




Java Source Code

package info.fitnesse.fixturegallery;

import fit.ColumnFixture;

public class ColumnFixtureTest extends ColumnFixture {
	public String firstPart;
	public String secondPart;
	private int length;
	public String together(){
		length=firstPart.length()+secondPart.length();
		return firstPart+ ", "+secondPart;
	}
	public int totalLength(){
		return length;
	}
}

.NET Source Code

using System;
using System.Collections.Generic;
using System.Text;

namespace info.fitnesse.fixturegallery
{
    public class ColumnFixtureTest: fit.ColumnFixture
    {
        public String firstPart;
        public String secondPart;
        public String Together
        {
            get
            {
                return firstPart + ", " + secondPart;
            }
        }
        public int TotalLength()
        {
            return firstPart.Length+secondPart.Length;
        }
    }
}

Python Source Code

from fit.ColumnFixture import ColumnFixture

class ColumnFixtureTest(ColumnFixture):
    _typeDict = {
        "firstPart":  "String",
        "secondPart": "String"
    }

    def __init__(self):
        ColumnFixture.__init__(self)
        self.firstPart  = ""
        self.secondPart = ""

    # JAVA: public String together(){
    _typeDict["together"] = "String"
    def together(self):
        return "%s, %s" % (self.firstPart, self.secondPart)

    # JAVA: public int totalLength(){
    _typeDict["totalLength"] = "Integer"
    def totalLength(self):
        return len(self.firstPart) + len(self.secondPart)


Notes


자바버전에서는 클래스 필드들은 오직 input으로 사용되고 클래스 메소드들은 output으로 사용될 수 있습니다. JavaBean 프로퍼티들은 직접적으로 support 되지는 않습니다. (getter들이 output method들의 역할을 할 겁니다. 여러분은 getCreditLimit와 같은 전체 메소드 이름을 명시해야 합니다. .NET 버전에서는 필드들과 프로퍼티들이 input과 output들로 사용될 수 있습니다. 게다가 파라미터가 없는 메소드들도 output으로 사용될 수 있습니다. Java implementation은 대소문자 구분을 합니다. (case sensitive) .NET 버전은 프로퍼티 이름들에 대소문자 구분을 무시합니다.

table row들은 top-down 으로 실행됩니다. cell들도 왼쪽에서 오른쪽으로 실행되구요. 만약 row들이 실행되는 사이에 어떤 특별한 cleanup을 해야 한다면  reset() 메소드를 override하시면 됩니다.

.NET 버전은 ColumnFixture 로 domain object들을 감쌉니다. 이렇게 하면 fixture내의 도메인 object들의 프로퍼티들과 메소드들을 재선언 하지 않고 ColumnFixture 를 사용할 수 있도록 해 줍니다. Target objects로 가시면 좀 더 자세한 내용을 보실 수 있습니다.

output cell 을 empty로 놔두면 아무것도 테스트 하지 않고 현재의 값이 표시 됩니다.

Usage

 
ColumnFixture 는 calculation-based business rule들과 state machine들에 대해 테스트를 만들 떄 적합합니다. 같은 타입의 calculation이 다른 input 값들을 받아서 결과를 테스트하는 작업을 할 때 아주 유용합니다.

만약 테스트가 반복적이지 않다면 ColumnFixture 를 사용하는 것이 아주 좋은 방법이 아닐 수 있습니다. DoFixture (see DoFixture )를 한번 고려해 보세요. 만약 object들의 다이나믹한 리스트를 테스트하시려면 ArrayFixture (see ArrayFixture) 나 RowFixture 를 사용하세요. 이 Fixture들은 contents들도 verify 하거든요.


사람들은 가끔 이 ColumnFixture 를 잘 못 사용하기도 합니다. 특히 domain object나 데이터베이스에 record들을 insert 할 때 말이죠. 이를 위해서는 테이블의 마지막 칼럼에서 Create 를 call 함으로서 추가적인 메소드를 실행시킵니다. 이 메소드는 OK 같은 status code를 return 합니다. 이것은 creation에 대한 비지니스 룰을 체크하기를 원할 때 사용하면 좋습니다. 다른 테스트들의 stage를 준비하기를 원하신다면 SetUpFixture (see SetUpFixture )를 사용하면 좋습니다. 이 Fixture는 test page를 좀 더 가독성 있도록 해 줍니다. 또한 코드량도 줄일 수 있습니다.


반응형


반응형

지난번에도 Mockito 를 다룬적이 있는데요.

그 때는 다른 블로그에 있는 Mockito 관련 글을 번역해서 공부해 봤습니다.

예제들도 Android 를 중심으로 만들었구요.

 

오늘은 제가 참여하고 있는 Spring Framework에서 jUnit 테스트를 Mockito 를 사용해서 만든 경험을 정리해 보겠습니다.

 

일반적으로 jUnit 테스트 만드는 방법을 웹에서 검색하면 아주 간단한 예제들이 나옵니다.

하지만 실제 프로젝트에서 jUnit 테스트를 만들다 보면 이게 그렇게 간단하지 않거든요.

 

테스트하는 메소드 내에서 다른 메소드를 호출해서 return 값을 받아서 그 다음 일을 진행하기도 하고 또 호출한 메소드 내에서 또 다른 메소드의 호출이 일어나기도 하고요.

 

이럴 경우 Mockito 를 사용하는데 이번에 제가 실제 프로젝트에 참여하면서 경험하고 배운 내용을 정리합니다.

 

Spring Framework에는 Controller - Service - Transaction 이렇게 세개의 Layer로 나눠집니다.

 

Controller 부분은 Client Web 부분과 소통하고 Transaction은 SOA 나 DB 랑 소통합니다.

 

 

 

제가 참여하는 프로젝트의 흐름을 간단히 설명 드리면 Client 웹 브라우저의 form 문을 통해서 Controller에 어떤 데이터가 전달 됩니다. (이 때 세션값들도 받구요.)

그럼 Controller에서는 이 데이터를 기반으로 Bean 을 만들어 Service Layer로 던집니다. 그러면 Service Layer에서는 DTO를 만들어 Transaction으로 패스하고 Transaction에서는 Request를 Web Service 로 보냅니다.

웹 서비스에서는 다른 시스템이나 디비와 소통하면서 Response를 받아 Transaction으로 던져 주고요. Transaction은 이 Response를 가지고 DTO를 만들어서 Service로 던져 줍니다.

그러면 이 Service에서는 이를 다시 Bean으로 만들어 Controller로 패스하구요.

Controller는 이 Bean을 가지고 ModelAndView를 만들어서 Client의 웹 브라우저로 전달해서 그 다음 서비스를 사용자에게 전달합니다.

 

대부분의 중요한 비지니스 로직은 다른 외부 시스템에서 많이 진행 되서 이 자바 콤포넌트 내에서는 크게 비지니스 로직이 있지는 않습니다.

 

여기서 Controller에 있는 한 Method에 대해 jUnit 테스트를 만들게 되면 이 메소드가 Service 의 메소드를 호출하고 이 서비스에서는 다시 Transaction을 호출하고 여기서 다시 웹서비스를 call 해서 거기서 어떤 응답을 받게 되는 모든 과정이 진행됩니다.

 

그래서 단순하게 Controller에 있는 한 Method를 테스트 하려면 이 Service 에 있는 Method를 Call 하는 부분에서 그 return 값을 Mock 하면 됩니다.

 

이를 위해 저는 Mockito를 사용합니다.

 

아래는 지금 주료 사용하는 Mockito의 주요 기능을 정리합니다.

 

@RunWith(MockitoJUnitRunner.class)

처음 Mockito를 사용한다고 선언하는 부분입니다.

프로젝트에서 사용하는 소스코드를 사용할 수 없어서 API 에 있는 예제를 가지고 설명하겠습니다.

 

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {

    @Mock
    private List list;

    @Test
    public void shouldDoSomething() {
        list.add(100);
    }
}

이 예제는 여기 에 있는 API 문서에 있는 예제입니다.

 

우선 클래스 이름 선언하는 부분 위에 @RunWith(MockitoJUnitRunner.class) 를 선언함으로서 이 클래스에서 Mockito를 사용할 것이라는 것을 알립니다.

 

@Mock

그 다음에 private List list: 위에 @Mock 이라고 돼 있는데요.

이 부분을 Mock 할 거라는 것을 알리는 겁니다.

 

 

@InjectMocks

이 Annotation을 설명하기 위해 다른 예제를 보여드리겠습니다.

이 예제는 여기로 가시면 보실 수 있습니다.

 

public class ArticleManagerTest extends SampleBaseTestCase {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock; // note the mock name attribute
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }
}

public class SampleBaseTestCase {

    @Before public void initMocks() {
        MockitoAnnotations.initMocks(this);
    }
}

 

이렇게 사용하는데요. Mock 한 데이터를 이 @injectMocks 가 선언된 변수에 값을 inject하겠다는 내용입니다.주로 테스트하는 그 메소드에 이 inject 구문을 사용합니다. Mock 한 데이터를 넣어야 하는 곳이 그 메소드일 테니까요.

 

 

다음은 when().then() 구문을 설명할 텐데요.

when(mockedList.get(0)).thenReturn("first");

 

이 코드는 mockedList.get(0) 이라는 부분이 오면 그 return 값으로 first라는 스트링을 넣으라고 하는 명령입니다.

이게 바로 Controller에서 Service Layer에 있는 메소드를 호출 했을 때 이 Service 에 있는 메소드를 실행하지 않고 그 return 값을 mock 할 때 사용하는 구문입니다.

 

제가 참여하는 프로젝트에서는 이런식으로 사용됩니다.

when(service.method(isA(bean.class)).thenReturn(Bean)

 

클래스 이름이나 메소드 이름 그리고 input, return 파라미터 값들은 임의로 정한겁니다.

이 구문을 해석하자면 service 클래스에 있는 method라는 메소드가 호출 되면, 이때 method의 input 값은 bean.class 일 경우 입니다. 하여간 이 method가 호출되면 Bean을 return 하라는 명령입니다.

이전에 Bean을 별도로 생성해서 데이터를 세팅해 놔야 합니다.

 

이상이 제가 참여하는 프로젝트에서 jUnit 테스트를 만들 때 주로 사용하는 Mockito 기능들입니다.

저 위에 예제에서 나왔던 spy()라는 것도 몇군데 사용했는데요. 많이 사용하지는 않았습니다.

 

이 외에 Mockito 명령문은 아니지만 주로 많이 사용했던게 ReflectionTestUtils.setField 인데요.

 

Controller나 Service Layer 등등에서 다른 Layer의 메소드가 아니라 같은 Layer의 utility 클래스나 validator 클래스의 메소드를 호출하는 경우가 많이 있습니다.

 

이 때 이것을 사용했는데요. 다른 클래스의 메소드를 Mock instance로 처리하는 명령어 입니다.

이 기능은 Mockito의 기능이 아니라 Spring Framework의 API 인것으로 알고 있습니다.

 

 

 

반응형


반응형

지난 번 글을 쓴지 3주나 지났군요. 자기가 쓴 코드도 며칠만 지나면 뭐가 뭔지 너무 오래 되서 이전에 어디까지 공부했는지도 잘 모르겠습니다.


지난번 글 대충 읽어 봤더니 chainFunctions() 함수를 분석하면 될 것 같습니다.


이 함수에 전달되는 파라미터를 보겠습니다.


function chainFunctions(exec,options,runParent,noanim)


exec,options,runParent,noanim 이렇게 4개의 파라미터들이 전달 됩니다.


이 함수를 call 하는 anim 함수에서 이 함수가 call 되는 부분을 보죠.


return chainFunctions(function () return transition.to(obj,options) end, options,runParent)


첫번째 exec 에 해당 되는 부분은 function () return transition.to(obj,options) end 라는 함수 입니다. 여기서 obj,option 은 지난 번 글에 서 다뤘죠.




obj는 흰공의 위치이고 options는 빨간점의 위치값입니다.

그러니까 chainFunction 의 첫번째 인자는 흰공이 options 에 설정된 위치까지 움직이는 겁니다.

두번째 파라미터는 뭘까요?  options입니다. 이 options는 빨간공의 위치이죠? 흰공이 빨간공으로 움직이면 그 다음은 그 빨간공 자리에 흰공이 있게 되니까 그 위치가 흰공의 위치값이 될 겁니다. 아마 그걸 다루려고 이 options 값을 전달하는 것 같습니다.

코드를 분석 해 보면 나오겠죠.

세번째 값인 runParent 는 뭘까요? anim() 함수에서도 runParent를 던져주긴 하는데 이제 main 함수에서 call 됐을 때는 전달받지 못한 겁니다.


그럼 뭘까요?



anim() 함수가 main 함수에서만 call 되는 건 아닙니다. chainFunction() 함수 안에서도 이 anim() 함수가 호출되는데요.


return anim(arg[1],arg[2],run) 라는 부분이 있습니다.


여기에 세번째 파라미터가 있죠?

이건 chinFunction() 내부를 분석하면 뭔지 알 수 있을 겁니다.


그리고 chainFunction() 에는 네번째 파라미터가 noanim 이라고 있는데 anim() 함수에서 호출 될 때는 이 값이 없습니다.

자세히 보니까 chainFunction() 함수 내에서 자기 자신인 chainFunction()을 호출하는 부분이 있습니다.


return chainFunctions(arg[1],{},run,true)


이 4번째 파라미터는 true 이군요.

이것도 chainFunctions() 함수 내부 로직을 분석하면 어떻게 사용되는지 알 수 있을 것 같습니다.


들어가기 전에 이 소스를 만든 개발자가 적어 놓은 주석부터 한번 보겠습니다.


각 파라미터 (parameters, arguments) 들에 대한 설명을 달아 줬네요.


-- args
-- exec - is the current function to execute. It is assumed to be a function that runs a Corona SDK transition

exec는 현재 실행되는 함수이다. 이 값은 Corona SDK transition이 실행되는 함수가 전해질 것이다.


-- options - options for the animation (see Corona SDK). These are assumed to be the same closed over by the exec call - chainFunctions relies on being able to manipulate them for "onComplete" and "whenDone" to work

options 는 animation을 위한 것이다. (Corona SDK)를 보세요. 이것들은 chainFunctions에서 exec에 의해 실행 된 다음에 onComplete과 whenDone 이 됐을 때 생성되게 되는 것들이 될 것이다.


--         # Additional feature: if options.delete==true the subject of the animation will be deleted (obj:removeSelf() called) when the animation completes

# 추가 기능들 : 만약 options.delete 가 true 이면 애니메이션이 완료 되면 그 애니메이션 되는 대상은 delete 될 것이다. (obj:removeSelf() 가 call 됨으로서).


-- runParent (optional) - run the previous function in the chain. It takes a single function as an argument. chainFunction creates this function itself

runParent(옵션)은 chain에 있는 이전 함수를 실행한다. argument로 single function을 갖는다. chainFunction 이 이 함수를 생성한다.


-- noanim (optional) - true if exec does not represent a function executing a Corona SDK transition. If true, the only valid call to the chain is "start"

noanim(옵션) - true. 만약 exec 가 Corona SDK 의 transition을 실행하지 않을 때 이 값은 true 가 될 것이다. 그러면 여기서 start 함수만 call 될 것이다.




이렇게 친절하게 주석을 달아 주었네요.


이 주석만 봐도 대충 이 함수의 로직이 이해가 됩니다.


main 함수에서 설정했던 빨간공의 위치들이 돌아가면서 options 가 되서 흰공이 이 빨간공 위치로 계속 이동할 것이고 더이상 빨간공의 위치가 없으면 noamin이 true 가 되서 start 함수가 호출되서 끝나는 거군요.


오늘은 여기까지 하구요.


다음 글에서 제대로 chainFunctions() 부분을 분석해 보도록 하겠습니다.



반응형