Unit test виды - лучшая практика
Может ли кто-нибудь поделиться опытом с просмотрами модулей тестирования? Я прочитал много уроков о том, как выполнять модульное тестирование с просмотрами, но все имеет некоторые недостатки.
Я пришел к следующему подходу. Это работает, но мне интересно, есть ли лучший способ сделать это. Есть также некоторые недостатки, которые я объясню позже. Я также делаю тесты E2E с транспортиром, но они всегда медленны, и поэтому я ограничу их до минимума.
Это мой контроллер. Он имеет две переменные, связанные с его $scope
, которые используются в представлении:
// test_ctrl.js
angular.module('app', [])
.controller('TestCtrl', ["$rootScope", "$scope", function ($rootScope, $scope) {
$scope.bar = "TEST";
$scope.jobs = [
{name: "cook"}
];
}]);
Вид принимает $scope.bar
в массив <span>
и $scope.jobs
в директиве ng-repeat
:
<!-- test.html the view for this controller -->
<span>
Bar is {{bar || "NOT SET"}}
</span>
<ul>
<li ng-repeat="job in jobs">{{job.name}}</li>
</ul>
И это тест:
describe('Controller: TestCtrl', function () {
beforeEach(module('templates'));
beforeEach(module('app'));
var TestCtrl, $rootScope, $compile, createController, view, $scope;
beforeEach(inject(function($controller, $templateCache, _$rootScope_, _$compile_, _$httpBackend_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = _$compile_;
createController = function() {
var html = $templateCache.get('views/test.html');
TestCtrl = $controller('TestCtrl', { $scope: $scope, $rootScope: $rootScope });
view = $compile(angular.element(html))($scope);
$scope.$digest();
};
}));
it('should test the view', function() {
createController();
expect(view.find("li").length).toEqual(1)
console.log($scope.jobs)
});
});
В функции beforeEach
я настрою контроллер. Функция createController
(которая вызывается из самих тестов) выводит представление из $templateCache
, создает контроллер с собственным $scope
, затем компилирует шаблон и запускает a $digest
.
Кэш шаблонов предварительно заполняется препроцессором karmas ng-html2js
// karma.conf.js
...
preprocessors: {
'app/views/*.html': 'ng-html2js'
}
...
При таком подходе у меня есть небольшая проблема, и некоторые вопросы:
1. Дополнительные $$ ключи hashKey в моих объектах от ng-repeat
expect($scope.jobs).toEqual([{name: "cook"}]);
в моем тесте выдает ошибку:
Expected [ { name : 'cook', $$hashKey : '009' } ] to equal [ { name : 'cook' } ]
Я знаю, что ng-repeat
добавляет эти ключи, но это глупо тестировать. Единственный способ, о котором я могу думать, - это отделить тесты контроллера и тесты просмотра. Но когда я проверяю массив jobs
внутри моего контроллера, $$hashKey
нет. Любые идеи, почему это происходит?
2. $scope
Когда я впервые пробовал это, у меня была только локальная область, определенная как $scope={}
, а не $scope = $rootScope.$new()
, как это было в других тестах контроллера. Но с помощью простого объекта в качестве локальной области я не смог его скомпилировать ($compile(angular.element(html))($scope);
выбрал ошибку).
Я также подумал, что неплохо передать сам $rootScope
как текущую локальную область для контроллера. Это хороший подход? Или есть какие-то недостатки, я еще не видел?
3. Лучшие практики
Я был бы очень рад узнать, как все остальные проводят модульные тесты в AngularJS. Я думаю, что взгляды должны быть проверены, потому что со всеми директивами angular в них много логики, и я был бы рад видеть водонепроницаемый;)
Ответы
Ответ 1
Я думаю, что то, что вы делаете, - отличный способ просмотра unit test. Код в вашем вопросе - хороший рецепт для тех, кто ищет представления unit test.
1. ng-repeat $$ hashKey
Не беспокойтесь о данных. Вместо этого проверьте результат различных операций, потому что это то, что вам действительно нужно в конце дня. Итак, используйте jasmine-jquery, чтобы проверить состояние DOM после создания контроллера и после моделирования click()
s и т.д.
2. $scope = $rootScope. $new() не проблема
$rootScope
представляет собой экземпляр Scope, а $rootScope.$new()
создает экземпляр ChildScope. Тестирование с помощью экземпляра ChildScope технически более корректно, потому что в процессе производства области управления являются экземплярами ChildScope.
BTW, то же самое касается директив единичного тестирования, которые создают изолированные области. Когда вы $compile
свою директиву с экземпляром ChildScope, изолированная область будет создана автоматически (это экземпляр Scope). Вы можете получить доступ к этой изолированной области с помощью element.isolateScope()
// decalare these variable here so we have access to them inside our tests
var element, $scope, isolateScope;
beforeEach(inject(function($rootScope, $compile) {
var html = '<div my-directive></div>';
// this scope is an instance of ChildScope
$scope = $rootScope.$new();
element = angular.element(html);
$compile(element)($scope);
$scope.$digest();
// this scope is an instance of Scope
isolateScope = element.isolateScope();
}));
3. +1 Тестирование просмотров
Некоторые люди говорят, что тестовые просмотры с помощью Protractor. Транспортир отлично подходит, если вы хотите протестировать весь стек: передний конец на задний. Тем не менее, Protractor работает медленно, и модульное тестирование выполняется быстро. Поэтому имеет смысл тестировать ваши взгляды и директивы с помощью модульных тестов, издеваясь над частью приложения, которая полагается на внешний сервер.
Директивы имеют высокую степень проверки. Контроллеры меньше. Контроллеры могут иметь много движущихся частей, и это может сделать их более трудными для тестирования. По этой причине я часто выступаю за создание директив. Результатом является более модульный код, который легче тестировать.