아래 글은 얼마전 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

DA - 데이터 관점에서 업무 수행

DBA - DB 관점에서 업무 수행



[DA]

- 개발하려고 하는 업무 분석하여 Entity와 Attribute 추출 및 정의(전산화 대상 추출)

- ERD 작성

- 관계 설정

- 작성 문서 : Entity 정의서, ERD..


[DBA]

- 개발에 필요한 DB 설치, 관리

- DA가 만든 ERD로 부터 물리적인 테이블, Index, PK, FK 생성

- 개발자 교육 및 기술 자문(SQL 작성 관련)

- Data migration

- 작성 문서 : 설계서, 인덱스 정의서..



업무 시에 예로 들자면 

1. 변경 되는 사항은 DA가 먼저 모델에 반영을 하고 

2. 바뀐 모델로 DBA가 테이블 변경하고 

3. 프로그래머가 관련 프로그램 수정하는 

형태가 되겠네요. 



오늘 프로젝트에서 DBA의 업무에 대한 이슈가 있었습니다. 

DBA로 투입 되신 분께 신규 메뉴에 대한 Entity 추출 등을 요청 드렸는데(내가 요청 드린 건 아니고..)

이런 저런 이유로 철수 하면서 나온 철수 사유 중 

'분석하고 Entitiy 추출하는건 본인의 업무 롤이 아니다'라고 했다더라구요. 


그 얘기를 전해 듣고 그럼 DBA는 DB 튜닝하고 백업 정책 만드는 거 외에 

또 뭘 하는 위치인가...궁금증이 생겨서 검색 해 보니 DA라는게 나오네요. 


챙피하지만 전 처음 들은 용어. ㅡ,.ㅡ;;

검색 해 보니 우리가 몰라서 실수 한 것 같네요. 


그나저나 이 프로젝트는 어찌 되려나. 에혀..

(아 지금은 제가 DA, DBA 다 하는 중입니다. 으레히 그래 왔던 것 처럼 ㅜㅜ)







'Database > Common' 카테고리의 다른 글

Migration with MS Excel  (3) 2017.06.16

들어가며

자, 오늘도 TDD에 대한 얘기입니다. :-)


Mars(마르스 or 마즈, 마스 아님!!) version은 ionic2를 지원하죠.

ionic2는 Angular2를 근간으로 하고 있으며, Angular2의 문법은 Typescript입니다. (꿈과 같은 Google과 Microsoft의 콜라보레이션?)

Mars 미국식 [mɑ:rz] 발음 듣기 영국식 [mɑ:z] 발음 듣기 중요

화성


Mars [maʀs] 발음 듣기 

1. 마르스 2. 화성


이런 새로운 환경 하에서 우리는 또 다시 어떻게 하면 TDD를 할 수 있을지를 심각하게 고민해 보아야 합니다.


간단하게 ionic2 test with jasmine으로 검색해 봤습니다.


AngularJs를 공부하면서 신세를 졌던 조쉬 모로니(joshmorony) 사이트가 나오네요.

들어가서 읽어보고 테스트를 해 봅니다.

https://www.joshmorony.com/how-to-unit-test-an-ionic-2-application/


으흠...

테스트 환경을 구축하는 방법이 분리된 post에 정리되어 있네요.

https://www.joshmorony.com/introduction-to-testing-ionic-2-applications-with-testbed/


엄청 깁니다! 그렇지만 포기하지 않고 열심히 따라해 보았습니다.

그런데 이런 된장!! 안되네요...


예제에서 import 하는 일부 class들이 현재와 조금 달라 보입니다.

Angular2 guide에서 찾아보니 이제는 사용하지 않는다네요... OTL


open source라는 게 이렇게 위험합니다 여러분~!! (!true)


...


잠깐 정신을 깨끗하게 하려 해우소에 다녀왔습니다.


어떻게 구글신에게 물으면 원하는 답변을 얻을 수 있을 지 잠깐 고민해 봅니다.

Simple is best!!


'ionic2 unit test' 으로 검색합니다.


ionic2 forum이 보입니다. 아하!!

들어가서 열심히 뒤져봅니다.


건졌습니다!!

http://roblouie.com/article/376/ionic-2-set-up-unit-testing-the-best-way/


몇 가지 수정해야 할 부분이 있지만, guide 거의 그대로 test 가능한 project를 만들 수 있었습니다. :D



Install ionic2

당연히 ionic2 app이 있어야 겠지요.

http://ionicframework.com/getting-started/

물론, 그 전에 node.js가 필요합니다. (# => LTS version을 다운로드 받아 설치해 줍시다.)


install ionic

npm install -g cordova ionic

ionic에는 cordova가 필요하죠. 한 번에 같이 설치하는 명령입니다.


start an app

ionic start {appname} blank

빈페이지를 시작페이지로 갖는 ionic app을 만듭니다. {appname}으로 folder가 생기고 그 아래에 필요한 파일들이 설치됩니다. (대략 150MB)


run your app

ionic serve

app folder로 이동해서 명령어를 실행하면 빈 페이지를 가지는 app이 실행됩니다.



Setup for test

먼저 karma를 설치해야 합니다.

(app folder로 이동해서 실행해주세요.)

npm install -g karma-cli

-g option은 global하게 쓰게 설치하는 option입니다.


그 뒤 테스트에 필요한 module들을 추가로 설치합니다. 

npm install --save-dev @types/jasmine@2.5.41 @types/node html-loader jasmine karma karma-webpack ts-loader karma-sourcemap-loader karma-jasmine karma-jasmine-html-reporter angular2-template-loader karma-chrome-launcher null-loader

뭔가 엄청나게 많은 모듈들이 필요하네요.

가만히 보면 webpack, karma, jasmine 이런 녀석들입니다.


karma를 실행하기 위해서는 몇 가지 configuration이 필요합니다.

보통 config.js파일을 미리 만들어 두고, 이를 로드해서 테스트를 수행하는 형태를 띕니다.

아래 파일을 다운로드 받아서, project folder에 'test-config'라는 folder를 만들고 그 아래에 붙여 넣으세요.


test-config.zip


앞으로 우리가 만들 test code는 실제로 배포를 할 성질의 것이 아니므로, tsconfig.json파일을 수정해서 exclude하도록 합시다.

"exclude": [
"node_modules",
"src/**/*.spec.ts"
],


준비는 모두 마쳤습니다.

이제 test가 잘 되는지 test해 보죠. :-)


karma start test-config/karma.conf.js

참조한 post에서는 해당 명령어를 package.json파일에 선언해 두고, npm test라는 명령어로 수행했는데, 왠지 저는 안되더라고요. 혹시 solution을 아시면 댓글 남겨주세요.


문제 없이 세팅이 되었다면 아래와 같은 메시지를 보실 수 있을 겁니다.

webpack: Compiled successfully.

webpack: Compiling...


그러면, 이제부터는 unit test code를 만들어 보겠습니다.



Write a test

module로 구성된 Angular2의 기능들 중에서 Service라는 녀석은 공용으로 사용할 library같은 녀석입니다.

그런 역할을 가진 만큼 다른 어떤 module 보다도 테스트의 우선순위가 높은 녀석이라고 생각합니다.


그래서 간단한 기능을 가진 serivce를 만들고, 이를 테스트해 보겠습니다.

(Component module은 test하기 어려워서 대는 핑계입니다...)


파일의 위치는 아래와 같습니다.



book.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class BookService {

constructor() { }

public greeting(): string {
return 'hello';
}
}

greeting이라는 function을 호출하면 'hello'를 반환하는 기능 밖에는 없는 단순한 service입니다.

실제 service는 보통 ajax call 등을 수행하고 그 결과를 반환하는 역할을 하겠지요.

우리의 경우는 native call이 될 수도 있고요.


Angular2의 서비스는 어디에든 가져다 쓸 수 있게 끔 @Injectable이라는 데코레이션을 달아줘야 합니다. (.net에서는 비슷한 용도의 코드를 attribute라고 불렀던 것 같은데)


book.service.spec.ts

import { BookService } from './book.service';

describe('Service: BookService', () => {
let service : BookService;

beforeEach(() => {
service = new BookService();
});

it('should be create', () => {
expect(service).toBeTruthy();
});

it('should be say hello', () => {
expect(service.greeting()).toEqual('hello');
});
});

BookSerivce를 참조하기 위해서 import해주었습니다.

서비스는 test 이전에 매번 새로운 instance를 만들도록 beforeEach구문을 통해서 생성해 주었고요.


여기 두 개의 test가 있습니다.

하나는 service가 제대로 instance화 되었는 지를 먼저 확인합니다.

다른 하나는 greeting function이 'hello'를 반환하는 지 확인합니다.


결과가 다음과 같이 나왔습니다.




참고로, test code는 대상 source code와 같은 folder에 위치하고, 그 파일의 이름을 .spec.ts이라고 명명합니다.

왜냐하면, test-config/karma-test-shim.js 파일에 그렇게 찾아서 테스트하라고 정의했거든요.

var appContext = require.context('../src', true, /\.spec\.ts/);


저는 이제부터 M모 방송사의 POC 앱을 만들러 갑니다.


References

http://roblouie.com/article/376/ionic-2-set-up-unit-testing-the-best-way/

'Javascript' 카테고리의 다른 글

Implement base controller  (0) 2017.07.26
Service workers.  (0) 2017.07.06
npm lite-server  (0) 2017.07.06
Calculate distance between two geolocations.  (1) 2017.06.28

NY** project를 리뷰하려고 이전 메일을 찾아보던 중에, project 진행 간 인상적이었던 부분들을 정리했던 내용이 있어 다시 한 번 공유합니다.


abstract class를 상속(inherits)하여 구현하는 예제입니다.

Controller 생성자에 $controller를 전달한 뒤 하이라이트 코드와 같은 형태로 상속이 가능합니다.

$scope injecting하기 위해서 두 번째 param를 전달합니다.

이를 이용하면 duplicate code between controllers 문제를 풀 수 있습니다.

 

'use strict';
angular.module('Diary')
// base controller containing common functions for add/edit controllers
.controller('Diary.BaseAddEditController',
['$scope', 'DiaryService',
function ($scope, DiaryService) {
$scope.diaryEntry = {};
$scope.saveDiaryEntry = function () {
DiaryService.SaveDiaryEntry($scope.diaryEntry);
};
// add any other shared functionality here.
}])
.controller('Diary.AddDiaryController',
['$scope', '$controller',
function ($scope, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
}])
.controller('Diary.EditDiaryController',
['$scope', '$routeParams', 'DiaryService', '$controller',
function ($scope, $routeParams, DiaryService, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
$scope.diaryEntry = data;
});
}]);


Reference

http://jasonwatmore.com/post/2014/03/25/angularjs-a-better-way-to-implement-a-base-controller

'Javascript' 카테고리의 다른 글

Ionic 2 Unit test  (0) 2017.07.31
Service workers.  (0) 2017.07.06
npm lite-server  (0) 2017.07.06
Calculate distance between two geolocations.  (1) 2017.06.28

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의 완성도를 높이는 가장 높은 방법 중 하나입니다.


KEMP.Zipper란?

KEMP engine (Mars ver)에서 운용할 binary를 압축하는 application입니다.

본연의 압축 기능 이외에 지난 버전에서 문제된 Customizing의 어려움, serialize 요소 부재 등을 해결하기로 하였고,

지난 UWP project에서 활용한 MVVM pattern을 적용시켜보기 위해서, WinForm이 아닌 WPF platform의 practice를 만들어 보기로 하였습니다.


기능 개발 외적인 목표

새로운 platform으로 개발하는 만큼, 또한 TDD에 대한 관심과 경험이 어느 정도 쌓인 만큼, project진행 간 기능 개발 외적인 목표를 두었습니다.

 1. TDD를 하자

 2. WPF를 하자

 3. CLI를 만들어 보자

 4. Resource 통합 관리를 해보자

 5. WPF application 개발 practice를 만들어 보자


결과적으로 WPF는 ModernUI(MUI) Template을 적용하는 등의 목표 대비 추가 달성을 하였으나,

UX 설계 및 테스트 과정에 아쉬움이 남는 project였습니다.


Solution overview



 Project

Description 

Remark 

KEMP.Zipper.App

WPF app 

MVVM 

KEMP.Zipper.BetaApp 

WPF app (beta) 

개발자 UI 

KEMP.Zipper.CLI 

Command Line Interface 

Console에서 기동(미완) 

KEMP.Zipper.Configuration 

Define configuration 

AppSetting 대체 

KEMP.Zipper.Core 

Zip, Validation 

압축 및 유효성 검증 로직 구현 

KEMP.Zipper.Model 

Define model 

Model class 선언 

KEMP.Zipper.Resources 

Define resource 

Resource 통합 관리 

KEMP.Zipper.Test 

Test project 

 




KEMP.Zipper.App / BetaApp

과거에 사용 경험이 있던 MVVM light NuGet을 사용하였습니다.

전에는 단순 MVVM pattern 개발을 위해서 사용했는데, ViewModelLocator에서 IoC(Inversion of Control) 처리가 손쉽게 되어 ViewModel에 필요한 Service를 손쉽게 전달할 수 있었습니다.


MVVM light가 제공하는 ViewModelLocator에서 IoC처리에 대한 선언을 하고, ViewModel을 property로 노출시킨 다음, View에서는 ViewModelLocator에서 그 ViewModel의 property를 참조하여 Data binding을 수행합니다.

ViewModel의 생성자에서 필요로하는 service interface는 MVVM의 IoC를 통해서 제공됨으로써 별도의 처리가 필요치 않습니다.

Service와 ViewModel의 분리하여 테스트할 수 있는 장점이 생기게 됩니다.


Metro UI라고도 불리는 Modern UI Template을 적용하였습니다.


이 틀을 벗어나는 UI를 만들기 위해서 Template 자체의 source를 까서 수정해야 했다...


이 Template을 사용함으로써 기존의 Winform application과 시각적으로 큰 차별화를 이루었으나, UI Design이 꽤 제한되는 상황이라 이를 customize하느라고... 상당히 애를 먹었습니다 :-D

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


KEMP.Zipper.Configuration

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="appStructureSection"
type="KEMP.Zipper.Configuration.AppStructureSection, KEMP.Zipper.Configuration" requirePermission="true"
restartOnExternalChanges="true"
allowLocation="true" />
</configSections>
<appStructureSection>
<angular>
<add rootFolder="client"
items="pages;app.js;route.json;index.html;" />
<add rootFolder="server"
items="BLP;Configuration;Configuration\AppSettings.xml;Metadata\...;" />
</angular>
<angular2>
<add rootFolder="client"
items="pages;app.js;route.json;index.html;" />
<add rootFolder="server"
items="BLP;Configuration;Configuration\AppSettings.xml;Metadata\...;" />
</angular2>
</appStructureSection>
</configuration>

자세한 내용은 아래의 link를 참고하시길 바랍니다.

요약하면 appStructureSection element에 대한 설계를 담고 있는 project이며, App.config로부터 해당 내용을 읽어들이는 logic을 포함하고 있습니다.

http://bandcy.tistory.com/entry/ConfigurationSection을-이용한-App-configuration


KEMP.Zipper.Core

public interface IZipper
{
event EventHandler<ProgressEventArgs> ProgressChanged;
void Zip();
void Validate();
string TargetPath { get; set; }
string ProjectPath { get; set; }
string ZipName { get; }
ValidationError[] Messages { get; }

void Serialize();
void Deserialize();
}

본 Application의 주가 되는 기능. 즉, Zip, Validation, Serialize/Deserialize 작업을 수행하는 project입니다.

또한, Zip Progress를 Event로 노출하여 progess의 진행률을 바깥 쪽에서 확인할 수 있도록 하였습니다.


IZipper를 상속받는 Zipper(AngularJs ver), Angular2Zipper(Angular2 ver)용을 구현하였습니다.


KEMP.Zipper.Model

ZipModel, ValidationError 등에 대한 선언을 담고 있습니다.

ZipModel은 사용자가 project path를 전달했을 때, 그 경로를 통해서 company, project 등의 기본적인 정보를 파악하는 기능을 가집니다.

보편적으로 DB작업을 하는 경우, Service, DAO tier에서 공통으로 사용하는 Model을 별도의 project로 분리하는 pattern을 자주 사용했던 패턴입니다.

습관적으로 본 project에서도 Model project를 별도로 분리하였으나, 완료한 현 시점 뒤돌아 보니 굳이 Core와 분리할 필요가 없었을 것 같습니다.


KEMP.Zipper.Resources

사용자에게 노출하는 문장을 통합 관리하기 위한 별도의 project

내부적으로 resource dictionary와 resource key를 인자로 sentence를 반환하는 기능만을 제공함. 이 key value pair를 MJ에게 전달하여 검수했으며, 그 결과를 손쉽게 적용할 수 있었습니다.

향후 다국어를 지원한다면, 당 project를 확장하여 제공하는 것으로 충분할 것으로 기대합니다.


KEMP.Zipper.Test

Project type은 Unit project이며, Test가 반복적으로 사용하는 상수는 별도의 Consts.cs 파일에 취합해 두고 참조하는 형태로 진행하였습니다.Model, Core, App 등의 project를 테스트하기 위한 목적의 project.

코드를 refactoring할 때마다, Run All 수행하면서 수정한 code의 side effect 여부를 체크하였습니다.



모두 녹색 불이 들어왔을 때의 짜릿한 기분이란!!


Slack의 활용

Project 진행 간 공지사항 및 issue 등을 별도의 #kempzipper라는 채널을 통해서 통합관리함으로써, 아 그게 뭐였던라…? 하던 궁금증을 손쉽게 찾아볼 수 있도록 하였습니다. 또한, 나중에 project에 참여한 member도 그 history를 손쉽게 파악 가능할 수 있었다고 생각합니다.

Members

 

@ally, @fireclon profile 사진 좀 바꿔요...


형상관리의 부재

이런 저런 이유로 형상 관리 시스템을 사용하지 않았는데, TDD에 의거하여 수시로 refactoring하다가 긴급하게 배포해야 할 타이밍에 배포할 수 없는 상황과 마주하게 되었습니다. 막판에 구두로 커뮤니케이션 한 부분에서 미스가 발생했습니다.

남은 시간 동안 마무리 할 수 없는 상황인지라, Beta라는 딱지를 달고 이전에 테스트했던 개발자 UI를 부랴부랴 복구하여 end user에게 전달했습니다.

Refactoring하면서 비난 받던 개발자 UI를 완전히 엎어버린게 얼마나 후회가 되던지...

잦은 refactoring을 수반하는 TDD는 형상관리가 필수적이다라는 것을 깨닫게 되었습니다.


개발자 UI라는 말은 듣기 싫지만, 이렇게 만드는 건 너무 편하다!!



회고

시간이 없어서 TDD를 못한다는 얘기는 자주 듣게되는 변명입니다.

그런데 저도 project 막바지에 App project 부분을 작업할 때, 시간이 없다는 핑계로 TDD를 못했습니다.

MVVM pattern에서 ViewModel에 대한 test practice를 좀 더 연구해 봐야겠습니다.

local file system에 대한 mock을 주입하는 것을 해 보지 못한 것도 매우 아쉬운 부분입니다.


이 화면을 만들기 위해서 나는 그렇게 고생을 했나 보다.


References

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

https://mvvmlight.codeplex.com/


AppCache(Application Cache)를 적용할 때, 가끔씩 석연치 않은 동작이 발견될 때가 있다는 제보가 들어왔습니다.

하여, AppCache를 사용할 때의 유의점 또는 자체적인 문제가 있는 지를 알아보겠습니다.



AppCache?

AppCache는 웹어플리케이션을 오프라인에서 동작할 수 있도록 지원하기 위해서 개발된 메카니즘입니다.

이 기술을 적용함으로써 다음과 같은 이점을 가질 수 있습니다.


오프라인 브라우징: 사용자가 오프라인인 상태에서도 페이지를 전환할 수 있다.

속도: 캐시된 리소스는 로컬에 위치하며, 더욱 빠르게 로드된다.

서버 부하 감소: 서버에서 변경된 리소스만을 다운로드 한다.


자세한 내용은 아래의 링크를 참조하세요.

https://developer.mozilla.org/ko/docs/Web/HTML/Using_the_application_cache

https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache


여기에 중요한 메시지가 있는데, 영문 페이지에서만 보여집니다. -_-;;

그 내용은 아래와 같습니다.

Deprecated

This feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Avoid using it and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

AppCache는 웹표준에서 삭제되었으며, 아직까지는 많은 browser가 이를 지원하지만 점차 퇴출될 것이다.


영문 위키피디아에서 AppCache를 찾아보니, 대신 Service worker를 사용하라고 권고하고 있네요.

https://en.wikipedia.org/wiki/Cache_manifest_in_HTML5


AppCache는 어떤 문제가 있을까요?


재미있게도 Service worker guide 문서에서 잘 정리된 내용을 찾을 수 있었습니다.

AppCache의 디자인이 단일페이지에서만 특히 잘 동작하고 복수의 페이지를 가진 환경에서 훌륭하게 동작하지 않으며, 실제로 많은 문제를 가지고 있다고 합니다. 문제에 대한 내용은 link를 참조하세요. (#)



Service worker?

오프라인을 통제할 수 있는 권한을 개발자에게 부여하여 오프라인 환경을 지원할 수 있도록 해주는 API 입니다.

chrome browser를 통해 google, youtube, facebook이 notification을 띄우는 녀석의 정체가 드러났습니다. :-)


Service worker는 DOM에 직접 접근할 수 없습니다.

Service worker를 사용하기 위해서는 Promise에 대한 이해가 필수입니다.

또한 개발 중에 localhost를 통해서 사용할 수는 있으나, 이를 사용하기 위해서는 서버에 https 설정이 필요합니다.


Service worker 등록

if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}


serviceWorker를 모든 browser가 지원하는 것이 아니기 때문에, 우선 지원여부를 판단해야 합니다.

mobile 쪽 browser별 지원 상황을 보면, 현재는 chrome, fireFox만 지원하고 있습니다.

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API


지원하는 browser라면 별도의 파일(sw.js)에 선언한 내용을 service worker로 등록합니다.

그 뒤 등록 결과를 log로 출력합니다.


설치 (Offline cache 예제)

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/static_pages/greeting.html',
'/assets/test.jpg'
];

self.addEventListener('install', function (event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
console.log('설치완료');
return cache.addAll(urlsToCache);
}).catch(function () {
console.log('설치실패')
})
);
});


urlsToCache에 선언한 resource를 cache에 등록합니다.

특정 이름의 cache에 url array를 등록하는 절차를 거칩니다.

이름이 필요한 이유는 향후 업데이트 등에서 활용하기 위함입니다.

이를테면, 더 이상 cache할 필요가 없는 url에 대해서 삭제를 하는 등의 활용이 있겠죠.



Service worker의 문제

개발되고 있는 최신 기술이기 때문에 아직 문제가 있다고 합니다.

지원하지 않는 browser가 있다는 것이 가장 큰 문제일 것이며, 설치 실패 시 처리가 매끄럽지 않은 부분이 있을 수 있습니다.

또한 https 환경이 아닌 경우 적용할 수 없다는 점도 AppCache의 대용으로 사용하기에는 조금 문제가 있는 부분입니다.


처참한 mobile browser 지원 현황



Service worker 관리

위의 예제를 테스트하면 service worker가 browser에 등록됩니다.

등록된 service workers는 chrome의 개발자 도구에서 관리할 수 있습니다.



Application tab의 Service workers sub tab을 click하면 목록이 나타나며,

등록한 항목을 찾아 Unregister 버튼을 클릭하는 것으로 삭제할 수 있습니다.



더 자세한 내용은 공식 guide를 참조하시길 바랍니다.

https://developers.google.com/web/fundamentals/getting-started/primers/service-workers?hl=ko



References

AppCache

https://developer.mozilla.org/ko/docs/Web/HTML/Using_the_application_cache

https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache

https://msdn.microsoft.com/ko-kr/library/hh673545(v=vs.85).aspx

http://b.mytears.org/2010/09/2272

https://mytory.net/archives/12616

http://blog.jamesdbloom.com/ProblemsWithApplicationCache.html

https://en.wikipedia.org/wiki/Cache_manifest_in_HTML5


ServiceWorker

https://developers.google.com/web/fundamentals/getting-started/primers/service-workers?hl=ko

https://vnthf.github.io/blog/serviceworker/



'Javascript' 카테고리의 다른 글

Ionic 2 Unit test  (0) 2017.07.31
Implement base controller  (0) 2017.07.26
npm lite-server  (0) 2017.07.06
Calculate distance between two geolocations.  (1) 2017.06.28

Client의 View(html, css) 및 Controller(javascript)를 browser를 간단하게 browser를 통해서 확인하려면 어떻게 할까요?

WAS를 설치하고 파일을 복사하고 web browser를 실행시켜서 url로 접속??

이건 정석적이지만, 절차가 너무 번거롭습니다.


혹은 file:// protocol을 이용하는 것도 하나의 방법입니다.

다만, 이 경우에는 localhost의 특수성을 이용할 수 없습니다.

이를테면 registration of worker service, navigator.geolocation 등은 https protocol인 경우 또는 localhost인 경우에만 정상적으로 동작합니다.



이를 어떻게 손쉽게 테스트할 수 있을까 고민하게 됩니다.

그리고 npm에서 그 해결책을 발견했습니다.


npm은 정말 보물창고이네요. :)


이러한 목적에 부합하는 lite-server라는 module이 있습니다.


lite-server? (#)

공식적인 소개 문구는 다음과 같습니다.

"Lightweight development only node server that serves a web app, opens it in the browser, refreshes when html or javascript change, injects CSS changes using sockets, and has a fallback page when a route is not found."

웹앱을 브라우저에서 열거나 html 또는 javascript의 변경을 감지하고, socket을 이용하여 css변경을 주입하는 등의 기능을 제공하는 가벼운 노드 서버입니다. 그리고, route를 찾을 수 없는 경우에는 fallback페이지를 출력합니다.


local NPM install

설치: npm install lite-server --save-dev


global installation

설치: npm install -g lite-server

실행: lite-server


command를 실행하는 folder의 ./index.html 파일을 launch합니다.


References

https://www.npmjs.com/package/lite-server

'Javascript' 카테고리의 다른 글

Ionic 2 Unit test  (0) 2017.07.31
Implement base controller  (0) 2017.07.26
Service workers.  (0) 2017.07.06
Calculate distance between two geolocations.  (1) 2017.06.28

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

두 좌표 간의 거리를 구하라는 task가 떨어졌습니다.

어떻게 계산하면 좋을까요?


저는 처음에 피타고라스의 정리를 이용하면 될 것 같았어요.

피타고라스의 정리에 대한 이미지 검색결과

a^{2}+b^{2}=c^{2}\,


이 그림 어때요? 오랜만이죠??



그런데, 지구 상의 두 좌표의 거리를 구하기 위해서는 Haversine formula를 적용해야 할 필요가 있습니다.

왜냐하면 지구는 평면이 아니기 때문입니다.


그 공식은 아래와 같습니다.



코딩해 보죠...

자 그럼 먼저...



으음...




하아...


찾아봅시다... :)



function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1); 
  var a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ; 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km
  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}

Stack overflow를 보니 Chunck라는 친구가 javascript function을 작성해서 공유해 주었습니다.

친절하게 단위도 km네요. 저는 mile을 써야 하는데 ㅋ


그리고 Salvador Dali라는 친구가 코드를 최적화 해 줬습니다.

처리 속도가 2배 빠르다고 합니다.


function distance(lat1, lon1, lat2, lon2) {
  var p = 0.017453292519943295;    // Math.PI / 180
  var c = Math.cos;
  var a = 0.5 - c((lat2 - lat1) * p)/2 + 
          c(lat1 * p) * c(lat2 * p) * 
          (1 - c((lon2 - lon1) * p))/2;

  return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
}

이렇게요.

최종 반환 값에 mile 변환을 위한 상수 0.621371를 곱하면 원하는 mile단위의 거리를 가져올 수 있겠네요.



결과를 검증해 보겠습니다.



google map에서 사무실 근처의 좌표를 가져왔습니다.

좌표는 각각 37.4137701, 127.1284930 / 37.4073826, 127.1338793이구요.

직선 거리는 약 853.75m가 나왔습니다.




jsFiddle에서 해당 function을 테스트해 보았습니다. (#)

좌표를 넣고 계산한 값을 console로 출력했습니다. km/mile 단위로


그 결과는 아래와 같습니다.

distance 0.854860114404647 km

distance 0.5311852841477299 mile


853.75 | 854.86 약 1km 거리에서 약 1m 정도 차이가 나네요.

허용 가능한 오차 수준이라고 우겨봅니다.



References

https://en.wikipedia.org/wiki/Haversine_formula

https://stackoverflow.com/questions/27928/calculate-distance-between-two-latitude-longitude-points-haversine-formula

https://jsfiddle.net/n5d7tgez/

'Javascript' 카테고리의 다른 글

Ionic 2 Unit test  (0) 2017.07.31
Implement base controller  (0) 2017.07.26
Service workers.  (0) 2017.07.06
npm lite-server  (0) 2017.07.06

+ Recent posts