Подтвердить что ты не робот

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 в них много логики, и я был бы рад видеть водонепроницаемый;)

4b9b3361

Ответ 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 работает медленно, и модульное тестирование выполняется быстро. Поэтому имеет смысл тестировать ваши взгляды и директивы с помощью модульных тестов, издеваясь над частью приложения, которая полагается на внешний сервер.

Директивы имеют высокую степень проверки. Контроллеры меньше. Контроллеры могут иметь много движущихся частей, и это может сделать их более трудными для тестирования. По этой причине я часто выступаю за создание директив. Результатом является более модульный код, который легче тестировать.