Unit test - Gulp + Karma + Jasmine + Istanbul
Acceptance test - CucumberJs + Selenium Web drive
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 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();
});
});
'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() {});
});
});
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');
});
});
Change and structure code
Solved bigger complexity problems
Selenium running time was
From 15-20 min and a lot of failing tests
Before any changes:
Use more selenium instances
Use separate selenium server
Make you environment Flexible
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
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
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
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:
Example:
gulp cucumber -e=DEV1 -i=4 -r=true -p=workspace --debug
-e environment
-i instances
-p folder location
--debug show more detail information
Local testing
Remote testing
Remote testing
Selenium server
Selenium server
Questions?