mocha ve node.js ile özel işlevlerin birim testi


131

Node.js için yazılmış bir uygulamayı birim test etmek için mocha kullanıyorum

Bir modülde dışa aktarılmamış işlevleri test etmenin mümkün olup olmadığını merak ediyorum.

Misal:

Şurada böyle tanımlanmış birçok işlevim var foobar.js

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}

ve genel olarak dışa aktarılan birkaç işlev:

exports.public_foobar3 = function(){
    ...
}

Test senaryosu aşağıdaki şekilde yapılandırılmıştır:

describe("private_foobar1", function() {
    it("should do stuff", function(done) {
        var stuff = foobar.private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

Açıkçası bu, private_foobar1ihraç edilmediği için çalışmıyor .

Özel yöntemleri birim test etmenin doğru yolu nedir? Mocha'nın bunu yapmak için bazı yerleşik yöntemleri var mı?


Yanıtlar:


64

Fonksiyon modül tarafından dışa aktarılmazsa, modül dışındaki test koduyla çağrılamaz. Bu, JavaScript'in çalışma biçiminden kaynaklanmaktadır ve Mocha kendi başına bunu engelleyemez.

Özel bir işlevi test etmenin yapılacak doğru şey olduğunu belirlediğim birkaç örnekte, yaptığım şey, modülümün bir test kurulumunda çalışıp çalışmadığını belirlemek için kontrol ettiği bazı ortam değişkenleri ayarlamaktı. Test kurulumunda çalışırsa, test sırasında arayabileceğim ek işlevleri dışa aktarır.

"Çevre" kelimesi burada genel olarak kullanılmaktadır. Kontrol etmek process.envveya "şimdi test ediliyorsunuz" modülüyle iletişim kurabilecek başka bir şey anlamına gelebilir . Bunu yapmak zorunda olduğum durumlar bir RequireJS ortamındaydı ve module.configbu amaçla kullandım.


2
Koşullu olarak dışa aktarılan değerler ES6 modülleri ile uyumlu görünmüyor. Ben alıyorumSyntaxError: 'import' and 'export' may only appear at the top level
aij

1
evet @aij ES6 statik ihracat nedeniyle size kullanamazsınız import, exportbir bloğun içini. Sonunda bu tür şeyleri ES6'da Sistem yükleyici ile gerçekleştirebileceksiniz. Şimdi bunu module.exports = process.env.NODE_ENV === 'production' ? require('prod.js') : require('dev.js')aşmanın bir yolu, es6 kod farklarınızı bu ilgili dosyalarda kullanmak ve saklamaktır.
cchamberlain

2
Sanırım tam kapsama alanına sahipseniz, onları ifşa etmiş olsanız da olmasanız da tüm özel işlevlerinizi test ediyorsunuz.
Ziggy

1
@aij Koşullu olarak dışa aktarabilirsiniz ... bu yanıta bakın: stackoverflow.com/questions/39583958/…
RayLoveless

187

Check out ReWire modülü. Bir modül içindeki özel değişkenleri ve işlevleri almanıza (ve değiştirmenize) olanak tanır.

Yani sizin durumunuzda kullanım aşağıdaki gibi olacaktır:

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
Benim kod @Jaro En ReWire edemiyor AMD modüller biçiminde, ya olduğu işlemek (AMD modülleri işlevleri vardır ama ReWire "işlevleri içinde değişkenleri" işleyemez çünkü). Veya başka bir senaryo aktarılırsa, yeniden kablolamanın üstesinden gelemez. Aslında, yeniden kablolamaya bakacak kişiler, kullanmaya çalışmadan önce sınırlamaları (daha önce bağlantılı olan) okumaları iyi olur. A) "özel" şeyleri dışa aktarmaya ihtiyaç duyan ve b) yeniden kablolama sınırlamasıyla karşılaşmayan tek bir uygulamam yok.
Louis

1
Küçük bir nokta, kod kapsamı böyle yazılan testleri almakta başarısız olabilir. En azından Jest'in yerleşik kapsama aracını kullanırken gördüğüm şey buydu.
Mike Stead

Rewire, jest'in otomatik alay aracıyla da iyi oynamıyor. Hala jestin avantajlarından yararlanmanın ve bazı özel kaynaklara erişmenin bir yolunu arıyorum.
btburton42

Bu yüzden bu işi yapmayı denedim ama bu soruna neden olduğunu tahmin ettiğim typcript kullanıyorum. Temelde aşağıdaki hatayı alıyorum: Cannot find module '../../package' from 'node.js'. Buna aşina olan var mı?
clu

rewire düzgün çalışıyor .ts, @clu typescriptkullanarak ts-nodekoşuyorum
muthukumar selvaraj

24

Blogunda bir Google mühendisi olan Philip Walton tarafından açıklanan özel yöntemlerinizi test etmek için gerçekten iyi bir iş akışı .

Prensip

  • Kodunuzu normal şekilde yazın
  • Özel yöntemlerinizi nesneye ayrı bir kod bloğunda bağlayın, _örneğin bir
  • Bu kod bloğunu başlangıç ​​ve bitiş yorumlarıyla çevreleyin

Ardından, bu bloğu üretim yapıları için çıkarmak üzere bir derleme görevi veya kendi derleme sisteminizi (örneğin grunt-strip-code) kullanın.

Test derlemelerinizin özel api'nize erişimi vardır ve üretim derlemelerinizin erişimi yoktur.

Pasaj

Kodunuzu şu şekilde yazın:

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
}())

Ve bunun gibi homurdanan görevlerin

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])

Daha derin

Daha sonraki bir makalede , "özel yöntemleri test etme" nin "nedenini" açıklıyor


1
Ayrıca benzer bir iş akışını destekleyebilecek gibi görünen bir webkit eklentisi buldu: webpack-strip-block
JRulle

21

Basit tutmayı tercih ederseniz, sadece özel üyeleri de dışa aktarın, ancak bazı kurallarla genel API'den açıkça ayırın, örneğin onlara bir önek ekleyin _veya tek bir özel nesne altına yerleştirin .

var privateWorker = function() {
    return 1
}

var doSomething = function() {
    return privateWorker()
}

module.exports = {
    doSomething: doSomething,
    _privateWorker: privateWorker
}

7
Bunu, tüm modülün ortak kullanım için değil, gerçekten özel olması gerektiği durumlarda yaptım. Ancak genel amaçlı modüller için, test için ihtiyacım olanı yalnızca kod test edilirken ortaya çıkarmayı tercih ederim . Nihayetinde birinin bir test ortamını taklit ederek özel içeriğe ulaşmasını engelleyecek hiçbir şey olmadığı doğrudur, ancak biri kendi uygulamasında hata ayıklama yaparken, olması gerekmeyen sembolleri görmemeyi tercih ederim. genel API'nin bir parçası. Bu şekilde, tasarlanmadığı amaçlar için API'yi kötüye kullanma eğilimi yoktur.
Louis

2
iç içe geçmiş sözdizimini de kullanabilirsiniz {... özel : {işçi: işçi}}
Jason,

2
Modül tamamen saf işlevlerden oluşuyorsa, bunu yapmanın bir dezavantajını görmüyorum. Eğer durumu koruyor ve mutasyona uğratıyorsanız, o zaman dikkatli olun ...
Ziggy

5

Bu amaç için yararlı bulabileceğiniz bir npm paketi hazırladım: required -from

Temel olarak, halka açık olmayan yöntemleri şu şekilde ifşa edersiniz:

module.testExports = {
    private_foobar1: private_foobar1,
    private_foobar2: private_foobar2,
    ...
}

not: tabii ki testExportsdışında istediğiniz herhangi bir geçerli isim olabilir exports.

Ve başka bir modülden:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;

1
Bu yöntemin pratik bir avantajı göremiyorum. "Özel" sembolleri daha özel hale getirmez. (Herkes çağırabilir requireFromdoğru parametrelerle.) Ayrıca, ile modül eğer textExportsbir tarafından yüklenen requireçağrı önce requireFrom yükler onu, requireFromdönecektir undefined. (Daha yeni test ettim.) Modüllerin yük sırasını kontrol etmek genellikle mümkün olsa da, bu her zaman pratik değildir. (SO ile ilgili bazı Mocha sorularının da gösterdiği gibi.) Bu çözüm genellikle AMD tipi modüllerle çalışmaz. (AMD modüllerini test için
Louis

AMD modülleri ile çalışmamalı! Node.js, common.js kullanır ve bunu AMD'yi kullanacak şekilde değiştirirseniz, bunu normalin dışında yapıyorsunuz demektir.
jemiloii

@JemiloII Yüzlerce geliştirici, AMD modüllerini test etmek için günlük olarak Node.js kullanıyor. Bunu yaparken "norm dışı" hiçbir şey yok. Söyleyebileceğiniz en fazla şey, Node.js'nin bir AMD yükleyici ile gelmediği, ancak bu pek bir şey ifade etmiyor, çünkü Node, geliştiricilerin geliştirmeyi umduğu formatı yüklemek için yükleyicisini genişletmek için açık kancalar sağlıyor.
Louis

Normların dışında. Bir amd yükleyiciyi manuel olarak eklemeniz gerekiyorsa, bu node.js için bir norm değildir. AMD'yi nadiren node.js kodu için görüyorum. Tarayıcı için göreceğim, ancak düğüm. Hayır. Bunun yapılmadığını söylemiyorum, sadece soru ve yorum yaptığımız bu cevap, amd modülleri hakkında hiçbir şey söylemiyor. Bu nedenle, kimse bir amd yükleyici kullandığını belirtmeden, düğüm dışa aktarmaları amd ile çalışmamalıdır. Not etmek istememe rağmen, commonjs es6 dışa aktarımıyla çıkış yolunda olabilir. Umarım bir gün hepimiz tek bir ihracat yöntemi kullanabiliriz.
jemiloii

4

Internal () olarak adlandırdığım ve oradan tüm özel işlevleri döndürdüğüm ekstra bir işlev ekledim . Bu Internal () işlevi daha sonra dışa aktarılır. Misal:

function Internal () {
  return { Private_Function1, Private_Function2, Private_Function2}
}

// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }

Dahili fonksiyonları şu şekilde çağırabilirsiniz:

let test = require('.....')
test.Internal().Private_Function1()

Bu çözümü en çok beğendim çünkü:

  • her zaman yalnızca bir işlev Internal () dışa aktarılır. Bu Internal () işlevi her zaman özel işlevleri test etmek için kullanılır.
  • Uygulaması basittir
  • Üretim kodu üzerinde düşük etki (yalnızca bir ekstra işlev)

2

@Barwin cevabını takip ettim ve rewire modülü ile birim testlerinin nasıl yapılacağını kontrol ettim . Bu çözümün işe yaradığını doğrulayabilirim.

Modül, iki bölüm halinde gerekli olmalıdır - herkese açık ve özel bölüm. Genel işlevler için bunu standart şekilde yapabilirsiniz:

const { public_foobar3 } = require('./foobar');

Özel kapsam için:

const privateFoobar = require('rewire')('./foobar');
const private_foobar1 = privateFoobar .__get__('private_foobar1');
const private_foobar2 = privateFoobar .__get__('private_foobar2');

Konu hakkında daha fazla bilgi sahibi olmak için, tam modül testi ile çalışan bir örnek oluşturdum, test özel ve genel kapsamı içerir.

Daha fazla bilgi için konuyu tam olarak açıklayan makaleyi ( https://medium.com/@macsikora/how-to-test-private-functions-of-es6-module-fb8c1345b25f ) incelemenizi tavsiye ederim , kod örnekleri içerir.


2

Bunun mutlaka aradığınız yanıt olmadığını biliyorum, ancak bulduğum şey, çoğu zaman özel bir işlev test edilmeye değerse, kendi dosyasında bulunmaya değer.

Örneğin, herkese açık olanlarla aynı dosyada özel yöntemler kullanmak yerine, bunun gibi ...

src / şey / 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);
}

... bunu şu şekilde bölüyorsunuz:

src / şey / 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);
}

src / şey / iç / helper1.js

export function helper1 (x) {
    return 2 * x;
}

src / şey / iç / helper2.js

export function helper2 (x) {
    return 3 * x;
}

Bu şekilde, Rewire ve diğer "sihir" i kullanmadan kolayca helper1ve helper2olduğu gibi test edebilirsiniz (ki bunların hata ayıklama sırasında kendi acı noktalarına sahip olduğunu veya TypeScript'e doğru ilerlemeye çalıştığınızda, daha zayıf yeni meslektaşlar için anlaşılabilirlik). Ve bunların adı verilen bir alt klasörde internalya da buna benzer bir şey, istenmeyen yerlerde yanlışlıkla kullanılmalarını önlemeye yardımcı olacaktır.


Not: "Özel" yöntemleriyle Başka bir ortak sorun, testin istiyorsanız olmasıdır publicMethod1ve publicMethod2ve yardımcıları alay, yine, normalde bunu rewire gibi bir şey gerekiyor. Bununla birlikte, ayrı dosyalardaysa, bunu yapmak için Proxyquire'ı kullanabilirsiniz ; bu, Rewire'ın aksine, oluşturma sürecinizde herhangi bir değişiklik gerektirmez, okunması ve hata ayıklaması kolaydır ve TypeScript ile bile iyi çalışır.


1

Özel yöntemleri test için kullanılabilir hale getirmek için şunu yapıyorum:

const _myPrivateMethod: () => {};

const methods = {
    myPublicMethod1: () => {},
    myPublicMethod2: () => {},
}

if (process.env.NODE_ENV === 'test') {
    methods._myPrivateMethod = _myPrivateMethod;
}

module.exports = methods;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.