꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by ozlael

이 글에서는 Unity에서 LOD(Level of Detail)을 사용하는 것에 대한 이야기를 해볼까 합니다 Object LOD에 촛점을 맞추고 있으며 Shader LOD와는 별개의 이야기입니다.


LOD란 무엇인가?

여러분이 게임 광고를 기획한다고 가정해보죠. 아리따운 누나가 대규모의 적들과 전투하는 켄셉을 잡고 모델을 섭외합니다. 역시 게임 광고 모델은 뭐니뭐니해도 아이유지요. 하지만 문제는 예산이네요. 예산을 최대한 절약 할 방법이 없을까 고민해봅시다.

이건 어떨까요? 기본적으로 모델을 아이유를 캐스팅하지만 하지만 굳이 클로즈업을 해도 되지 않는 장면이라면요? 아이유와 똑같이 생겼지만 아이유보다는 몸값이 저렴한 신봉선으로 대체해도 되지 않을까요? 클로즈업이 필요한 씬에서는 아이유를, 그렇지 않은 씬에서는 신봉선을 이용한다면 광고료를 아낄 수 있지 않을까요? (물론 실제로는 말도 안되는 소리입니다 ㅋ ) 이러한 전략이 바로 Level of Detail의 컨셉입니다. 

이미지 : TIG, 마영전

게임같은 Real-time Rendering에서는 최대한 렌더링 비용을 절약하는 것이 중요합니다. 화면에 작게 그려지는 모델을 그리는데 비싼 비용을 지불 할 필요가 없는 것이지요. 때문에, 카메라와 가까이 있거나 큰 오브젝트는 높을 퀄리티로 그리고, 멀리 떨어져 있거나 작은 오브젝트는 낮은 퀄리티로 표현하거나 연산처리하여 퍼포먼스를 향상시킬 수 있습니다. 이러한 기법을 Level of Detail(이하 LOD)이라 부릅니다. 말 그대로 디테일의 단계를 두어서 상황에 맞게 표현하는 것이지요.이러한 컨셉은 Material LOD, Terrain LOD, Mesh LOD, Bone LOD 등 여러 방면에서 쓰입니다. 이 중 Mesh 를 LOD 시키는 것에 대한 이야기를 할까 합니다.

https://www.assetstore.unity3d.com/kr/#!/content/8855


Unity에서의 LOD

LOD는 3D 게임 개발에 있어서 꼭 필요한 기능이고 당연히 Unity에서도 제공을 합니다. 다만 Pro Version에서만 제공이 되고 있어서 Free Version에서는 사용이 불가능합니다.(2015년 1월 기준) 사용법도 간단합니다. 오브젝트에 LOD Group 컴포넌트를 추가하고 LOD 0에는 가까이서 보일 오브젝트를 설정해주고, LOD 1에는 멀리서 보일 오브젝트를 설정해주면서 조절해주면 됩니다. 카메라 아이콘을 슬라이드해가면서 바로바로 확인하면서 편집이 가능합니다. 

자세한 설명은 공식 메뉴얼을 참고해주세요. (공식 메뉴얼 :  http://docs.unity3d.com/Manual/LevelOfDetail.html)


LOD 툴

유니티로 게임을 개발 할 때 에셋스토어를 활용하면 게임 모델을 손쉽게 구할 수 있습니다. 판타지, 레이싱, SF등 다양한 장르의 배경 및 케릭터 오브젝트들이 있어서 다운로드만 받으면 즉시 사용이 가능합니다. 유료는 물론 무료 모델들도 많이 있어서 저도 에셋스토어에서 다운로드받아 게임 제작을 하고 있습니다. 하지만 몇 가지 문제점들이 존재합니다.

첫째, 모든 리소스들이 LOD 시스템을 대응하고 있지는 않다는 것입니다. 이러한 경우는 Low quality 모델을 따로 생성해줘야 하지요. 아티스트팀이 따로 존재한다면 아티스트가 직접 Low quality 모델을 제작해줄 수도 있겠지요. 하지만 아티스트가 아예 없거나 일손이 모자란다면 자동으로 모델을 만들어주는 툴이 필요할겁니다. 

둘째, PC 및 콘솔 대응 리소스들도 많이 있습니다. 모델 하나가 만 단위가 넘는 폴리곤을 가지고 있는 경우도 허다합니다. 타겟이 PC 및 콘솔이라면 케릭터에 그정도를 투자할 수도 있을 것입니다. 하지만 모바일 타겟이라면 거의 불가능에 가까운 무거운 모델들일 것입니다. 이러한 모델들을 모바일에서 사용 할 수 있을 정도로 폴리곤을 줄여주는 툴이 필요할겁니다.

셋째, 파츠가 많이 나뉘어져 있는 모델도 존재합니다. 건물 하나가 문, 지붕 벽 등 몇 파츠로 나뉘어져 있거나, 애초아 드럼통 벽돌 등 다른 오브젝트등을 조합해서 하나의 배경 프랍으로 사용해야 하는 경우도 존재할 것입니다. 이러한 경우 메시 및 매터리얼의 갯수 등 상황에 따라 드로우콜이 늘어나게 됩니다. 드로우콜이 많아지면 성능이 느려지기 때문에 이 오브젝트들을 하나의 메시와 매터리얼로 만들어줘는 툴이 필요할겁니다. (드로우콜이 성능에 왜 영향을 미치는 지는 나중에 따로 설명하는 시간을 갖도록 하겠습니다.) 

이러한 이유들로 많은 LOD 관련 툴들이 존재합니다. 당연히 에셋스토어에도 LOD 툴들이 많이 있습니다.


Simplygon

그 중 Simplygon(심폴리곤이 아니라 심플리곤입니다 ㅋ)을 소개할까 합니다. Simplygon은 무료입니다. 몇 가지 제약이 있긴 합니다만 기본적으로는 무료입니다. 일단 에셋스토어에서 다운로드후 Import하시면 Unity 메뉴 Window에 Simplygon 항목이 생깁니다.(https://www.assetstore.unity3d.com/kr/#!/content/10144)

이제 실제로 사용하는 예를 보여드릴까 합니다. 우선 에셋스토어에서 모델을 하나 받습니다. 

https://www.assetstore.unity3d.com/kr/#!/content/10739

언니가 무섭고 이쁘긴 한데 폴리곤이 너무 많습니다. 세상에나 2만7천 폴리곤이라니 모바일에서는 전혀 써먹지를 못하겠네요.

이제 이 모델을 Simplygon으로 폴리곤을 반토막 내볼까 합니다. 모델을 선택 후 Simplygon 창을 클릭하면 다음과 같이 네개의 하위 탭이 존재합니다. (가입이 안되어있으시면 계정 등록을 하시면 됩니다. 일단 무료 계정으로 가입하셔도 충분합니다.) 반토막낼거니 Quick Start탭의 Reduction 항목에 50으로 입력사고 아래의 노란색 마크 버튼을 클릭합니다.

그러면 모델 데이터를 Simplygon서버에 보내고 받아오는 과정이 이루어지면서 Manage Jobs 탭의 톱니바퀴 아이콘이 움직입니다. Manage Jobs 탭을 열어 Download Assets Automatically를 체크해줍니다.

 그리고나서 status 상태가 100%이 되면 처리된 에셋을 자동으로 import하고 LODs 폴더에 처리된 모델이 생깁니다. 

확인해볼까요? 일단 육안으로는 큰 차이를 모르겠네요.

하지만 폴리곤 수를 보면 2만7천 폴리곤이였던 모델이 1만3천 폴리곤으로 확 줄었습니다. RPG처럼 케릭터가 많이 나오는 게임에서는 사용이 불가능하겠지만 대전격투게임처럼 케릭터가 적게 나오는 게임에서는 쓸만하겠네요.

한번 더 줄여볼까요? 이번엔 원본에서 3%로 설정해서 돌려본 결과입니다. 원본과 비교해보면 얼굴이 많이 못생겨지긴 했지만 전체적인 실루엣은 어느정도 유지해주고 있습니다.

하지만 폴리곤은 540여개로 대폭 줄었습니다.

멀리 두고 비교해보면 두 모델 간의 차이는 눈에 띄지 않을 정도입니다.

이번엔 다른 모델도 한번 살펴볼까요? 이 두 골렘은 작게 해서 보면 차이는 없어보입니다.

https://www.assetstore.unity3d.com/kr/#!/content/13631

하지만 폴리곤은 3배 이상 차이가 납니다.

이런 식으로 SImplygon같은 툴을 이용하면 LOD에 필요한 데이터를 쉽게 생성 할 수 있습니다. 더 나아가서는 PC 및 콘솔을 타겟으로 만들어진 데이터를 모바일에서 사용 할 수 있도록 폴리곤을 줄이는 용도로도 사용이 가능합니다. 물론 툴로 줄이는 것 보다는 아티스트가 이태리 장인정신으로 한땀 한땀 줄여주는 것이 가장 퀄리티는 좋습니다. 하지만 그렇지 못하는 상황이라면 툴의 도움이 정말 요긴하게 쓰일 것입니다. 

SImplygon의 추가적인 사용법은 공식 튜토리얼을 참고해주세요. (https://www.youtube.com/watch?v=qEyPVNGxGb8)


케릭터 LOD

이제 메시는 생성했으니 실제 LOD가 작동하는 케릭터를 만들어보도록 하죠. non-skinned mesh를 이용하는 오브젝트(예를 들면 건물이나 배경 프랍들)의 LOD 처리는 어려울게 없습니다. 하지만, 에니메이션 처리 되는 skinned mesh 케릭터에게 LOD 처리하는 것은 조금 귀챦습니다. 일단 Simplygon으로 생성하기 이전의 원본 mesh와 Simplygon 처리 후의 LOD 메시간의 본 에니메이션 정보는 공유되지 않습니다.

우선, Simplygon으로 생성 한 케릭터 중 Level 0 즉 가까이 있을 때 그릴 용도로 사용 할 케릭터를 씬에 올려놓습니다. 그러면 케릭터 오브젝트 바로 하위에는 본 계층구조 오브젝트와 skinned mesh 오브젝트가 있습니다. 이 메시 오브젝트를 Duplicate하여 사본을 만듭니다. 다른 오브젝트나 prefab에 있는 메시를 가져오면 안됩니다.

 그 후, 새로 만들어진 사본의 Skinned Mesh Renderer의 Mesh를 Level 1 즉 멀리 있을 때 그릴 용도로 사용 할 메시로 바꿔치기해줍니다.

이제 케릭터 오브젝트에 LOD Group 컴포넌트를 추가해주고 LOD 0, LOD 1 각각 메시를 설정해줍니다.

이제 케릭터가 하나의 에니메이터로 에니메이션하면서 거리에 따라 LOD 메시가 바뀌는 것을 확인 가능합니다.


마치며

게임 개발 시 얼마나 최적화를 시키느냐가 중요한 과제중 하나입니다. LOD 처리를 하는 것은 쉬우면서도 효과적인 최적화 방법중 하나입니다. 유니티는 이 기능을 쉽게 사용 가능하도록 제공해주고 있고 에셋스토어에는 보조장치들이 많이 존재합니다.SImplygon은 그 중 하나일 뿐이지 반드시 이것을 사용해야한다는 것은 아닙니다. 다만 LOD를 처리하는 예를 보여주기 위한 수단으로 소개해드린 것일 뿐입니다. 물론, 카메라가 고정되어있는 탑뷰(및 쿼터뷰)에서는 LOD 처리가 필요 없을 수도 있습니다. 하지만 요즘은 모바일 게임들도 탑뷰에서 탈피하는 게임도 많이 있어서 LOD의 필요성이 많아지게 될 것입니다. 

감사합니다.





반응형
,
Posted by 알 수 없는 사용자

안녕하세요. 오래간만에 돌아온 낄님입니다.


근 2년간 글을 올리지 못했는데, 현실의 아바타를 본래 캐릭터를 포함해서 부가 캐릭터까지 키우느라 복귀가 늦어졌습니다. (_ _)

딸, 아들을 둔 아빠가 되었습니다.



한국에 mmorpg가 부흥기부터 지금까지 10년의 세월이 흘렀는데요

스마트폰 게임이 주류가 되면서 사운드는 그동안 조금 소홀했었던 점이 있었으나

다시 좋은 게임들과 높은 사운드 경험을 요구하는 개발사들이 많아져서 사운드가 좀 더 힘을 보탤 수 있는 환경이 되었습니다.

많은 관계자 분들과 사운드 연출과 제작에 관한 이야기를 나누고 싶습니다. :)



이번에는 그동안 제작해오면서, 기술적으로 문제가 발생한 경우들에 대해 풀어보려 합니다..

다행이 커뮤니케이션 문제가 아니여서 해답을 찾는게 상대적으로 쉽습니다.

다만 시간이 많이 들어가게 되고, 정확한 원인을 파악하는 데 까지 버리는 시간을 생각하면 지난 젊음이 참 아깝네요.


비긴 어게인의 LOST STARS의 가사가 생각나는 하루입니다


God, tell us the reason youth is wasted on the young

신이시여, 우리에게 어린날이 어린날의 낭비인 이유를 말해줘요


여자와 문제 해결을 했었다면 더 유익한(?) 시간을 보냈을 텐데.... 라고 하면 이제 집에서 OUT 되는 사태가 (흑흑)


그래서 젊음을 이성과 함께 유익하게 보낼 수 있도록 

삽질을 예방하는 글을 적었습니다.


부제#1 

어째서 그들은 고통 받는가? - 소리가 튀면 튄다고 왜 말을 못해!


사운드 제작의 대부분은 영상이 나오고 나서 제작이 들어가게 됩니다.

따라서 영상이 제작되기 전까진 사운드는 놀아도 됩니다(?) 가 아니라, 


기획이나 영상이 제작 될 때, 참여하여 어떤 방향으로 진행이 될 지 미리 파악해 두는 것도 좋고, 

사운드 재생에 문제가 발생할 소지들을 미연에 방지해 주는 게 좋습니다.


그럼. 이제  영상이 나왔습니다.

굉장히 짧고, 엄청난 반복을 하는데, 루프 포인트를 잡기가 모호합니다.

그래서 기획팀에 문의해보니 제작 도구에서 찍은 건데, 플레이하면 계속 반복되는지라 이렇게 찍혔다고 하네요.



마르지 않는 눈물입니다. (게임 영상을 올리자니 보안상….)



이런 영상인 경우엔, 타임 라인이 밑에 있어서 '초' 또는 '프레임' 정보가 적히도록 제작 도구를 고쳐 주시는 게 현명합니다.

어렵다면, 이 영상의 프레임정보와 시작위치를 확인할 수 있도록 영상을 찍어주시는 게 좋겠네요.


하지만 사운드는 언젠가 제작도구에 심게 되는데, 이런 상황이라면 감으로 심는 방법뿐이어서,

도구를 개선하는 게 필수적입니다.


어쨌든 저 짧은 영상에 맞춰서 사운들을 제작했고 적용했습니다.

그런데 어느 날 확인해 보니, 소리가 반복되는 지점에서 '툭' 튀는 현상이 발생했습니다.


분명 사운드 자체는 루프 포인트를 잘 잡아서 흔히 '튀지 않게' 제작을 했는데도, 게임에 넣은 것은 이상하게 튑니다.


'뭐지, 나의 존재가 왜이리 가볍게 느껴지는가....'

팀원들의 눈빛이 루프하나 못잡는 잉여인력으로 보여지는 것 같고 왠지 해결 못하면 이 자리를 떠야 할 것 같네요.




흔한 오류인데,

다음과 같은 상황 중의 하나일 가능성이 높습니다.


1. 영상을 찍어준 프레임과 실제 게임에서 구동되는 프레임이 다르다.

2. 사운드 포맷이 WAV나 GOG가 아닌 MP3다.

3. 영상의 길이가 나중에 수정되어서 좀 더 길어졌거나 짧아졌다.

4. 사운드 재생 설정이 루프가 아니라, 원샷을 계속 시작 지점에서 PLAY를 하고 있었다.

5. 사운드 칩셋이나 메인보드에 문제가 있었다(...)


네. 무척 어처구니없는 상황이 아닐 수 없습니다.


제작과 관련한 문제는 하등 없었고요. 대부분의 사운드 오류는 디자인 과정에서 발생하는 것 보다, 실 구현에서 생기는데

그동안 가장 황당한 오류는 - 메인보드 업데이트였습니다.


누구도 예상하지 못한 문제였는데, 이렇게 적고 보니 그동안의 헤딩이 여기에서 빛을 발하려나요….


#좀 더 구체적인 해결방법


답1 : 영상을 찍어준 담당자의 PC가 너무 느리거나, 코덱의 문제가 크다. 가능하면 MOV 포맷으로 영상을 준비하도록 하는 게 좋다.(요즘엔 이런 문제는 거의 없죠)

답2: MP3는 재생 시점에 코덱 정보를 읽어드리면서 지연시간이 발생함 (즉시 재생이 필요하거나 루프가 필요하다면 WAV, 나 GOG를 활용)

답3: 영상이 수정되면, 바뀌었다고 사운드 쪽에 이야기 좀……. (내부 팀인데도 얘기를 안 해주면 혼나야 함)

답4: 플레이 방식을 루프로 재설정

답5: 메인보드 업데이트(..)


진정 별것 아닌 상황으로 고통받는 사운드 분들에게 힘이 되었으면 합니다.

대부분 구현과 관련된 부분에서 발생하는 문제들은 미연에 방지가 가능합니다.


영상이 바뀌든 말든 사운드는 튀지 말아야 하고, 

프로파일링을 통해서 사운드 오류가 있는 부분을 제작 도구에서 확인을 할 수 있다면 모두가 행복하겠죠

우리 스스로가 오디오 프로그래밍을 직접 하지 않는 이상,  꾸준한 치킨 상납으로 유능한 프로그래머님을 모시고 있는게 좋습니다.


다음은 거리에 따른 사운드 조율이 이상할 때 발생하는 문제들에 대해 이야기 하겠습니다.


감사합니다.



#새해 복 많이 받으세요~~

BY EIM 낄님




 



반응형
,

sort와 stable_sort

프로그래밍 2014. 12. 25. 18:58
Posted by 친절한티스

최근 sort와 stable_sort를 써볼일이 있어서 포스팅해봅니다. 일반적으로 sort 알고리즘을 자주 쓰는데, stable_sort 라는 것도 있더군요. MSDN에 나와있는 stable_sort의 특징으로는 아래와 같습니다.


  • 복잡도에 따라 많은 양의 메모리를 필요로 한다
  • 최적의 상황(충분한 메모리/정렬된 원소)에서는 O(N log N)의 성능을 보여준다
  • 그렇지 못한 경우에는 O( N ( log N )2 )의 성능을 보여준다
  • 일반적인 경우 sort가 더 빠르다
  • 정렬후 동일값의 원소 순서를 보장 해준다

sort와 stable_sort를 벤치마크한 결과를 보더라도 sort가 더 빠르다는 것을 알수 있습니다. 그렇다면 stable_sort는 언제 쓰는걸까요? sort와 stable_sort의 큰 차이점은 바로 같은 원소값의 순서를 보장해주냐 안해주냐에 있습니다. 이게 무슨 말이냐면 설명 보다 아래 코드의 결과값을 보시면 어떤 차이인지 바로 알수 있습니다.
using namespace std;

class CFoo
{
public:
	CFoo(int num,const string &str) : mNum(num),mStr(str) {}
	void PrintFoo()
	{
		cout << mNum << "[" << mStr.c_str() << "] ";
	}

	int GetNum() const
	{
		return mNum;
	}

private:
	int mNum;
	string mStr;
};

void PrintVector(const vector<CFoo*> &vec)
{
	for (auto it : vec)
	{
		it->PrintFoo();
	}
}

int _tmain(int argc,_TCHAR* argv[])
{
	vector<CFoo*> someListForSort{
		new CFoo(3, " "), new CFoo(3, " ") , new CFoo(4, " "), new CFoo(2, " ")
		, new CFoo(6, " "), new CFoo(3, " ") , new CFoo(5, " "), new CFoo(4, " ")
		, new CFoo(2, " "), new CFoo(2, " ") , new CFoo(6, " "), new CFoo(3, " ")
		, new CFoo(5, " "), new CFoo(4, " ") , new CFoo(2, " "), new CFoo(6, " ")
		, new CFoo(3, " "), new CFoo(5, " ") , new CFoo(1, "A"), new CFoo(1, "B")
		, new CFoo(1, "C"), new CFoo(1, "D") , new CFoo(1, "E"), new CFoo(1, "F")
		, new CFoo(1, "G"), new CFoo(1, "H") , new CFoo(1, "I"), new CFoo(1, "J")
		, new CFoo(4, " "), new CFoo(2, " ") , new CFoo(6, " "), new CFoo(3, " ")
		, new CFoo(5, " "), new CFoo(4, " ") , new CFoo(2, " "), new CFoo(6, " ")
		, new CFoo(3, " "), new CFoo(5, " ") , new CFoo(4, " "), new CFoo(2, " ")
		, new CFoo(2, " "), new CFoo(6, " ") , new CFoo(3, " "), new CFoo(5, " ")
		, new CFoo(4, " "), new CFoo(2, " ")
	};

	vector<CFoo*> someListForStableSort(someListForSort);

	// Sort
	cout << "Before Sort" << endl;
	PrintVector(someListForSort);
	cout << endl;

	sort(someListForSort.begin(), someListForSort.end(), [](const CFoo* lhs, const CFoo* rhs)->bool {
		if (lhs->GetNum() > rhs->GetNum())
			return true;
		return false;
	});
	
	cout << "Aftere Sort" << endl;
	PrintVector(someListForSort);
	cout << endl;

	cout << endl;

	// Stable Sort
	cout << "Before stable_sort" << endl;
	PrintVector(someListForStableSort);
	cout << endl;

	stable_sort(someListForStableSort.begin(), someListForStableSort.end(), [](const CFoo* lhs, const CFoo* rhs)->bool {
		if (lhs->GetNum() > rhs->GetNum())
			return true;
		return false;
	});

	cout << "Aftere stable_sort" << endl;
	PrintVector(someListForStableSort);

	return 0;
}

결과는 아래와 같습니다.



sort로 정렬된 값을 보시면 같은 1의 값을 갖고 있는 알파벳 순서가 뒤바뀐것을 확인할 수 있습니다. 그에 반해 stable_sort는 알파벳 순서가 변하지 않은 것을 확인할 수 있습니다.


참조

http://msdn.microsoft.com/ko-kr/library/z02ba27t.aspx

https://solarianprogrammer.com/2012/10/24/cpp-11-sort-benchmark/

http://ohyecloudy.com/pnotes/archives/294/

http://www.soen.kr/lecture/ccpp/cpp4/42-3-1.htm

반응형
,
Posted by 친절한티스

웹서버 구축시 골치 거리중 하나가 바로 핫링크(Hotlink) 입니다. 이미지나 동영상 같은 미디어 파일의 링크를 따서 외부 사이트에 걸어놓는 거죠. 이로인해 엄청난 양의 불필요한 트래픽이 발생할 수 있습니다. MVC4에서는 web.config 수정을 통해 이를 방지 할수 있습니다.


방법은 간단합니다. web.config 파일에 아래의 rule을 추가해주면, 해당 url에서 오는 요청이 아닌 것들은 rewrite에 지정된 이미지로 교체 해서 전송됩니다. 이 이미지는 저용량의 불펌금지!!! 같은 이미지로 설정해두면 좋겠죠.


// mydomain에서 오는 요청이 아닌 것은 noimage.jpg로 전송
<system.webServer>
	<rewrite>
      <rules>
        <rule name="Prevent hotlinking">
          <match url=".*\.(jpg|jpeg|png|gif|bmp)$"/>
          <conditions>
            <add input="{HTTP_REFERER}" pattern="^$" negate="true" />
            <add input="{HTTP_REFERER}" pattern="^http://www.mydomain\.com/.*$" negate="true" />
            <add input="{HTTP_REFERER}" pattern="^http://mydomain\.com/.*$" negate="true" />
          </conditions>
          <action type="Rewrite" url="/images/noimage.jpg" appendQueryString="false" />
        </rule>
      </rules>
    </rewrite>
</system.webServer>



반응형
,
Posted by 친절한티스

아래와 같이 DB에서 학생의 성적들을 얻어와 점수의 평균을 구하여 출력하는 로직이 있습니다.

// 학생 성적 모델
public class StudentResult
{
	int MathResult;
	int EnglishResult;
	int ScienceResult;
}

// 학생 성적 DB 컨텍스트
public class StudentDBContext : DbContext
{
	public DbSet<StudentResult> StudentResultTable { get; set; }
}

// DB에서 학생들의 성적을 얻어와 평균을 구합니다.
public class School
{
	public float GetStudentAvr()
	{
		var cont = new StudentDBContext();
		int studentCount = cont.StudentResultTable.Count();
		int resultSum = 0;
		foreach( var student in cont.StudentResultTable )
		{
			resultSum += student.MathResult;
			resultSum += student.EnglishResult;
			resultSum += student.ScienceResult;
		}
		float resultAvr = resultSum / studentCount;
		return resultAvr;
	}
}

이제 이 로직을 테스트 해보겠습니다.

[TestClass]
public class SchoolTest
{
	[TestMethod]
	public void GetStudentAvrTest()
	{
		var schoolTest = new School();
		float reulstAvr = schooolTest.GetStudentAvr();
		Assert.AreEqual(reulstAvr, "기대값");
	}
}

간단한 테스트 로직입니다. 하지만 예상한대로 기대값을 주어도 위 테스트는 실패하게 됩니다. 왜냐하면 테스트 환경에서는 실제 DB가 갖춰져 있지 않기 때문이죠. 실제 DB에 접속을 할수 없으니 School 클래스에서 사용하고 있는 StudenDBContext 객체에 예외 상황이 발생하게 됩니다.


그렇다면 위와 같이 DB를 사용하는 로직은 테스트를 못하는 것인가? 아닙니다. 바로 Mock을 이용하면 됩니다. Mock이란 단어 그대로 실제 그것인 것 마냥 흉내를 내주는 장치입니다. 이를 이용하면 실제 DB에 접속해서 데이터를 가져오는 것처럼 흉내를 낼수 있습니다.


먼저 Mock을 사용하기 위해서는 해당 라이브러리를 설치하여야 합니다. 저는 많이 애용되고 있다는 Moq4 라이브러리를 설치하였습니다.




도구 - 라이브러리 패키지 관리자 - 솔루션용 NuGet 패키지 관리를 통해 Moq 을 설치합니다.


이제 Mock을 이용해 진짜 DB를 사용하는 듯하게 해줄 모형을 만들어보겠습니다. 먼저 아래와 같이 인터페이스를 하나 만듭니다. 기존에는 직접 DBContext를 다뤘지만, 앞으로 만들 Mock을 사용하기 위해서는 약간의 추상화가 필요합니다.

public interface IStudentDBContextController
{
	int GetStudentsCount();
	IQueryable<StudentResult> GetStudentResultsAll();
}

다음, 실제 로직에서 직접 DBContext를 사용하던 부분을 인터페이스를 이용 하도록 아래와 같이 수정하여 줍니다.

// 인터페이스 구현
public class StudentDBContextController : IStudentDBContextController
{
	private StudentDBContext cont = new StudentDBContext();
	public int GetStudentsCount()
	{
		return cont.StudentResultTable.Count();
	}
	public IQueryable<studentresult> GetStudentResultsAll()
	{
		return cont.StudentResultTable;
	}
}

// 인터페이스를 사용한 변경된 School 클래스
public class School
{
	IStudentDBContextController StudentDBContextCont { get; set; }
	
	public School() : this(new StudentDBContextController())
	{
	}

	public School(IStudentDBContextController newStudentDBContextController)
	{
		StudentDBContextCont = newStudentDBContextController;
	}
	
	public float GetStudentAvr()
	{
		var cont = new StudentDBContext();
		int studentCount = StudentDBContextCont.GetStudentsCount();
		var studentResultTable = StudentDBContextCont.GetStudentResultsAll();
		int resultSum = 0;
		foreach( var student in studentResultTable )
		{
			resultSum += student.MathResult;
			resultSum += student.EnglishResult;
			resultSum += student.ScienceResult;
		}
		float resultAvr = resultSum / studentCount;
		return resultAvr;
	}
}

실제 코드 부분은 이걸로 됐습니다. 다음 테스트 코드를 보겠습니다. 위에서 만드는 IStudentDBContextController의 Mock을 만들어 인터페이스를 함수를 호출했을때 실제 DB에서 값을 얻어오는 것처럼 셋팅을 해줍니다.


[TestClass]
public class SchoolTest
{
	private Mock<IStudentDBContextController> mock = new Mock<IStudentDBContextController>();
	
	[TestInitialize]
	public void Initialize()
	{
		// Mock 함수 셋업
		mock.Setup(cont => cont.GetStudentsCount().Returns(4);
		mock.Setup(cont => cont.GetStudentResultsAll().Returns(
			var studentResultsAll new List<StudentResult> {
				new StudentResult { MathResult = 90,
                            EnglishResult = 70,
							ScienceResult = 60 },
				new StudentResult { MathResult = 80,
                            EnglishResult = 90,
							ScienceResult = 70 },
				new StudentResult { MathResult = 60,
                            EnglishResult = 70,
							ScienceResult = 70 },
				new StudentResult { MathResult = 80,
                            EnglishResult = 50,
							ScienceResult = 90 }
			};
			return studentResultsAll.AsQueryable();
		);
	}
	
	[TestMethod]
	public void GetStudentAvrTest()
	{
		// Mock 객체로 사용하도록 수정
		var schoolTest = new School(mock.Mock.Object);
		float reulstAvr = schooolTest.GetStudentAvr();
		Assert.AreEqual(reulstAvr, "기대값");
	}
}

아래 GetStudentAvrTest를 살펴 보시면 School 객체 생성자 초기값을 Mock 객체로 넘겨주고 있습니다. 이를 통해 GetStudentAvr 로직에서 Mock에 셋팅된 함수 값을 리턴받게 되고, 실제 DB에 접속된 듯한 환경을 조성할수 있게됩니다. 이로서 해당 로직이 제대로 값을 계산하는지 테스트 할수 있게 되었습니다.


참조 : https://github.com/Moq/moq4/wiki/Quickstart

반응형
,
Posted by ozlael

KGC13에서 발표했던 쿠킹스타 제작 사례로 보는 cocos2d-x 소개 자료입니다. 한참 지난건데 게데포에는 안올려뒀더군요 ㅋ 뒷북이지만 살포시 올려둡니다.


반응형
,