Обзор будущих технологий тестирования

Глеб Бахмутов

Applause

MathWorks

EveryScape

Тестирование?

Скучно ...

Приходится делать

Лишний груз

Напиши 100 тестов и заработай $0

Тестирование откладывается на последний момент

Программисты не видят смысла в тестировании во время разработки

Можно тестировать легко и быстро

лимит:  30 секунд или 2 команды

Дополнительная польза

документация, версии

// add.js
module.exports = function add(a, b) {
  return a + b;
};
npm install --save-dev gt
gt --bdd *-spec.js

1

2

Можно тестировать легко и быстро

// add-spec.js
var add = require('./add');
describe('addition', function () {
  it('adds 2 numbers', function () {
    console.assert(add(2, 3) === 5);
  });
});

Пример теста

gt --bdd add-spec.js
gt --bdd -t "add 2 numbers" *-spec.js

Тест!

результаты тестов

покрытие кода

Автоматическое тестирование

Плохой коммит не пройдет

npm install --save-dev pre-git
"pre-commit": "npm test"

1

2

Что мы тестируем?

npm install --save-dev karma-coverage
preprocessors: {
    '*.js': 'coverage'
},
reporters: ['progres', 'coverage']

1

2

покрытие кода тестами в GT по умолчанию

Код протестирован

code-coverage / index.html

{
  "/Users/kensho/git/quick-testing/add.js": {
    "path": "/Users/kensho/git/quick-testing/add.js",
    "s": {
      "1": 1,
      "2": 1
    },
    "b": {},
    "f": {
      "1": 1
    },
...

каждая строка кода

каждое условие

каждая функция

Код протестирован

code-coverage.json

За последний месяц

Каждый файл

Дополнительная польза от тестов

Данные для документации, простой код,

автоматический апгрейд версий

Автоматическая документация с примерами

1 пример "стоит" 10 страниц текста

/**
 * Creates an array with all falsey values removed. 
 * The values `false`, `null`,
 * `0`, `""`, `undefined`, and `NaN` are falsey.
 *
 * @static
 * @memberOf _
 * @category Array
 * @param {Array} array The array to compact.
 * @returns {Array} Returns the new array of filtered values.
 * @example
 *
 * _.compact([0, 1, false, 2, '', 3]);
 * // => [1, 2, 3]
 */
function compact(array) { ... }

xplain - тесты в примеры

/**
 * Creates an array with all falsey values removed. 
 * The values `false`, `null`,
 * `0`, `""`, `undefined`, and `NaN` are falsey.
 *
 * @static
 * @memberOf _
 * @category Array
 * @param {Array} array The array to compact.
 * @returns {Array} Returns the new array of filtered values.
 */
function compact(array) { ... }

lodash.js

/**
 * Creates an array with all falsey values removed. 
 ...
 */
function compact(array) { ... }

lodash.js

/**
 * @example compact
 */
it('removes falsy values', function () {
  assert(_.compact([0, 1, false, 2, '', 3]) === [1, 3, 3]);
});

compact-spec.js

/**
 * @example compact
 */
it('removes falsy values', function () {
  assert(_.compact([0, 1, false, 2, '', 3]) === [1, 3, 3]);
});

compact-spec.js

xplain lodash.js lodash-spec.js
_.compact([0, 1, false, 2, '', 3]); // [1, 3, 3]

Проще: README.md

/**
 * @example compact
 */
it('removes falsy values', function () {
  assert(_.compact([0, 1, false, 2, '', 3]) === [1, 3, 3]);
});

compact-spec.js

xplain lodash.js lodash-spec.js -o README.md
`_.compact` removes ...

### removes falsy values

    _.compact([0, 1, false, 2, '', 3]); // [1, 2, 3]

README.md

Сложный код

(function foo(a, b) {
  if (a === 42 && b === 20) {
    return baz(a) || 100;
  } else {
    doSomething(a, b);
    doSomethingElse(a + b, b - a);
  }
}(100, 40));
function isCondition(a, b) {
  return a === 42 && b === 20;
}
function compute(a) {
  return baz(a) || 100;
}
function foo(a, b) {
  if (isCondition(a, b)) {
    return compute(a);
  }
  doSomething(a, b);
  doSomethingElse(a + b, b - a);
}
foo(100, 40);

Как измерить?

function add(a, b) { return a + b }
// cyclomatic = 1
// halstead volume = 2
function abs(a) {
  return a >= 0 ? a : -a
}
// cyclomatic = 2
// halstead volume = 3

Измерить легко

CodeClimate.com

плохо

хорошо

автоматический апгрейд версий

{
  "name": "my app",
  "version": "1.0.0",
  "dependencies": {
    "foo": "0.5.0",
    "bar": "0.1.0"
  }
}
{
  "name": "my app",
  "version": "1.0.0",
  "dependencies": {
    "foo": "0.5.0",
    "bar": "0.1.0"
  }
}

foo: 0.5.1, 0.5.2, 0.6.0

bar: 0.2.0, 0.2.1, 1.0.0,

1.0.1

Можно ли перейти на foo@0.6.0 и bar@1.0.1 ?

Никто не знает ...

Можно ли автоматически

  1. Установить каждую новую версию

  2. Прогнать все наши тесты

  3. Если

    1. Все тесты работают - использовать новую версию

    2. Что-то перестало работать - оставаться на той же версии

Ответ: next-update

Качество без тестов ...

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

Что дальше?

Все ошибки - нам

Вся информация: стэк, переменные и т.д.

Легко и просто

npm install --save raven
var client = new raven.Client(
    'https://<key>:<secret>@app.getsentry.com/<project>');

1

2

Престиж в тестирование

Вспомогательный код можно выложить в открытый доступ

Возможность показать всем свои навыки

// typical AngularJS unit test
describe('typical test', function () {
    var $rootScope, foo;
    beforeEach(function () {
        angular.mock.module('A');
        // other modules
    });
    beforeEach(inject(function (_$rootScope_, _foo_) {
        $rootScope = _$rootScope_;
        foo = _foo_;
    }));
    it('finally a test', function () {
        $rootScope.$apply(); // for example
        expect(foo).toEqual('bar');
    });
});
ngDescribe({
    modules: 'A',
    inject: ['$rootScope', 'foo'],
    tests: function (deps) {
        it('finally a test', function () {
            deps.$rootScope.$apply();
            expect(deps.foo).toEqual('bar');
        });
    });
});

Краткость - сестра таланта

AngularJS testing library kensho/ng-describe

+ 1000 downloads / month

Мой код - моя визитная карточка

Обзор будущих технологий тестирования

слайды: slides.com/bahmutov

блог: glebbahmutov.com/blog/tags/testing

код: github.com/bahmutov

твиттер: @bahmutov