Модульное тестирование частных функций с моккой и node.js
Я использую mocha для unit test приложения, написанного для node.js
Интересно, возможно ли использовать функции unit test, которые не были экспортированы в модуле.
Пример:
У меня есть много функций, определенных как в foobar.js
function private_foobar1(){
...
}
function private_foobar2(){
...
}
и несколько функций экспортируются как общедоступные:
exports.public_foobar3 = function(){
...
}
Тестовый пример структурирован следующим образом:
describe("private_foobar1", function() {
it("should do stuff", function(done) {
var stuff = foobar.private_foobar1(filter);
should(stuff).be.ok;
should(stuff).....
Очевидно, это не работает, поскольку private_foobar1
не экспортируется.
Каков правильный способ индивидуального тестирования частных методов? Мокка имеет встроенные методы для этого?
Ответы
Ответ 1
Если функция не экспортируется модулем, она не может быть вызвана тестовым кодом вне модуля. Это из-за того, как работает JavaScript, и Мокка сама по себе не может обойти это.
В тех немногих случаях, когда я решил, что проверка частной функции - это правильная вещь, я выполнил некоторую переменную среды, которую мой модуль проверяет, будет ли он запущен в тестовой настройке или нет. Если он запускается в тестовой настройке, он экспортирует дополнительные функции, которые я могу вызвать во время тестирования.
Слово "среда" здесь свободно используется. Это может означать проверку process.env
или что-то еще, которое может связываться с модулем, который вы сейчас тестируете. Экземпляры, где я должен был это сделать, были в среде RequireJS, и я использовал module.config
для этой цели.
Ответ 2
Откройте rewire модуль. Это позволяет вам (и манипулировать) частными переменными и функциями внутри модуля.
Итак, в вашем случае использование будет выглядеть примерно так:
var rewire = require('rewire'),
foobar = rewire('./foobar'); // Bring your module in with rewire
describe("private_foobar1", function() {
// Use the special '__get__' accessor to get your private function.
var private_foobar1 = foobar.__get__('private_foobar1');
it("should do stuff", function(done) {
var stuff = private_foobar1(filter);
should(stuff).be.ok;
should(stuff).....
Ответ 3
Вот действительно хороший рабочий процесс для тестирования ваших личных методов, который объясняет Филипп Уолтон, инженер Google из своего блога.
Принцип
- Напиши свой код нормально
- Свяжите свои частные методы с объектом в отдельном блоке кода, отметьте его, например,
_
- Окружите этот блок кода начальными и конечными комментариями
Затем используйте задачу сборки или собственную систему сборки (например, код grunt-strip-code), чтобы удалить этот блок для производственных сборок.
Ваши тестовые сборки имеют доступ к вашему частному API, а ваши производственные - нет.
отрывок
Напишите свой код как это:
var myModule = (function() {
function foo() {
// private function 'foo' inside closure
return "foo"
}
var api = {
bar: function() {
// public function 'bar' returned from closure
return "bar"
}
}
/* test-code */
api._foo = foo
/* end-test-code */
return api
}())
И ваши грубые задачи, как это
grunt.registerTask("test", [
"concat",
"jshint",
"jasmine"
])
grunt.registerTask("deploy", [
"concat",
"strip-code",
"jshint",
"uglify"
])
Глубже
В более поздней статье это объясняет "почему" "тестирования частных методов"
Ответ 4
Если вы предпочитаете, чтобы все было просто, просто экспортируйте также приватные элементы, но четко отделив их от общедоступного API с некоторыми соглашениями, например, добавьте к ним префикс _
или вложите их в один частный объект.
var privateWorker = function() {
return 1
}
var doSomething = function() {
return privateWorker()
}
module.exports = {
doSomething: doSomething,
_privateWorker: privateWorker
}
Ответ 5
Для этого я создал пакет npm, который может оказаться полезным: require-from
В основном вы выставляете закрытые методы:
module.testExports = {
private_foobar1: private_foobar1,
private_foobar2: private_foobar2,
...
}
примечание: testExports
может быть любым допустимым именем, кроме exports
конечно.
И из другого модуля:
var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;
Ответ 6
Я добавил дополнительную функцию, которую я назвал Internal() и возвращает оттуда все частные функции. Эта функция Internal() затем экспортируется. Пример:
function Internal () {
return { Private_Function1, Private_Function2, Private_Function2}
}
// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }
Вы можете вызвать внутренние функции следующим образом:
let test = require('.....')
test.Internal().Private_Function1()
Мне больше нравится это решение, потому что:
- всегда экспортируется только одна функция Internal(). Эта функция Internal() всегда используется для проверки приватных функций.
- Это просто реализовать
- Низкое влияние на производственный код (только одна дополнительная функция)
Ответ 7
Я выполнил ответ @barwin и проверил, как модульные тесты могут быть выполнены с помощью модуля rewire. Я могу подтвердить, что это решение просто работает.
Модуль должен быть необходим в двух частях - общедоступном и частном. Для публичных функций вы можете сделать это стандартным способом:
const { public_foobar3 } = require('./foobar');
Для частного использования:
const privateFoobar = require('rewire')('./foobar');
const private_foobar1 = privateFoobar .__get__('private_foobar1');
const private_foobar2 = privateFoobar .__get__('private_foobar2');
Чтобы узнать больше о предмете, я создал рабочий пример с полным тестированием модулей, тестирование включает в себя частную и общедоступную область.
Для получения дополнительной информации я рекомендую вам проверить статью (https://medium.com/@macsikora/how-to-test-private-functions-of-es6-module-fb8c1345b25f), полностью описывая тему, она включает образцы кода.
Ответ 8
Я знаю, что это не обязательно тот ответ, который вы ищете, но я обнаружил, что большую часть времени, если частная функция заслуживает тестирования, она стоит в своем собственном файле.
Например, вместо использования закрытых методов в том же файле, что и общедоступные, как это...
ЦСИ/вещь/PublicInterface.js
function helper1 (x) {
return 2 * x;
}
function helper2 (x) {
return 3 * x;
}
export function publicMethod1(x) {
return helper1(x);
}
export function publicMethod2(x) {
return helper1(x) + helper2(x);
}
... ты разделил это так:
ЦСИ/вещь/PublicInterface.js
import {helper1} from './internal/helper1.js';
import {helper2} from './internal/helper2.js';
export function publicMethod1(x) {
return helper1(x);
}
export function publicMethod2(x) {
return helper1(x) + helper2(x);
}
ЦСИ/вещь/внутренняя /helper1.js
export function helper1 (x) {
return 2 * x;
}
ЦСИ/вещь/внутренняя /helper2.js
export function helper2 (x) {
return 3 * x;
}
Таким образом, вы можете легко протестировать helper1
и helper2
как есть, без использования Rewire и другой "магии" (у которой, как я обнаружил, есть свои болевые точки при отладке или при попытке перейти к TypeScript, а не к упомянуть о худшей понятности для новых коллег). А то, что они находятся в подпапке с internal
именем или чем-то в этом роде, поможет избежать их случайного использования в непредусмотренных местах.
PS: Еще одна распространенная проблема с "закрытыми" методами заключается в том, что если вы хотите протестировать publicMethod1
и publicMethod2
и высмеивать помощников, то вам, как правило, для этого требуется что-то вроде Rewire. Однако, если они находятся в отдельных файлах, вы можете использовать для этого Proxyquire, который, в отличие от Rewire, не требует каких-либо изменений в процессе сборки, легко читается и отлаживается и хорошо работает даже с TypeScript.
Ответ 9
Чтобы сделать частные методы доступными для тестирования, я делаю это:
const _myPrivateMethod: () => {};
const methods = {
myPublicMethod1: () => {},
myPublicMethod2: () => {},
}
if (process.env.NODE_ENV === 'test') {
methods._myPrivateMethod = _myPrivateMethod;
}
module.exports = methods;