AngularJS 기반하에 TDD를 수행하거나 테스트 코드를 작성할 때 적합한 툴과 개념에 대해 이해를 하자
1. Unit Testing
- 단위 기능 테스트
- 추천 단위 테스트 툴
+ Mocha : 단순하며 유연한 테스트 프레임워크 (추천)
+ Jasmine : Behavior-Driven Development Framework for testing Javascript (추천)
+ QUnit : jQuery에서 사용
- 단위 테스트 만들기
+ 사용자 스토리 기반으로 만들어 보자
"사용자로써 나의 앱에 로그인을 하면 대시보드 페이지를 본다"
+ Story -> Features -> Units 로 구분을 한다
"로그인 폼"을 하나의 기능으로 선택한다
하나의 스토리는 여러 기능의 합집합이며, 각 기능의 정상작동 검증(Verification)을 거쳐서 하나의 스토리는 완성된다
+ 로그인 폼에 대한 Spec을 만든다 (BDD )
describe ('user login form', function() {
// critical : 기능의 검증
it ('ensure invalid email addresses are caught', function() {});
it('ensure valid email addresses pass validation', function() {});
it('ensure submitting form changes path', function() { });
// nice-to-haves
it('ensure client-side helper shown for empty fields', function() { });
it('ensure hitting enter on password field submits form', function() { });
});
- 위의 테스트 Spec을 AngularJS로 단위 테스트 만들기
+ 파일 : angular.js, angular-mocks.js, app.js(모듈명-App) , l oginController.js(LoginCtrl), loginService.js(LoginService)
+ beforeEach에서 angular.mock.module 통해 모듈을 셋업한다
beforeEach에서 angular.mock.inject 통해 controller에서 $scope 셋업한다 ( 참조 )
+ it 에서 inject 통하여 service를 셋업한다 (참조 )
describe ("Unit Testing Examples", function() {
beforeEach (angular.mock.module('App'));
it ('should have a LoginCtrl controller', function() {
expect(App.LoginCtrl).toBeDefined();
});
it('should have a working LoginService service', inject(['LoginService' ,
function(LoginService) {
expect(LoginService.isValidEmail).not.to.equal(null);
// test cases - testing for success
// 검증을 위한 테스트 데이터
var validEmails = [
'test@test.com',
'test@test.co.uk',
'test734ltylytkliytkryety9ef@jb-fe.com'
];
// test cases - testing for failure
var invalidEmails = [
'test@testcom',
'test@ test.co.uk',
'ghgf@fe.com.co.',
'tes@t@test.com',
''
];
// you can loop through arrays of test cases like this
for (var i in validEmails) {
var valid = LoginService.isValidEmail(validEmails[i]);
expect(valid).toBeTruthy();
}
for (var i in invalidEmails) {
var valid = LoginService.isValidEmail(invalidEmails[i]);
expect(valid).toBeFalsy();
}
}] ) // end inject
); // end it
}); // end describe
2. Integration Testing
- 통합 테스트는 end-to-end 테스트(E2E)이고, 수행순서대로 차례로 수행해 보는 것이다.
그리고 애플리케이션과 UI의 상태를 검증한다. 심지어 Visual적인 부분에 대한 검증 도 가능하다
- 추천 도구
+ Karma : AngularJS에서 공식사용 (추천)
+ CasperJS : headless browser로 사용
+ PhantomJS : headles brower
- 통합 테스트 만들기
+ "사용자로써 나의 앱에 로그인하여 대시보드 페이지를 본다"
describe ('user login', function() {
// 수행 및 결과 상태의 검증
it ('ensures user can log in', function() {
// expect current scope to contain username
});
it ('ensures path has changed', function() {
// expect path to equal '/dashboard'
});
});
- 위의 테스트 Spec을 AngularJS로 통합 테스트 만들기 (참조 )
+ 최근에는 Angular Scenario Test Runner와 같은 Protractor E2E test framework 이 새롭게 소개되고 있다
describe ("Integration/E2E Testing", function() {
// start at root before every test is run
beforeEach (function() {
browser().navigateTo('/');
});
// test default route
it ('should jump to the /home path when / is accessed', function() {
// 루트 패스로 이동 - 로그인 화면
browser().navigateTo('#/');
expect(browser().location().path()).toBe("/login");
});
it('ensures user can log in', function() {
// 로그인 화면인지 체크
browser().navigateTo('#/login');
expect(browser().location().path()).toBe("/login");
// assuming inputs have ng-model specified, and this conbination will successfully login
// 테스트 데이터를 통해 브라우져 로그인 submit 버튼 click 이벤트 발생시킴
input('email').enter('test@test.com');
input('password').enter('password');
element('submit').click();
// logged in route
// 로그인이 성공했을 경우 대시보드 패스에 있는지 검증
expect(browser().location().path()).toBe("/dashboard");
// my dashboard page has a label for the email address of the logged in user
// DIV의 id="email"의 html 값이 로그인시 입력한 값인지 검증
expect(element('#email').html()).toContain('test@test.com');
});
it('should keep invalid logins on this page', function() {
// 로그인 화면 인지 검증
browser().navigateTo('#/login');
expect(browser().location().path()).toBe("/login");
// assuming inputs have ng-model specified, and this conbination will successfully login
// 잘 못된 값을 넣었을 가지고 로그인 버튼 클릭
input('email').enter('invalid@test.com');
input('password').enter('wrong password');
element('submit').click();
// DIV id="message"에 failed메세지 있는지 검증
expect(element('#message').html().toLowerCase()).toContain('failed');
// logged out route
// 제대로 로그인을 못했으므로 로그인 패스 그대로 인지 검증
expect(browser().location().path()).toBe("/login");
}); // end it
}); // end describe
3. Mocking Services and Modules in AngularJS
- 목(Mock ) 서비스를 이용한 테스트 방법
- 목 오브젝트를 사용하는 이유는 외부적인 요소 의존적인 부분을 제거하고 테스트 하기 위해서 이다
- ngMock.$httpBackend : service에서 http login 호출을 하면 응답 JSON 객체를 넘겨주는 mock 객체를 만든다
it('should get login success',
inject(function(LoginService, $httpBackend) {
$httpBackend.expect('POST', 'https://api.mydomain.com/login')
.respond(200, "[{ success : 'true', id : 123 }]");
LoginService.login('test@test.com', 'password')
.then(function(data) {
expect(data.success).toBeTruthy();
});
$httpBackend.flush();
});
<참조>
- 원문 : Best Practices in AngularJS
- AngularJS Controller Unit Test 방법
- AngularJS Service Unit Test 방법
- Full Spectrum Testing(E2E) in Angular with Karma (필독)
- Intro ducing E2E Testing in Angular 동영상
VIDEO