web-dev-qa-db-ja.com

Jasmineを使用してテストコードを再利用する良い方法は何ですか?

Jasmine BDD Javascriptライブラリを使用していて、本当に楽しんでいます。再利用したいテストコードがあり(たとえば、基本クラスの複数の実装をテストしたり、わずかに異なるコンテキストで同じテストを実行したり)、Jasmineを使用してそれを行う方法がわかりません。コードをジャスミン関数から再利用可能なクラスに移動できることは知っていますが、コードがジャスミン関数に散在して読み取られる方法(説明、それ)が好きで、テストコードから仕様を分離したくないのはする必要がある。 Jasmineを使用している人がこの問題に遭遇したことがありますか?また、どのように対処しましたか?

24
glenn

これは、Pivo​​tal Labsの男性による記事で、describe呼び出しをラップする方法について詳しく説明しています。

動作を共有してJasmineスペックを乾燥させる

ラッパー関数の一部を示す記事からの抜粋:

function sharedBehaviorForGameOf(context) {
  describe("(shared)", function() {
    var ball, game;
    beforeEach(function() {
      ball = context.ball;
      game = context.game;
    });
  });
}
28
starmer

@starmerのソリューションがどのように機能するかわかりません。コメントで述べたように、彼のコードを使用する場合、contextは常に未定義です。

代わりに、(@ moefinleyで言及されているように)実行する必要があるのは、代わりにコンストラクター関数への参照を渡すことです。私は ブログ投稿を書いた 例を使用してこのアプローチの概要を説明しました。これがその本質です:

describe('service interface', function(){
    function createInstance(){
        return /* code to create a new service or pass in an existing reference */
    }

    executeSharedTests(createInstance);
});

function executeSharedTests(createInstanceFn){
    describe('when adding a new menu entry', function(){
        var subjectUnderTest;

        beforeEach(function(){
            //create an instance by invoking the constructor function
            subjectUnderTest = createInstanceFn();
        });

        it('should allow to add new menu entries', function(){
            /* assertion code here, verifying subjectUnderTest works properly */
        });
    });
}
13
Juri

Throughbotのウェブサイトに素敵な記事があります: https://robots.thoughtbot.com/jasmine-and-shared-examples

簡単なサンプルを次に示します。

appNamespace.jasmine.sharedExamples = {
  "rectangle": function() {
    it("has four sides", function() {
      expect(this.subject.sides).toEqual(4);
    });
  },
 };

そして、itShouldBehaveLikeを定義するためのいくつかの下線関数を使用します

window.itShouldBehaveLike = function() {
  var exampleName      = _.first(arguments),
      exampleArguments = _.select(_.rest(arguments), function(arg) { return !_.isFunction(arg); }),
      innerBlock       = _.detect(arguments, function(arg) { return _.isFunction(arg); }),
      exampleGroup     = appNamespace.jasmine.sharedExamples[exampleName];

  if(exampleGroup) {
    return describe(exampleName, function() {
      exampleGroup.apply(this, exampleArguments);
      if(innerBlock) { innerBlock(); }
    });
  } else {
    return it("cannot find shared behavior: '" + exampleName + "'", function() {
      expect(false).toEqual(true);
    });
  }
};
2
Rimian

実例でまとめましょう

  describe('test', function () {

    beforeEach(function () {
      this.shared = 1;
    });

    it('should test shared', function () {
      expect(this.shared).toBe(1);
    });

    testShared();
  });

  function testShared() {
    it('should test in function', function() {
      expect(this.shared).toBe(1);
  });

  }

ここでの重要な部分は、コンテキストを渡すためのthisキーワードであり、このため、"通常の"関数(別の重要な部分)を使用する必要があります。

プロダクションコードの場合、コンテキストを渡す/抽出するためにbeforeEachでのみ通常の関数を使用しますが、簡潔にするために仕様では矢印関数を使用し続けます。

通常、コンテキストは後に呼び出されるbeforeEachブロックで定義するため、コンテキストをパラメーターとして渡すことは機能しません。

describeセクションを持つことは重要ではないようですが、より良い構造のために歓迎します

2
s-f

これはスターマーの答えに似ていますが、それを試した後、私は指摘するいくつかの違いを見つけました。欠点は、仕様が失敗した場合、Jasmineレポートに「一般的な保存仕様に準拠する必要がある」と表示されることです。スタックトレースは、失敗した場所を見つける唯一の方法です。

// common specs to execute
self.executeCommonSpecifications = function (vm) {
  // I found having the describe( wrapper here doesn't work
  self.shouldCallTheDisplayModelsSaveMethod(vm);
}
self.shouldCallTheDisplaysSaveMethod = function (vm) {
  expect(vm.save.calls.count()).toBe(1);
};

// spec add an it so that the beforeEach is called before calling this
beforeEach(function(){
  // this gets called if wrapped in the it
  vm.saveChanges();
}
it('should adhere to common saving specifications', function () {
  executeSavingDisplaysCommonSpecifications(vm);
});
1
Aligned

これは私が取ったアプローチであり、この記事に触発されています。

https://Gist.github.com/traviskaufman/111313

これはジャスミン自身のドキュメントに基づいています:

http://jasmine.github.io/2.0/introduction.html#section-The_%3Ccode%3Ethis%3C/code%3E_keyword

共有依存関係をbeforeEach関数プロトタイプのプロパティとして設定することにより、beforeEachを拡張して、この依存関係をthis経由で利用できるようにすることができます。

例:

describe('A suite', function() {
    // Shared setup for nested suites
    beforeEach(function() {
        // For the sake of simplicity this is just a string
        // but it could be anything
        this.sharedDependency = 'Some dependency';
    });

    describe('A nested suite', function() {
        var dependency;

        beforeEach(function() {
            // This works!
            dependency = this.sharedDependency;                
        });

        it('Dependency should be defined', function() {
            expect(dependency).toBeDefined();
        });
    });

    describe('Check if string split method works', function() {
        var splitToArray;

        beforeEach(function() {
            splitToArray = this.sharedDependency.split();                
        });

        it('Some other test', function() { ... });
    });
});

私の例は役に立たないことは知っていますが、コード例としての目的を果たす必要があります。

もちろん、これはあなたが言うことを達成するためにあなたができる多くのことの1つにすぎません。私は、より複雑なデザインパターンがこれの上または横に適用されるかもしれないと確信しています。

それが役に立てば幸い!

1
Amy Pellegrini

これがより単純なソリューションです。 thisキーワードまたはコンテキストを使用せずに、変数関数を宣言して使用します。

describe("Test Suit", function ()
{
   var TestCommonFunction = function(inputObjects)
   {
     //common code here or return objects and functions here etc
   };

   it("Should do x and y", function()
   {
       //Prepare someInputObjects
       TestCommonFunction(someInputObjects);
       //do the rest of the test or evaluation
   });
});

より多くの関数を含むオブジェクトを返し、その後、返された関数を呼び出すこともできます。

0
Andrew Marais