아래 글은 얼마전 Damon이 메일로 공유 해준 내용입니다. 

오늘에서야 본사 복귀 해서 PC로 읽어 봤는데 볼만한 포스팅이네요. 


필요성에 대한 부분도 공감이 되고..



#고양이목에_방울달기




서론

 이 글은 필자가 속해 있는 팀의 코딩 컨벤션(스타일 가이드)를 맞추기 위해 '더글라스 크락포드의 자바스크립트 코드 컨벤션'을 참고하여 작성한 것 입니다. 무조건 이 규칙들이 좋은 것은 아니니, 팀원들의 규칙들을 잘 통합하여 각자 팀만의 코딩 컨벤션을 적용하길 권장합니다. 


1. 코딩 컨벤션(스타일 가이드)가 필요한 이유
2. 들여쓰기
3. 연산자
4. 괄호
5. 주석
6. 기본 리터럴
7. 객체 리터럴
8. 변수 선언
9. 함수 선언
10. 문장 규칙
11. 가독성을 위한 공백







1. 코딩 컨벤션(스타일 가이드)가 필요한 이유

  대학 시절, 컴공과를 다니던 황OO군은 아무것도 없는 공백에 필요한 코드를 작성하여 '동작'을 목적으로 '성적'을 목적으로 의식의 흐름따라 코딩을 하였습니다.


예를 들면 이런 것 (내용도 내용이지만 필체마저 의식을 따라감)


  그렇게 잘 '동작'하는 코드를 통해 좋은 성적을 거두고 회사에 입사하게 되었습니다. 막상 와보니 교수님의 과제처럼 아무것도 없는 상태에서 뭔가를 구현하는 일은 생각보다 없습니다. 이미 선임이 짜놓은 코드를 기반으로 개발하게 된 것입니다. 문제는 여기서 발생합니다. "코드가 술술 읽히네 예술적이다." 라는 반응이 나오는 코드가 있는 반면 "이 코드 짠 XX 얼굴 한번 보고싶다." 라는 반응이 나오는 코드도 있습니다. 이 같은 문제를 해결하기 위해 나온 것이 자바스크립트 코딩 컨벤션(스타일 가이드)입니다. 


  실무에서는 새로운 코드 작성보다 '유지보수'에 더 많은 시간을 보낸다고 합니다. 대부분 이미 있는 코드에 새로운 코드를 추가하거나 수정합니다. 유지보수성이 좋게 코드를 작성했다면 작성자 본인 뿐만 아니라 다음 사람에게도 유지보수 시간을 단축할 수 있도록 도와줍니다. 다음은 '자바 언어를 위한 코드 컨벤션'에서 언급한 코딩 컨벤션의 중요성입니다.

- 소프트웨어 개발 시 드는 비용의 80%는 '유지보수'에 사용된다.
- 소프트웨어 개발 생명주기에서 최초 개발자가 끝까지 유지보수하는 경우는 거의 없다.
- 코딩 컨벤션은 소프트웨어 가독성을 향상시켜, 개발자가 새로운 코드를 더 빠르고, 완전하게 이해하게 함.

코딩 컨벤션의 필요성을 알았으니, 다음의 코딩 컨벤션 규칙들을 살펴보도록 하겠습니다.




2. 들여쓰기

  최근 에디터들은 자동으로 Enter를 치면 들여쓰기를 해주고 있습니다. 하지만 그것에 의존하지 말고 1번의 탭 또는 4번의 공백 둘 중 하나의 규칙을 습관적으로 적용합니다.
// 좋은 예
function test() {
console.log('들여쓰기 테스트');
}

// 나쁜 예
function test() {
console.log('들여쓰기 테스트');
}





3. 연산자

3.1 연산자 삽입 시 공백 삽입 위치

 두 개의 피연산자와 함께 사용하는 연산자는 사용 시 연산자 앞 뒤에 공백을 한 칸씩 추가해주도록 합니다. 이는 연산자의 목적를 명확하게 보여주기 위함입니다. (주의, 단항 연산자는 붙여서 사용)

// 좋은 예
var isSuccess = (condition <= 10);

var i;
for (i = 0; i < 10; i++) {
work();
}

// 나쁜 예
var isSuccess = (condition<=10);

var i;
for (i=0; i<10; i++) {
work();
}



3.2 할당 연산자


 할당 연산자 사용시 할당 값이 비교 연산식이면 괄호로 감싸줍니다.

// 좋은 예
var isBaby = (age < 10);

// 나쁜 예
var isBaby = age < 10;



3.3 동등 연산자


 동등 비교를 할 때 타입 강제 변환의 위험이 있으면 무조건 ==, != 대신 ===, !==를 사용해줍니다.

// 좋은 예
console.log('1' === 1);

// 나쁜 예
console.log('1' == 1);





4. 괄호

여는 괄호 다음과, 닫는 괄호 이전에 공백은 없습니다.

// 좋은 예
var isBaby = (age <= 10);

// 좋은 예
var i;
for (i = 0; i < 10; i++) {
work(hour, amount);
}

// 나쁜 예: 앞 뒤에 불필요한 공백 들어감
var isBaby = ( age <= 10 );

// 나쁜 예: 인자 사이에 불필요한 일 들어감
var i;
for (i = 0; i < 10; i++) {
work( hour, amount );
}




5. 주석

5.1 한 줄 주석
  • 하단의 코드를 설명하기 위해 독립된 줄에 주석 작성
  • 주석 앞의 코드를 설명하기 위해 줄 끝에 주석 작성
  • 여러 줄에 걸친 설명일 때는 사용하지 않는다.
  • 독립된 줄에 한 줄 주석 작성 시에는 설명할 코드와 같은 단계 들여쓰기 사용하며, 이전 줄은 한 줄 비웁니다.

// 좋은 예
if (age > 20) {

// 이 줄은 나이가 20살 이상일 때만 실행됩니다.
showAdultMovie();
}

// 나쁜 예: 이전 줄에 공백이 없어 가독성이 떨어짐
if (age > 20) {
// 이 줄은 나이가 20살 이상일 때만 실행됩니다.
showAdultMovie();
}

// 나쁜 예: 주석의 들여쓰기가 설명할 주석과 단계가 다릅니다.
if (age > 20) {

// 이 줄은 나이가 20살 이상일 때만 실행됩니다.
showAdultMovie();
}

// 나쁜 예: 한 줄 주석은 한 줄만 사용합시다.
// 이 코드는 만으로 나이가 20세 이상인 것이 아니고
// 그냥 20세 이상 아니아니 이게 아니고
// 아 아닙니다 만으로 나이가 20세 이상인
// 사람을 판별하기 위한 조건문입니다.
if (age > 20) {

// 이 줄은 나이가 20살 이상일 때만 실행됩니다.
showAdultMovie();
}




5.2 여러 줄 주석
  • 첫 번째 줄은 여러 줄 주석을 여는 방법인 /*를 삽입하며, 다른 텍스트는 넣지 않는다.
  • 그 다음 줄 부터는 *와 텍스트를 입력하는데 첫 째 줄의 *과 열을 맞춘다. *과 텍스트 사이에는 공백 하나를 넣어줍니다.
  • 마지막 줄은 여러 줄 주석을 닫는 방법인 */를 삽입하며 다른 텍스트는 넣지 않는다.

// 좋은 예
if (age > 20) {

/*
* 이 코드는 만으로 나이가 20세 이상인 것이 아니고
* 그냥 20세 이상 아니아니 이게 아니고
* 아 아닙니다 만으로 나이가 20세 이상인
* 사람을 판별하기 위한 조건문입니다.
*/
showAdultMovie();
}

// 나쁜 예: 이전 줄에 공백이 없어 가독성이 떨어짐
if (age > 20) {
/*
* 이 코드는 만으로 나이가 20세 이상인 것이 아니고
* 그냥 20세 이상 아니아니 이게 아니고
* 아 아닙니다 만으로 나이가 20세 이상인
* 사람을 판별하기 위한 조건문입니다.
*/
showAdultMovie();
}

// 나쁜 예: *과 텍스트 사이에 공백이 없음
if (age > 20) {

/*
*이 코드는 만으로 나이가 20세 이상인 것이 아니고
*그냥 20세 이상 아니아니 이게 아니고
*아 아닙니다 만으로 나이가 20세 이상인
*사람을 판별하기 위한 조건문입니다.
*/
showAdultMovie();
}



5.3 주석 어노테이션
어노테이션은 코드에 추가적인 설명을 더합니다. 한 단어이며 그 뒤에 콜론이 붙습니다.
ㄱ. TODO : 코드를 다 작성하지 않음. 다음에 작성할 내용이 반드시 기술되어 있어야합니다.
ㄴ. HACK : 어거지(임시변통)으로 해결한 코드. 핵을 왜 사용했는지 정보가 있어야하며, 더 나은 방법으로 해결할 수 있음을 뜻합니다.
ㄷ. XXX : 코드에 문제가 있어 가능한 빨리 수정되야 함을 의미
ㄹ. FIXME : 코드에 문제가 있어 수정해야하지만 XXX보다는 덜 중요합니다.
ㅁ. 변경 가능성이 있거나 더 나은 방법이 있을 수 있음으로 리뷰가 필요한 코드를 의미합니다.



6. 기본 리터럴

  • 문자열은 무조건 큰 따옴표로 묶습니다. 문자열을 \로 끊어 여러줄로 적지 않습니다.
  • 숫자는 10진수 정수, 지수표현법을 이용한 정수, 16진수 정수, 부동 소수점을 이용한 정수만 사용하며, 소수점 앞 뒤에는 숫자가 최소 하나는 있어야 합니다. 8진수는 사용하지 않습니다.

// 좋은 예
var name = "황혁진";

// 나쁜 예
var name = '황혁진';

// 나쁜 예: 문자열이 다음으로 넘어감
var story = "옛날 옛날에 어느 마을에 어떤 집에 \
어떤 사람과 어떤 사람이 살았어요.";


// 좋은 예
var num = 10;
var num = 10.0;
var num = 10.00;
var num = 0xB2;
var num = 3e1;

// 나쁜 예
var num = 10.; // 소수점 뒤에 숫자 없음
var num = .1; // 소수점 앞에 숫자 없음
var num = 010; // 8진수 사용 안함






7. 객체 리터럴

  • 여는 중괄호는 중괄호를 여는 문장과 같은 라인에 있어야합니다.
  • 프로퍼티 - 값 쌍은 여는 중괄호 다음 줄 부터 작성하고 여는 중괄호가 있는 라인을 기준으로 한단계 들여쓰기를 합니다.
  • '프로퍼티: 값' 의 형식을 지켜줍니다. 공백없이 콜론을 입력하고 공백 하나 후에 값을 입력합니다.
  • 값이 함수라면 프로퍼티명 다음에 한줄로 입력하지 않고 함수 이전 줄, 다음 줄에 공백을 넣어줍니다.
  • 관련된 프로퍼티끼리 묶기 위해 프로퍼티간 공백을 넣어도 됩니다.
  • 닫는 중괄호는 텍스트와 함께 두지 않으며 끝에 세미콜론(;)을 붙여줍니다.

// 좋은 예
var person = {
name: "송민재",
age: 26,

getName: function() {
return this.name;
}
};

// 나쁜 예: 함수 이전에 공백이 없음
var person = {
name: "송민재",
age: 26,
getName: function() {
return this.name;
}
};

// 나쁜 예: 잘못된 들여쓰기
var person = {
name: "송민재",
age: 26,

getName: function() {
return this.name;
}
};

// 나쁜 예
var person = {name: "송민재", age: 26};



 함수에 객체 리터럴을 전달할 때도 간단한 객체는 한 줄로 전달하고 싶겠지만, 가독성을 위해 위의 규칙을 적용해줍니다.

// 좋은 예
getDepartment({
name: "송민재",
age: 26
});

// 나쁜 예: 객체 리터럴 규칙을 지키지 않음
getDepartment({name: "송민재", age: 26});



 객체 프로퍼티 작성 규칙은 8.2 변수명 생성 규칙과 같습니다. 객체 메서드는 9.2 함수명 규칙을 따릅니다. 프로퍼티나 메서드가 private라면 _를 맨 앞에 붙여 private라고 명시적으로 알려줍니다.

var person = {
_homeAddress: "인천",

name: "황혁진",
age: 26,

_getHomeAdrress: function() {
return this._homeAddress;
}
}




8. 변수 선언

8.1 변수 선언 방법

 변수 연산자는 반드시 사용 전에 선언되어야합니다. 한 개의 var을 이용해 함수의 맨 윗줄에 선언하며, 대입 연산자는 같은 단계의 들여쓰기를 사용해주면 가독성에 도움이 됩니다. 마지막 변수 선언 전까지는 ,로 구분을 해주며 마지막 변수에는 세미콜론(;)을 붙여줍니다. 초기화 된 변수는 반드시 초기화 되지 않은 변수보다 앞에 위치 시킵니다.


// 좋은 예
var name = "권현아",
age = 23,
home = "방화동";

// 나쁜 예: 대입연산자의 들여쓰기가 맞지 않음
var name = "권현아",
age = 23,
home = "서울과 인천의 경계";

// 나쁜 예: 들여쓰기가 맞지 않음
var name = "권현아",
age = 23,
home = "서울과 인천의 경계";

// 나쁜 예: 한 줄에 여러 변수 선언
var name = "권현아", age = 23, home = "서울과 인천의 경계";

// 나쁜 예: 초기화 되지 않은 변수가 먼저 위치함
var name,
age = 23,
home = "서울과 인천의 경계";



8.2 변수명 규칙


변수명은 낙타표기법을 사용합니다. $나 \는 사용하지 않으며 _도 사용을 자제하겠습니다. 변수명은 소문자로 시작하며 새로운 단어의 첫 번째 문자는 대문자를 사용합니다. 변수명의 첫 단어는 반드시 명사를 사용해주어 함수와 구분하기 쉽도록 합니다. 

// 좋은 예
var firstName = "혁진";

// 나쁜 예: 대문자로 시작
var FirstName = "혁진";

// 나쁜 예: 빈칸 들어감
var first_name = "혁진";

// 나쁜 예: 동사로 시작
var getFirstName = "혁진";



변수를 값이 변하지 않는 상수로 사용하려면 이름의 모든 문자를 대문자로 사용하며 단어 사이에 밑줄( _ )을 넣어줍니다.

var TOTAL_COUNT      = 1,
TOTAL_POPULATION = 1000;






9. 함수 선언

9.1 함수 선언 방법
  • 객체에 선언된 메서드가 아니라면 반드시 함수 선언 형식을 사용합니다.
  • 함수 표현식, new를 이용한 생성자 형식을 사용하지 않습니다. (객체 생성 시 사용)
  • 함수명과 여는 괄호 '(' 사이에는 공백 넣지 않습니다. '(' 와 ')' 사이에는 공백 하나를 넣습니다.
  • 인자 입력시 , 뒤에 공백을 넣습니다.
  • 함수 본문은 한 단계 들여쓰기를 사용합니다.
// 좋은 예
function working(arg1, arg2) {
return money;
}

// 나쁜 예: 함수 명과 괄호 사이에 공백
function working (arg1, arg2) {
return money;
}

// 나쁜 예: 함수 표현식 사용
var working = function(arg1, arg2) {
return money;
}

// 나쁜 예: 생성자 방식 사용
var money = new Function("arg1", "arg2");



 익명 함수는 객체에 메서드 할당 시나, 다른 함수에 인자로 전달할 때 사용합니다.

person.name = function() {
// 코드
};

getName(function() {
// 코드
});



9.2 함수명 규칙


함수명 역시 낙타표기법을 사용합니다. 첫 번째 단어는 동사를 사용하여 변수명과 구분해줍니다. 

// 좋은 예
function getName() {

};

// 나쁜 예: 대문자로 시작
function GetName() {

};

// 나쁜 예: 명사 사용
function name() {

};

// 나쁜 예: _ 사용
function get_name() {

};



 생성자 함수는 new를 통해 생성할 때 실행되는 함수며 객체 인스턴스를 생성할 때 사용하므로 동사로 시작하지 않고, 첫 문자를 대문자로 시작합니다.

// 좋은 예
function Enemy() {

};

// 나쁜 예: 소문자로 시작
function enemy() {

};






10. 문장 규칙

15.1 return문
반환하는 값의 명확한 의미를 알기 위해 괄호를 사용하지 않습니다.
// 좋은 예
var generation = (age <= 7 ? "baby" : "teenager");
return generation;

// 나쁜 예
return (age <= 7 ? "baby" : "teenager");



15.2 if 문
한 문장이라도 중괄호를 포함해줍니다. (다음에 문장을 추가할 때 중괄호를 빼먹는 실수하지 않도록)
// 좋은 예
if (person != null) {
work();
}

// 나쁜 예
if (person != null)
work();


15.3 for 문
변수는 for문의 초기화 구문 이전에 선언해줍니다.
// 좋은 예
var i = 0,
len = 10;
for (i = 0; i < len; i++) {

}

// 나쁜 예
for (var i = 0; i < 10; i++) {

}


15.4 switch 문
  • case는 switch를 기준으로 한단계 들여쓰기 합니다.
  • 첫 번째 case를 제외한 case와 default 사이에는 한 줄의 공백이 있어야합니다.
  • default를 제외한 case는 return, break, throw 중 하나로 마쳐야 하고 다음 case로 의도적으로 넘기는 부분은 주석처리를 해줍니다.
  • default를 의도적으로 생략했으면 주석처리 해줍니다.
// 좋은 예
switch (num) {
case 0:
break;

case 1:
break;

default:
break;
}

// 좋은 예
switch (num) {
case 0:
// 다음 case와 같은 동작 처리

case 1:
break;

default:
break;
}

// 좋은 예
switch (num) {
case 0:
break;

case 1:
break;

// default 구문 생략
}







11. 가독성을 위한 공백

의미 없는 공백은 사용하지 않습니다. 한 줄 공백은 다음의 상황에 사용합니다.
  • 메서드 사이
  • 메서드와 지역 변수와 첫 번째 문장 사이
  • 여러 줄 주석이나 한 줄 주석 이전
  • 메서드 내에서 가독성 향상을 위해 논리적으로 나눈 그룹 사이

공백은 다음 상황 때 사용합니다.
  • 인자가 여러개 일때 콤마(,) 뒤에 공백을 넣습니다.
  • 이항 연산자는 앞 뒤에 공백을 넣고, 단일은 넣지 않는다.
  • for 문의 표현식은 공백을 넣어 구분



출처: http://itmining.tistory.com/72 [IT 마이닝]


'Dev pattern' 카테고리의 다른 글

WPF Developement  (0) 2017.07.21
AngularJs service unit test via jasmine.  (0) 2017.06.29
Jasmine getting started to test javascript (Angular)  (5) 2017.05.26

Zipper를 개발하면서 WPF 개발 practice를 정리해 보았습니다.

개념적인 부분 위주로 다루겠습니다.


WPF

Windows Presentation Framework.

뭔가 거창하지만 .Net 진영의 Winform의 버전업이라고 보시면 될 것 같습니다.

RIA(Rich internet application) 및 WindowsPhone7을 위한 platform인 Silverlight의 부모격이며, UWP 등의 현재 MS platform의 근간이기도 합니다.


MS가 ASP.NET Webform에서 MVC로 바뀌면서 패러다임 및 기술의 큰 변화를 이루었고, Winform을 WPF로 변경하면서도 그렇습니다.

UI는 XML베이스의 xaml(재믈이라고 읽습니다.)로 작성합니다.

로직은 전통적인 방식의 code_behind 형태로 개발할 수도 있으나, MVVM이라는 패턴을 권장하고 있습니다.



MVVM pattern

Model - View - ViewModel의 약자입니다.

익숙한 MVC와 크게 다르지 않습니다만, View와 Model을 완벽하게 분리할 수 있다는 점이 특징입니다.

이를 위해서는 Data binding, Command(Behavior)라는 개념이 필요합니다.


이를 통해서 xaml designer와 programmer는 서로의 영역을 침범하지 않고 app을 개발할 수 있습니다.


출처: http://hackersstudy.tistory.com/71



MVVM Light

이러한 MVVM pattern을 지원하기 위한 다양한 library가 존재합니다.

대표적으로 MVVM Light를 들 수 있으며, 지난 NY**에서 사용한 MVVM Cross도 그 일종입니다. (Alex는 후자를 좋아하더라고요. )


MVVM pattern에 따른 개발에 필요한 추상 class 및 helper들을 제공하며, IoC 기능도 지원합니다.

예를 들면, ViewModel의 base가 되는 ViewModelBase, IoC를 위한 SimpleIoc, Command를 지원하기 위한 RelayCommand 등이 있습니다.



IoC

Inversion of Control.

해석하기 참 애매하네요, 제어 반전??


이건 도저히 짤게 넘길 수 없는 주제이네요.

아래의 link를 살펴 봅시다.


https://msdn.microsoft.com/en-us/library/ff921087.aspx


이를 위한 개발 패턴으로는 Service Locator pattern과 Dependency Injection(DI) pattern이 있습니다.

개발한 Zipper에서 실제 App 부분을 MVVM light를 통한 service locator pattern을 사용하였고, Unit test에서는 Dependecy injection pattern을 이용하였습니다.



MUI Template

WPF를 통해 UI 표현력을 크게 향상시켰음에도 불구하고, 기본적인 그 형태는 기존의 것 (Winform)과 큰 차이를 느끼기 어렵습니다.

WPF가 공개되고 Morden UI (or Metro UI라고 불리우는)를 Windows8을 통해서 크게 대중화시켰던 MS의 제품의 그러한 design을 손쉽게 적용할 수 있도록 MUI Template이 제공됩니다.


쉽게 이와 같은 UI를 만들 수 있다는 거죠.


https://github.com/firstfloorsoftware/mui/wiki


Conclusion

WPF는 관심사의 분리를 완벽하게 해낸 의미 있는 framework입니다.

실제 개발 과정에서 개발자는 VisualStuio를 통해, 디자이너는 Blend라는 툴을 이용해 완벽하게 따로 작업할 수 있습니다.


초기 웹에 대한 붐이 일었을 때, Html과 javascript 및 ASP, PHP 코드가 뒤섞인 환경에서 개발자와 디자이너는 앙숙일 수 밖에 없었습니다.

내가 다 개발해 놓으면 디자이너가 기능을 망가뜨리고, 디자이너가 UI를 잘 꾸며 놓으면 개발자가 layout을 흐트려 놓는...


이렇게 틀을 잘 만들어 놓았기에 이어서 UWP나 Xamarin이나 이를 근간으로 할 수 있었습니다.

xaml이 XML을 근간으로 하다 보니 Microsoft 진영의 mark up 언어의 parsing 속도는 Java 진영에 비해서 월등하다는 소문을 들은 적도 있습니다. :)



또한, 개발에 대한 pattern도 바뀌었습니다.

저는 게으른 개발자로서 application을 개발하면서, 수시로 app을 실행해 보고 테스트해 보는 작업이 피하고 싶었습니다.

그리고 그러한 고민을 하던 선배들이 그 방법을 검토해왔고, 그러한 practice들을 만들어 왔습니다.


흐름에 뒤쳐지지 않기 위해서가 아니라 더 나은 개발을 위해서 그리고 더 나은 결과를 만들어 내기 위해서 이러한 패턴의 습득은 선택이 아닌 필수라고 생각합니다.


MV* pattern이나 IoC는 비단 .net platfrom에서만 도입된 개념이 아니고, Java나 혹은 javascript framework에도 적용이 됩니다.

AngularJs도 controller 선언할 때 참조할 service 등을 나열하는 것으로 의존성을 해결하고 있습니다.


이는 또한 product의 완성도를 높이는 가장 높은 방법 중 하나입니다.


Code coverage 측면에서 가능한 모든 코드가 test 되어야겠지만, 사실 여러 가지 요인으로 이 수준을 달성하기가 쉽지 않습니다.

결국은 코드의 중요도를 매기고 그에 따라 선별적으로 TDD 적용을 시작하는 것이 합리적이라고 생각합니다.


그에 따라 AngularJs의 framework에서는 contoller보다는 service 부분이 더욱 TDD가 필요한 부분이라는 생각이 듭니다.

이 글에서는 AngularJs의 service를 테스트하는 방법을 알아 보겠습니다.


우선은 테스트할 대상이 되는 service가 있어야 겠지요.

현재 위치를 확인하는 용도로 gpsService를 만들어 보겠습니다.

getCurrentPosition function은 단말의 GPS 센서를 통해 위치를 가져오거나 navigator.geolocation을 통해서 위치를 가져오도록 구현할 예정입니다.

(참고로 후자의 경우는 protocol이 https가 아니라면 그 요청이 fail처리됩니다.)



gpsService


var app = angular.module('app', []);
app.service('gpsService', ['$q', 'nativeService', function ($q, nativeService) {
this.getCurrentPosition = function () {
var deferred = $q.defer();

// native call.
if (nativeService.isNative()) {
// check
nativeService.getCurrentPosition()
.then(function (position) {
deferred.resolve(new Position(position.lat, position.long);
}, function (fail) {
deferred.reject(fail); // turned off.
});
} else {
var geo = navigator.geolocation;
if (geo) {
try {
geo.getCurrentPosition(function (position) {
var coords = position.coords;
var position = new Position(coords.latitude, coords.longitude);
deferred.resolve(position);
}, function (fail) {
deferred.reject(fail.message);
});
} catch (err) {
deferred.reject(err);
}
} else {
deferred.reject('can\'t detect location via web.');
};
};
return deferred.promise;
};
}]);


promise를 활용하기 위해서 $q를, KEMP native를 연동하기 위하여 nativeService를 참조했습니다.

promise는 async call을 효과적으로 지원하기 위한 spec으로써, 자세한 내용은 link를 참고하시길 바랍니다. (#)

요점만 정리하면 async call에 대해서 성공한 요청은 resolve, 실패한 요청은 reject으로 그리고 기타 알림은 notify로 응답을 전달합니다.

(promise, deferred에 대해서 정말 잘 설명이 되어 있습니다.)


이 서비스는 nativeService를 이용할 수 있는 경우 (즉, service app이 device에서 수행되는 경우)에는 GPS 센서를 통해서 위치를 취득합니다.

그렇지 않은 경우에는 navigator.geolocation을 통해서 위치를 취득합니다.

위치 취득에 성공하는 경우 resolve를 반환하며 반대로 실패하는 경우에는 reject를 반환할 것입니다.

resolve로 전달하는 인자는 Position이라는 별도의 class로 포장하여 일원화하였습니다.



test code


describe('app.gpsService', function () {
// const
var timeout = 800;

// variable
var $rootScope;
var $q;
var gpsService;
var createService;


beforeEach(module('app'));
beforeEach(inject(function (_$rootScope_, _$q_) {
$rootScope = _$rootScope_;
$q = _$q_;
}));
beforeEach(inject(function ($injector) {
createService = function () {
return $injector.get('gpsService');
};
createMockService = function () {
nativeService = $injector.get('nativeService');
nativeService.isNative = function () { return true; };
nativeService.getCurrentPosition = function () {
var deferred = $q.defer();
deferred.resolve(new Position(37.4137701, 127.1284930));
return deferred.promise;
};
return $injector.get('gpsService');
};
}));
it('GPS서비스의 getCurrentPosition이 응답하는지 확인(device가 아닌 경우)', function (done) {
gpsService = createService();
var position, failreason;
var promise = gpsService.getCurrentPosition();
promise.then(function (_position) {
position = _position;
}, function (err) {
failreason = err;
});

setTimeout(function () {
$rootScope.$apply();
if (position) {
expect(position).toBeTruthy();
} else {
expect(failreason).toBeTruthy();
};
console.log('position', position);
console.log('failreaonse', failreason);
done();
}, timeout);
});
it('GPS서비스의 getCurrentPosition이 응답하는지 확인(device인 경우)', function (done) {
gpsService = createMockService();
var promise = gpsService.getCurrentPosition();
var position, failreason;
promise.then(function (_position) {
position = _position;
}, function (err) {
failreason = err;
});
setTimeout(function () {
$rootScope.$apply();
if (position) {
expect(position).toBeTruthy();
} else {
expect(failreason).toBeTruthy();
};
done();
}, timeout);
});
});
});


코드가 좀 기네요...

서비스의 코드량을 가뿐히 능가하고 있습니다. -_-;;


자아, 숨을 가다듬고 천천히 살펴봅시다.


describe는 진입점이라고 생각하시면 됩니다. 두 번째 인자인 function이 실제 역할을 수행하는 부분입니다.


beforeEach

beforEach는 각각의 test를 수행하기 전에 공통적으로 수행할 코드가 담기는 영역입니다.

테스트에 필요한 'app'을 로드하고 $rootScope, $q를 가져오는 기능을 하고 있습니다.


마지막으로 gpsService의 instance가 필요합니다.

이는 세 번째 beforeEach function을 참고하시길 바랍니다.

$injector를 인자로 넘김으로써 AngularJs의 $injector를 참조하고, 그 뒤에 $injector의 get function을 통해서 gpsService를 반환 받았습니다.


it

it의 첫 번째 인자는 test에 대한 설명 구문이며 두 번째 인자가 실제 test코드가 있는 function입니다.

이 function에 선택적으로 전달되는 done 인자는 비동기 function을 테스트하기 위해서 받는 인자이며, 비동기 function의 동작이 마무리 된 이후에 done()을 호출함으로써 테스트를 완료할 수 있습니다.


두 번째 test를 보면 createService() 가 아닌 createMockService()를 통해서 service의 instance를 가져옵니다.

MockService는 테스트를 위해 설정한 natvieService를 반환함으로써 실제 device인 척 가장하고 테스트를 할 수 있게 해 줍니다.

이는 mock을 이용한 테스트 기법입니다.


또한, jasmine을 통해 async function을 테스트하기 위해서는 $rootScope.$apply()을 호출할 필요가 있습니다.

그렇지 않은 경우에는 undefined가 반환됩니다.

왜 $rootScope.$apply()를 호출해야 하는 가에 대해서는 link를 참고하시길 바랍니다. (#)



Conclusion

gpsService의 최초 구조는 위와 같지 않았습니다. 좀 더 많은 function이 있었습니다.

그러나 TDD를 통해서 gpsService를 테스트하면서 gpsService를 참조하는 코드를 작성하면서 복잡함과 불편함을 느끼게 되었고,

이를 통해서 gpsService자체를 refactoring하게 되었습니다.

이 과정에서 nativeService를 분리하였고, @Justin이 그 구현을 도와줌으로써 일에 대한 효율적인 분배도 이뤄낼 수 있었습니다.


잦은 refactoring이 TDD의 핵심입니다.

단순 code coverage만이 목적이라면 개발하기 전에 test를 하라는 개발방법론은 의미가 없습니다.


잦은 refactoring의 원동력은 해당 module을 사용하는 입장이 되어 코드를 작성한다는 점입니다.

Test code는 또한 실제 module을 이용하는 개발자가 참고할 수 있는 훌륭한 sample code가 됩니다.


이렇게 해서 개발한 gpsService는 nativeService와 훌륭하게 연계하여, quickOn POC에 단번에 녹아들어 훌륭하게 동작하였습니다.

이것은 하루를 기분 좋게 만드는 정말로 좋은 경험이었습니다. :)



References

Jasmine expect's matchers => https://jasmine.github.io/api/2.6/matchers.html

inject custom service => https://nathanleclaire.com/blog/2014/04/12/unit-testing-services-in-angularjs-for-fun-and-for-profit/

=> https://coderwall.com/p/l4fvmq/injecting-custom-services-in-an-angularjs-unit-test

get currentPosition => https://developer.mozilla.org/ko/docs/Web/API/Geolocation/getCurrentPosition

setTimeout() method => https://www.w3schools.com/jsref/met_win_settimeout.asp

unit test promise $q => http://www.bradoncode.com/blog/2015/07/13/unit-test-promises-angualrjs-q/

AngularJs $q => https://docs.angularjs.org/api/ng/service/$q

promise controller example code => http://jsfiddle.net/jsengel/8fzmqy4y/

promise? deferred? => http://webframeworks.kr/tutorials/angularjs/angularjs_promise_deferred/

'Dev pattern' 카테고리의 다른 글

[펌] 자바스크립트 코딩 컨벤션 가이드  (0) 2017.10.16
WPF Developement  (0) 2017.07.21
Jasmine getting started to test javascript (Angular)  (5) 2017.05.26

제가 가진 TDD에 대한 관심과 열정은 아직 식지 않았습니다.

미국 현지 프로젝트를 통해서 constructor를 통한 dependency injection pattern을 숱하게 보면서 느낀 점도 많았고요, 아직 갈 길이 멀더라도 TDD는 선택이 아닌 당면한 과제가 된 것 같다는 인상을 받습니다.

 

다만, 계속 아쉬웠던 것은 사실상 현재 주로 작업하는 front-end, javascript에 대해서 (정확하게는 Angular)에 대한 TDD를 시도하지 못하고 있다는 점이었습니다.

1여년전에 도전했다가 angular node.js에 대한 이해 부족으로 미뤄왔던 것을, 이번 ionic2, eletron project를 통해서 node.js(정확하게는 npm)에 대한 경험이 쌓여 다시 시도해 보았습니다.

다행히 전보다는 쉽게 따라갈 수 있었고, 부족하지만 해당 내용을 공유하고자 합니다.

 

도구의 선택

Angular를 테스트하기 위한 최적의 옵션 중 하나는 karma & jasmine입니다.

Karma는 javascript code를 다양한 browser에서 테스트해 볼 수 있도록 도와주는 tool입니다.

JasmineTDD가 아닌 BDD(Behavior driven development) framework로써 자세한 내용은 다음 설명을 참고하세요.


Test-Driven Development : 비교적 새로운 소프트웨어 개발 기술로써, 프로세스는 아래와 같습니다.

 

개발된 코드의 특정 부분을 위한 시험(tests)을 작성합니다. 만약 계산기를 예로 들자면 양수, 음수 정수 등등을 더하는 시험을 작성합니다. 하지만 아직 실제 코드가 작성된 게 아니기 때문에, 테스트를 진행한다면 실패(fail)가 될 것입니다.

이제 시험에 연관된 개발코드를 작성합니다. 이 코드는 테스트를 성공(pass)하기 위한 것입니다.

테스트에서 성공하였다면 이제 작성한 코드를 제 위치에 리팩토링하며 집어넣습니다.

TDD는 개발자들이 개발의 명세(Specification)에 대해 개발을 직접 세밀하게 진행보기 전에, 보다 선명하게 생각할 수 있게 해줍니다. 또한 한번 작성된 시험은 언제나 유용합니다

 

Behavior-Driven Development : BDD는 명세를 작고 쉽게 작성합니다. 기본적으로 BDD는 아래 2개의 중심부분이 있습니다

 

테스트는 반드시 작고 한가지를 테스트해야합니다. 어플레키에션 전체를 테스트하는 대신, 작지만 많은 수의 시험(tests)을 작성합니다. 계산기를 예로 들자면 하나의 테스트는 '1더하기', 하나의 테스트는 '0더하기0', 또 다른 테스트는 '-5더하기6', 그리고 '1.2더하기3.1' 등을 작성하게 됩니다.

시험은 문장이어야 합니다. 자스민프레임워크와 계산기를 예로들면, 문장은 "Calculator adds two positive integers"와 같아야합니다. 그러면 테스팅프레임워크가 자동으로 테스트를 진행할 것 입니다.


출처: http://webframeworks.kr/getstarted/jasmine/

전 아직 위의 두 가지가 정확히 뭐가 다른 지 잘 모르겠네요.

 

Installing

우선 Node.js가 필요합니다.

https://nodejs.org/en/ 에 접속하여 LTS / Current version LTS를 다운로드 하여 설치합니다.

 

npm을 통해서 karma를 설치합니다.

콘솔창에서 다음 커맨드를 실행합니다.

Npm install karma --save-dev

--save-dev 옵션은 package.json을 자동으로 갱신하도록 합니다. 이를 통해서 package.json 파일의 devDependencies에 해당 module이 등록되는 것을 확인할 수 있습니다.

 

기본적인 준비는 마무리 되었습니다.

 

Test code 작성

describe('calculator', function () {

 

    beforeEach(module('myApp'));

 

    var $controller;

 

    beforeEach(inject(function (_$controller_) {

        $controller = _$controller_;

    }));

 

    describe('mainController test', function () {

        it('sample test', function () {

            var $scope = {};

            var controller = $controller('mainController', { $scope: $scope });

            expect($scope.greeting).toBe('Hello world');

            expect($scope.fnTest()).toBe(2);

        });

    });

});

Inject를 통해서 controllerinstance를 가져옵니다.

이 부분은 학습이 더 필요한 영역으로 경험치가 아직 없습니다만, jasmine BDD framework는 문서화가 잘되어 있으므로 손쉽게 따라할 수 있을 것으로 생각합니다.

다만, 위의 코드를 통해서 우리는 특정 controller의 instance를 가져올 수 있습니다.

그리고, 해당 controller의 property나 function을 테스트할 수 있게 되죠.

 

테스트 대상이 되는 controller는 다음과 같습니다.

var myApp = angular.module('myApp', []);

 

myApp.controller('mainController', ['$scope', function ($scope) {

    $scope.greeting = 'Hello world';

    $scope.fnTest = function () {

        return 2;

    };

}]);

 

 

Configuration for test runner

Karma를 통해서 자동 테스트를 수행하기 위해서, test에 대한 설정 파일을 생성해야 합니다.

Karma init karma.conf.js

Test framework > jasmine

Use require.js > no

Browser > Chrome

Location of test code > {test.js 파일의 path} 예를 들면 ‘test/*.js’

 

Run test

테스트를 위해서는 angular / angular-mocks / jasmine-core 등이 필요합니다. 아래의 devDependencies 항목을 package.json 파일에 붙여넣기를 한 후 저장합니다.

{

  "name": "tddtest",

  "version": "1.0.0",

  "description": "",

  "main": "index.js",

  "scripts": {

    "test": "echo \"Error: no test specified\" && exit 1"

  },

  "author": "Ethan",

  "license": "ISC",

  "devDependencies": {

    "angular": "^1.6.4",

    "angular-mocks": "^1.6.4",

    "jasmine-core": "^2.6.2",

    "karma": "^1.7.0",

    "karma-chrome-launcher": "^2.1.1",

    "karma-jasmine": "^1.1.0"

  }

}

 

Npm install을 수행하면 package.json에 선언한 module중 필요한 것을 자동으로 다운로드합니다.

 

거의 모든 준비가 완료되었습니다.

마지막으로 실제로 test간 사용할 파일의 karma config값을 수정해야 합니다.

// list of files / patterns to load in the browser

    files: [

      'node_modules/angular/angular.min.js',

      'node_modules/angular-mocks/angular-mocks.js',

      'app/*.js',

      'tests/*.js'

    ],

files arrayangular, angular-mock, source file 그리고 test file을 순서대로 작성합니다.

 

Karma start karma.conf.js를 수행합니다.

마지막 인자는 테스트 환경을 설정 파일을 지정합니다.



 

테스트 코드의 수행 결과가 출력됩니다.

Karma.conf.js 파일의 autoWatch 값을 true로 설정한 경우 Source code, test code 가 변경된 경우 자동으로 탐지하여 테스트 결과를 화면에 출력하여 줍니다.


References

https://www.youtube.com/watch?v=shqptAMZ_NM

https://www.youtube.com/watch?v=YRzr27Bpx_g

http://webframeworks.kr/getstarted/jasmine/

http://www.bradoncode.com/blog/2015/05/19/karma-angularjs-testing/


'Dev pattern' 카테고리의 다른 글

[펌] 자바스크립트 코딩 컨벤션 가이드  (0) 2017.10.16
WPF Developement  (0) 2017.07.21
AngularJs service unit test via jasmine.  (0) 2017.06.29

+ Recent posts