Code coverage 측면에서 가능한 모든 코드가 test 되어야겠지만, 사실 여러 가지 요인으로 이 수준을 달성하기가 쉽지 않습니다.
결국은 코드의 중요도를 매기고 그에 따라 선별적으로 TDD 적용을 시작하는 것이 합리적이라고 생각합니다.
그에 따라 AngularJs의 framework에서는 contoller보다는 service 부분이 더욱 TDD가 필요한 부분이라는 생각이 듭니다.
이 글에서는 AngularJs의 service를 테스트하는 방법을 알아 보겠습니다.
우선은 테스트할 대상이 되는 service가 있어야 겠지요.
현재 위치를 확인하는 용도로 gpsService를 만들어 보겠습니다.
getCurrentPosition function은 단말의 GPS 센서를 통해 위치를 가져오거나 navigator.geolocation을 통해서 위치를 가져오도록 구현할 예정입니다.
(참고로 후자의 경우는 protocol이 https가 아니라면 그 요청이 fail처리됩니다.)
gpsService
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는 진입점이라고 생각하시면 됩니다. 두 번째 인자인 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 |