HTML5 toolkit
Testing
Testing UI
Unit test - Gulp + Karma + Jasmine + Istanbul
Acceptance test - CucumberJs + Selenium Web drive
Unit tests beggining
have way more examples of unit test
cover base code parts instead of selenium
What we need to:
deal with higher complexity code parts
Unit test
Unit test example:
'use strict';
var LivePreviewModule = require('src/app/workspace/live-preview/module');
describe('LivePreview', function(){
/*
* Test cases
*/
it('should LivePreviewModule to be defined', function() {
// Expectations
expect(LivePreviewModule).toBeDefined();
});
});
Unit tests mocks, stabs
'use strict';
var ComponentsServiceModule = require('src/app/components/components-service/module.js');
var componentsMock = require('test/unit/components/mockComponents.js');
describe('ComponentsService', function() {
var q,
deferred,
FileContentGetDeferred,
FileContentSaveTextFileDeferred,
RESTServiceMock,
ComponentManagerMock,
scope,
componentsService,
sourceFileMock,
FileContentMock,
ArrayUtilsMock,
UiBlockingServiceMock,
TemplatesDomMock,
BannerServiceMock,
componentSettingsMock,
expectedComponents;
beforeEach(function() {
expectedComponents = {
'id1': {},
'id2': {}
};
componentSettingsMock = {};
RESTServiceMock = {
getBanner: function() {
deferred = q.defer();
return deferred.promise;
},
updateAsset: function() {}
};
BannerServiceMock = {
getBanner: function() {
return {
assets: [{
source: 'fileId',
clicktags: []
}]
};
}
};
ComponentManagerMock = {
register: function() {},
registry: {
'animation': { hidden: true },
'positioning': { hidden: true },
'clicktag': { hidden: true }
},
getComponents: function() {
return expectedComponents;
},
read: function() {
return componentSettingsMock;
},
remove: function() {},
write: function() {}
};
FileContentMock = {
get: function() {
FileContentGetDeferred = q.defer();
return FileContentGetDeferred.promise;
},
saveTextFile: function() {
FileContentSaveTextFileDeferred = q.defer();
return FileContentSaveTextFileDeferred.promise;
}
};
ArrayUtilsMock = {
removeObjectByProperty: function(array, property, value) {
for (var i = 0, l = array.length; i < l; i++) {
if (array[i][property] === value) {
return array.splice(i, 1);
}
}
},
findObjectByProperty: function() { return undefined; },
findObjectIndexByProperty: function() { return -1; }
};
UiBlockingServiceMock = {
'enable': function() {},
'disable': function() {}
};
TemplatesDomMock = {
create: function() {
return {
reset: function() {},
render: function() {}
};
}
};
sourceFileMock = {
id: 'fileId'
};
angular.mock.module(ComponentsServiceModule);
angular.mock.module(function ($provide) {
$provide.value('ComponentsRegistry', componentsMock);
$provide.value('ComponentManager', ComponentManagerMock);
$provide.value('TemplatesDOM', TemplatesDomMock);
$provide.value('FileContent', FileContentMock);
$provide.value('ArrayUtils', ArrayUtilsMock);
$provide.value('BannerService', BannerServiceMock);
$provide.value('RESTService', RESTServiceMock);
$provide.value('UiBlockingService', UiBlockingServiceMock);
});
spyOn(ComponentManagerMock, 'register').and.callThrough();
spyOn(ComponentManagerMock, 'read').and.callThrough();
spyOn(FileContentMock, 'get').and.callThrough();
spyOn(FileContentMock, 'saveTextFile').and.callThrough();
spyOn(UiBlockingServiceMock, 'enable').and.callThrough();
spyOn(UiBlockingServiceMock, 'disable').and.callThrough();
inject(function(ComponentsService, $q, $rootScope) {
componentsService = ComponentsService;
q = $q;
scope = $rootScope.$new();
});
});
it('should register all components', function() {
expect(ComponentManagerMock.register.calls.count()).toEqual(componentsMock.length);
});
describe('loadComponents, getComponents', function() {
var promise,
resolvedComponents;
beforeEach(function() {
promise = componentsService.loadComponents(sourceFileMock);
FileContentGetDeferred.resolve('');
promise.then(function() {
resolvedComponents = componentsService.getComponents();
});
});
it('should load components from given file', function() {
var expectedComponentsLength;
expectedComponents = {
'id1': {},
'id2': {},
'id3': {}
};
expectedComponentsLength = Object.keys(expectedComponents).length;
scope.$apply();
expect(resolvedComponents.length).toEqual(expectedComponentsLength);
});
it('first loaded component should have 2 children', function() {
expectedComponents = {
'id1': { id: 'id1'},
'id2': { id: 'id2', pid: 'id1' },
'id3': { id: 'id3', pid: 'id1' }
};
scope.$apply();
expect(resolvedComponents[0].$children.length).toEqual(2);
});
});
describe('saveComponents', function() {
beforeEach(function() {
componentsService.loadComponents(sourceFileMock);
FileContentGetDeferred.resolve('');
expectedComponents = {
'id1': { id: 'id1', type: 'button'},
'id2': { id: 'id2', pid: 'id1', type: 'positioning' },
'id3': { id: 'id3', pid: 'id1', type: 'animation' },
'id4': { id: 'id4', pid: 'id1', type: 'clicktag' }
};
scope.$apply();
});
it('should save components to file', function() {
componentsService.saveComponents();
expect(FileContentMock.saveTextFile.calls.count()).toEqual(1);
});
it('should block UI while saving components', function() {
componentsService.saveComponents();
expect(UiBlockingServiceMock.enable.calls.count()).toEqual(1);
});
it('should unblock UI after successful save', function() {
componentsService.saveComponents();
FileContentSaveTextFileDeferred.resolve();
scope.$apply();
expect(UiBlockingServiceMock.disable.calls.count()).toEqual(1);
});
it('should reset components model after successful save', function() {
var model = componentsService.getModel();
model.changes = 'notVisible';
componentsService.saveComponents();
FileContentSaveTextFileDeferred.resolve();
scope.$apply();
expect(model.changes).toBe('none');
});
it('should keep selected component after save', function() {
var model = componentsService.getModel(),
lastSelected;
model.selected = { id: 'componentId' };
lastSelected = model.selected;
componentsService.saveComponents();
FileContentSaveTextFileDeferred.resolve();
scope.$apply();
expect(model.selected).toEqual(lastSelected);
});
it('should remove disabled components before save', function() {});
it('should sync asset\'s and components\' clicktags before save', function() {});
});
});
Writing Unit tests
From 32 test in 1 month without code coverage
To 157 in 5 month with code coverage
'use strict';
var WorkspaceModule = require('src/app/workspace/module.js');
describe('WorkspaceService', function() {
var workspaceService;
beforeEach(function() {
angular.mock.module(WorkspaceModule);
inject(function(WorkspaceService) {
workspaceService = WorkspaceService;
});
});
it('should be defined', function() {
expect(workspaceService).toBeDefined();
});
it('should return model', function() {
expect(workspaceService.getModel().topBar).toBeDefined();
});
it('should set leftSidebar active tab to settings',function() {
workspaceService.openSidebar('settings');
expect(workspaceService.getModel().rightSidebar.collapsed).toEqual(false);
expect(workspaceService.getModel().leftSidebar.activeTab).toEqual('settings');
});
it('should set rightSidebar collapsed to true when toggle called second time',function() {
workspaceService.toggleSidebar('settings');
workspaceService.toggleSidebar('settings');
expect(workspaceService.getModel().rightSidebar.collapsed).toEqual(true);
expect(workspaceService.getModel().leftSidebar.activeTab).toEqual('');
});
it('should close active rightSidebar and open file-tree if leftSidebar from settings set to file-tree',function() {
workspaceService.openSidebar('settings');
workspaceService.openSidebar('file-tree');
expect(workspaceService.getModel().rightSidebar.collapsed).toEqual(false);
expect(workspaceService.getModel().leftSidebar.activeTab).toEqual('file-tree');
});
});
Unit tests code coverage Istambul
Unit tests code coverage Istambul
Unit test optimization results
Change and structure code
Solved bigger complexity problems
Allow us:
More information about selenium setup with cucumber
see in Vilmantas slides
Acceptance tests
Selenium running time was
From 15-20 min and a lot of failing tests
Before any changes:
How to improve?
Use more selenium instances
Use separate selenium server
Make you environment Flexible
How to use more instance
So we have .feature files and have a lot of scenarios.
One scenario per single feature file one
We have 27 feature files
After splitting we have 106 feature/scenario files
And make "card dealing" to instance folder
What Gulp task do?
2. Read all feature scenario
1. Read all feature folders
3. Separate scenario to single files.
4. Move scenarios to instance folders
5. Run tests
6. Show output on demand "TeamCity or Local"
7. Clean instance folders after test done
Separate selenium server
On selenium instance for one cpu core?
From 16 core and 16 instances in 3-4 min stability issue.
4 instances and stable tests in 6-7min
Works for us
Flexible environment
Dev1 , Dev3 ,Preprod , Prod
Have ability to change environments
Change number of instances remotely and locally
Run single test folder
Max 16 instances
Change test output
Default, Teamcity, debug
Cucumber Gulp task allowed us to:
Flexible environment
Example:
gulp cucumber -e=DEV1 -i=4 -r=true -p=workspace --debug
-e environment
-i instances
-p folder location
--debug show more detail information
Flexible environment
Local testing
Flexible environment
Remote testing
Flexible environment
Remote testing
Selenium server
Selenium server
Questions?
Adform HTML5 toolkit testing
By Darius Laurinčikas
Adform HTML5 toolkit testing
- 1,103