programing

Mocha 및 Node.js를 사용한 개인 기능의 장치 테스트

goodsources 2023. 8. 29. 20:29
반응형

Mocha 및 Node.js를 사용한 개인 기능의 장치 테스트

나는 Node.js용으로 작성된 애플리케이션을 유닛 테스트하기 위해 Mocha를 사용하고 있습니다.

모듈에서 내보내지 않은 테스트 기능을 유닛화하는 것이 가능한지 궁금합니다.

예:

는 는나이정많의기있가다습니지고에서 이렇게 정의된 기능들이 많이 있습니다.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내보내지 않습니다.

개인 방법을 유닛 테스트하는 올바른 방법은 무엇입니까?모카는 그것을 할 수 있는 몇 가지 기본적인 방법이 있습니까?

리와이어 모듈을 점검하십시오.모듈 내에서 개인 변수와 함수를 가져오거나 조작할 수 있습니다.

따라서 사용자의 경우 다음과 같은 용도가 있습니다.

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).....

모듈에서 해당 기능을 내보내지 않으면 모듈 외부의 테스트 코드로 호출할 수 없습니다.그것은 자바스크립트가 작동하는 방식 때문이며, 모카만으로는 이것을 피할 수 없습니다.

개인 기능을 테스트하는 것이 옳다고 판단한 몇 가지 사례에서 모듈이 테스트 설정에서 실행 중인지 여부를 확인하기 위해 확인하는 환경 변수를 설정했습니다.테스트 설정에서 실행되는 경우 테스트 중에 호출할 수 있는 추가 기능을 내보냅니다.

단어는 여기서 됩니다."환경"이라는 단어는 여기서 느슨하게 사용됩니다.그것은 확인을 의미할 수도 있습니다.process.env또는 "지금 테스트 중" 모듈과 통신할 수 있는 다른 것.이 작업을 수행해야 했던 경우는 Require(요구 사항)에 있었습니다.JS 환경, 그리고 나는 사용해 왔습니다.module.config이를 위하여

구글 엔지니어 필립 월튼이 블로그에서 설명한 개인적인 방법을 테스트할 수 있는 정말 좋은 워크플로우가 있습니다.

원리

  • 코드를 정상적으로 작성합니다.
  • 코드하고 개별코블개개메바를서다표다인시합니로로 합니다._를 들어예를 들어)
  • 코드 블록을 시작 및 끝 주석으로 둘러쌉니다.

그런 다음 빌드 작업 또는 자체 빌드 시스템(예: 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 작업:

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

더 깊은

이후 기사에서는 "개인 방법 테스트"의 "이유"를 설명합니다.

하여 공개 API를 하십시오. 를 들어, " 단하게유고싶만와지내개내보도다구원사니일, "구관를여하 API용례합분게하확명공개부순하지면"로 접두사를 . 예를 들어, 다음과 같이 접두사를 붙입니다._또는 하나의 개인 개체 아래에 둥지를 틀 수 있습니다.

var privateWorker = function() {
    return 1
}

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

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

내부()라는 이름의 추가 기능을 추가하고 거기서 모든 개인 기능을 반환합니다.그러면 이 내부() 기능이 내보내집니다.예:

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

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

다음과 같은 내부 함수를 호출할 수 있습니다.

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

이 솔루션이 가장 마음에 드는 이유는 다음과 같습니다.

  • 항상 하나의 함수 Internal()만 내보냅니다.내부() 함수는 항상 개인 기능을 테스트하는 데 사용됩니다.
  • 구현이 간단합니다.
  • 생산 코드에 미치는 영향이 적음(단 하나의 추가 기능만 있음)

나는 이 목적을 위해 당신이 유용하다고 생각할 수 있는 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;

이것이 반드시 여러분이 찾고 있는 답이 아니라는 것을 알지만, 대부분의 경우 개인 기능이 테스트할 가치가 있다면 자체 파일에 저장할 가치가 있다는 것을 알게 되었습니다.

예를 들어, 공개된 파일과 같은 파일에 개인적인 방법을 사용하는 대신, 이렇게...

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

...이렇게 나누면 다음과 같습니다.

src/thing/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/thing/내부/내부1.js

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

src/thing/내부/slot2.js

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

그렇게 하면 쉽게 테스트할 수 있습니다.helper1그리고.helper2Rewire 및 기타 "마법"을 사용하지 않고 그대로 사용할 수 있습니다(디버깅을 하는 동안 또는 새로운 동료에 대한 이해력이 떨어지는 것은 말할 것도 없고 TypeScript로 이동하려고 할 때).은 그고그하폴있습다니더에라는 하위 .internal의도하지 않은 장소에서 실수로 사용하는 것을 방지하는 데 도움이 될 것입니다.


추신: "개인" 방법의 또 다른 일반적인 문제는 테스트를 원하는 경우publicMethod1그리고.publicMethod2그리고 도우미들을 조롱합니다. 다시 말하지만, 당신은 보통 그것을 하기 위해 리와이어 같은 것이 필요합니다.그러나 별도의 파일에 있는 경우 Proxyquire를 사용하여 이 작업을 수행할 수 있습니다. 이 작업은 Rewire와 달리 빌드 프로세스를 변경할 필요가 없고 읽기 및 디버깅하기 쉬우며 TypeScript에서도 잘 작동합니다.

바윈의 답변에 따라 리와이어 모듈로 유닛 테스트를 어떻게 하는지 확인했습니다.저는 이 솔루션이 단순히 작동한다는 것을 확인할 수 있습니다.

모듈은 공용 모듈과 전용 모듈의 두 부분으로 구성되어야 합니다.공용 기능의 경우 표준 방식으로 이를 수행할 수 있습니다.

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

개인 범위:

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

이 주제에 대해 더 자세히 알기 위해, 저는 전체 모듈 테스트, 테스트에는 개인 및 공용 범위가 포함된 작업 예제를 만들었습니다.

자세한 내용은 기사(공통개인 기능을 테스트하는 방법)를 확인하시기 바랍니다.JS 모듈)에서 주제를 완전히 설명합니다.코드 샘플이 포함되어 있습니다.

개인 방법을 테스트에 사용할 수 있도록 하려면 다음을 수행합니다.

const _myPrivateMethod: () => {};

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

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

module.exports = methods;

옵션으로 주입을 사용하여 중복 코드를 만듭니다.

예:

./syslog_code.js

export default class A{
  #privateMethod(){
     return 'hello';
  }
}

./test_code.js

    import A from './prod_code.js';

    function inject_method_into_duplicate_сlass(MClass,injectMethodStr){
        let str_Class = MClass.toString();
        let code='return '+MClass.toString().replace (/^[\s]*class[\s]+(\w+)([\s]+extends[\s]+[\w]+)?[\s]*{([\s\S]*)}[\s]*$/,function(str,class_name,extend_class,code){
            return `class ${class_name}${extend_class??''} {\n${injectMethodStr} ${code}}`;
        });
        return Function(code)();
    }

//...
    let Mod_A=inject_method_into_duplicate_сlass(A,'static runPrivateMethod(name,...args){return eval(`this.${name}`)(...args);}')

    assert.ok(Mod_A.runPrivateMethod('#privateMethod')==='hello');

코드는 예제로 제공됩니다.모든 사람이 테스트를 위해 자신만의 구현을 생각해 낼 수 있습니다.

이러한 주입의 도움으로 제품 코드는 테스트 코드에서 가능한 한 깨끗해질 것입니다.

업데이트됨

그러나 이 방법은 부작용이 있습니다. 클래스 뒤에서 초기화된 모든 메서드, 속성 및 클래스 데이터는 중복에 존재하지 않습니다.따라서 이러한 속성과 방법을 직접 초기화해야 합니다.

class A{
}
//initialization behind  class
A.prop='hello';
Object.defineProprty(A,'prop2',{
  value:'bay'
});


업데이트됨

클래스에서 전역 변수 또는 모듈을 선언하는 데도 문제가 발생합니다.

import A from './A.js';
 class B extends A // when creating a duplicate, class A must be in scope
{
}
// or 
class B{
   #privateMethod(){
      return new A();// when creating a duplicate, class A must be in scope
   }
}

하지만 그러한 어려움은 해결할 수 있습니다.

  1. tests/dublishate_your_class.js와 같은 테스트 파일을 만듭니다.
  2. 그 안에서 본류의 종속을 선언하다.
  3. 필요한 주입으로 클래스를 동적으로 복제합니다.
  4. 중복 클래스의 속성, 메서드 및 데이터를 기본 클래스와 동기화합니다.
  5. 장치 테스트 전반에 걸쳐 중복 클래스를 사용합니다.

하지만 남은 문제는 메인 클래스에 부작용이 있는지 여부입니다.다른 코드를 실행하는 동안 정적 데이터가 변경되는 경우.그러나 이 경우, 그러한 코드를 테스트하는 것은 단위 테스트의 범위 내에 있지 않습니다.

거의 항상 그렇듯이, 이 질문에 대한 가장 좋은 대답은 아키텍처입니다. 운영 환경에 추가적인 종속성을 요구하지 않고 기본적으로 테스트 가능한 방식으로 코드를 작성하는 것입니다.

ES6 코드를 작성하는 경우 기본 ES6 모듈 내보내기가 ES6 소스가 되어야 합니다.당신은 전언할 필요가 없습니다.

이 경우 다음을 수행합니다.

  • 테스트 가능한 메서드가 공용이므로 장치 테스트 프레임워크에서 액세스할 수 있지만 패키지에서 내보내지 않는 "개인" 클래스를 만듭니다.

  • 이 클래스를 구성별로(상속이 아닌) 패키지에서 내보내는 "공개" 래퍼 클래스에 포함합니다.

이제 최상의 시나리오가 완성되었습니다.

  • 게시 전에 새 종속성을 추가하거나 테스트된 코드를 수정하지 않고 모든 클래스 메서드를 있는 그대로 테스트할 수 있습니다.
  • 래퍼 클래스가 노출하는 메서드를 완전히 제어할 수 있습니다.

언급URL : https://stackoverflow.com/questions/22097603/unit-testing-of-private-functions-with-mocha-and-node-js

반응형