【问题标题】:What's a good way to reuse test code using Jasmine?使用 Jasmine 重用测试代码的好方法是什么?
【发布时间】:2011-06-30 23:55:19
【问题描述】:

我正在使用Jasmine BDD Javascript 库并且非常喜欢它。我有想要重用的测试代码(例如,测试基类的多个实现或在稍微不同的上下文中运行相同的测试),但我不知道如何使用 Jasmine。我知道我可以将代码从 jasmine 函数中移出并放入可重用的类中,但我喜欢代码读取与 Jasmine 函数(描述它)的方式,我不想将规范与测试代码分开,除非我不得不。有没有人在使用 Jasmine 时遇到过这个问题,您是如何处理的?

【问题讨论】:

    标签: javascript bdd jasmine


    【解决方案1】:

    这是一个更简单的解决方案。 声明一个变量函数并使用它,而不使用 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
       });
    });
    

    您也可以返回具有更多函数的对象,然后调用返回的函数。

    【讨论】:

      【解决方案2】:

      让我用工作示例来总结一下

        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 部分似乎并不重要,但仍然欢迎更好的结构

      【讨论】:

        【解决方案3】:

        我不确定@starmer 的解决方案是如何工作的。正如我在评论中提到的,当我使用他的代码时,context 始终是未定义的。

        相反,您必须做的(如@moefinley 所述)是传递对构造函数的引用。我有written a blog post 使用示例概述了这种方法。这是它的精髓:

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

        【讨论】:

        • 在下面查看我的回答如何通过这个传递上下文
        【解决方案4】:

        这是 Pivotal Labs 的一个人写的一篇文章,详细介绍了如何包装描述调用:

        DRYing up Jasmine Specs with Shared Behavior

        文章中显示部分包装函数的片段:

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

        【讨论】:

        • 当我尝试本文中的代码时,传入的范围(篮球或足球)在 beforeEach 函数运行之前被传入,因此是一个空对象。我通过传入构造函数的实例解决了这个问题。但我仍然很好奇戴维斯的代码是如何工作的。我错过了什么吗?
        • 这里相同...对象未定义,因为 beforeEach 尚未执行。 @moefinley 找到其他解决方案了吗??
        • @Juri 传入构造函数的实例有什么问题?
        • 如果对象未定义,请尝试用它包装函数调用,例如it('blah', function(){ sharedBehaviorForGameOf(football); });
        【解决方案5】:

        这是我在这篇文章的启发下采取的方法:

        https://gist.github.com/traviskaufman/11131303

        基于 Jasmine 自己的文档:

        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() { ... });
            });
        });
        

        我知道我的示例有点用处,但它应该作为代码示例发挥其作用。

        当然,这只是您可以做的许多事情之一,以实现您所说的,我相信更复杂的设计模式可能会应用于此之上或之外。

        希望对你有帮助!

        【讨论】:

          【解决方案6】:

          这与starmer的答案相似,但在完成之后我发现了一些需要指出的差异。不利的一面是,如果规范失败,您只会在 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);
          });
          

          【讨论】:

            【解决方案7】:

            thoughbot 的网站上有一篇不错的文章: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);
                });
              }
            };
            

            【讨论】:

              【解决方案8】:

              有人向我指出,将 describe 调用包装在一个向其传递参数的函数中。

              【讨论】:

              • 你至少应该粘贴一个 sn-p
              • 这正是@starmer 回答的内容,您可能想要更改您接受的答案
              猜你喜欢
              • 1970-01-01
              • 2016-08-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多