【问题标题】:trying to write test cases for each and every line尝试为每一行编写测试用例
【发布时间】:2018-02-11 12:55:19
【问题描述】:
  • 已经编写了跳转方法的测试用例,
  • 但当我看到代码覆盖率报告时,它并没有进入 onloadend 方法 seat.onloadend。
    • 在 createSpyObj 中我调用了 loadend,但它仍然没有进入
  • 你们能告诉我如何解决它吗?
  • 在下面提供我的代码和测试用例。
  • 我正在尝试为每一行编写测试用例。
  jumping(inputValue: any): void {
    var that = this;
    var file: File = inputValue.files[0];

    var seat: FileReader = new FileReader();
    seat.onloadend = (e) => {
      this.encodeBase64 = seat.result;
      that.fileSelect = $("#laptop").val().replace(/^.*\\/, "");
      if (that.fileSelect == '') {
        that.dragDrop = that.swimming;
      } else {
        that.dragDrop = "";
        that.dragDrop = that.fileSelect;
      }
    }
    $('.running').show();
    if (inputValue.files.length > 0) {
      var wholeQuantity = 0;

      wholeQuantity = inputValue.files[0].size / 1048576; //size in mb 

      if (wholeQuantity > 5) {
        $('.stars').show();
        $("#laptop").val('');
        this.fileSelect = "";
      }

      seat.readAsDataURL(file);
    }
  }






describe('Jasmine Unit Tests: hand-Basketball-Manage-mobiles', () => {
    let rainSPORTSService:SPORTSService;
    let SPORTSService: SPORTSService;
    let decodeService: DecodeService;
    let BasketballChainComponent: handBasketballChain;
    let kickViewrainsComponent: kickViewrains;
    let tiger: Componenttiger<handBasketballChain>;
    let raintiger: Componenttiger<kickViewrains>;
    let foodktiger: Componenttiger<foodkCarousel>;
    let kendotiger: Componenttiger<KendoGridComponent>;
    let foodkComponent:foodkCarousel;
    let kendoComponent:KendoGridComponent;
    beforeEach(async(() => {

    jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;

    TestBed.configureTestingModule({
      imports: [HttpModule, FormsModule,BrowserModule ],
      declarations:[handBasketballChain, KendoGridComponent,ProgressCircle,
            kickViewrains,handLeftSliderComponent,foodkCarousel,kickmobiles],
      providers:[SPORTSService,DecodeService,recentPinnedHistoryService,
        {provide: Router, useClass: RouterModule}, validationService,saveService,
        ChainService]
     }).compileComponents().then(() =>{
        foodktiger = TestBed.createComponent(foodkCarousel);
        kendotiger = TestBed.createComponent(KendoGridComponent);
        foodkComponent = foodktiger.componentInstance;
        kendoComponent = kendotiger.componentInstance;
        tiger = TestBed.createComponent(handBasketballChain);
        BasketballChainComponent = tiger.componentInstance;
        SPORTSService = tiger.debugElement.injector.get(SPORTSService);
        tiger.componentInstance.kickmobiles.SPORTSService=tiger.debugElement.injector.get(SPORTSService);
        tiger.componentInstance.kickViewrains.SPORTSService=tiger.debugElement.injector.get(SPORTSService);
        decodeService = tiger.debugElement.injector.get(DecodeService);
        BasketballChainComponent.inputfoodkCarousel = foodkComponent; //jasmine.createSpy('foodkCarousel');//.andCallFake(function(msg) { return this });
        BasketballChainComponent.kickmobiles.gridkendo=kendoComponent;    
    })}
    ));



    it('Read kick mobile', (done) => {


        let callFirstTime : boolean = true;
        let url=

        spyOn(BasketballChainComponent.kickmobiles.SPORTSService,'getResponse').and.
            callFake(() => { 
                if(callFirstTime) {
                    callFirstTime = false; // Invoked by detectChanges() 
                    return Observable.of([{
                        "mobileId": "100",
                        "mobileName": "http://localhost:3000/assets/js/actualairings.json",
                        "mobileType": "TITLE",
                        "mobileData": "YWZjYXJlZ2Vyamh2dmFyZWdoYnZi",
                        "notes": "",
                        "notesId": "100",
                        "elfDocID": "100",
                        "url": "http://localhost:3000/upload",
                        "date": "06/27/2017",
                        "addedByName": "Kamal",
                        "userID": "206509786",
                        "operationType": "create"
                    }]);
                }
            });


            const fileReaderSpy = jasmine.createSpyObj('FileReader', ['readAsDataURL', 'onloadend']);
            spyOn(window, 'FileReader').and.returnValue(fileReaderSpy);

            BasketballChainComponent.kickmobiles.jumping({
                files: "Untitled-2.txt"
            });

            var seat = new FileReader();

            //seat.onloadend(e);

            //BasketballChainComponent.kickmobiles.jumping.onloadend()


            tiger.whenStable().then(() => {
                done();
            });
    });
});

【问题讨论】:

  • 也许一个好主意是将匿名函数 ((e) =>) 提取到一个命名函数中,并通过显式调用它来测试它。这样,您将降低复杂性,并且易于测试和维护。
  • @GonzaloMatheu 你能在我的测试用例中更新吗....它太令人困惑了:(
  • @texirv 希望我的回答对您有所帮助。我根据要求从信誉良好的来源中获取信息,并为您提供了有关单元测试的更多信息以及您为什么要这样做。我还解释了代码覆盖率并不是测试的全部和结束。我为我的答案付出了很多努力。我注意到您没有接受我的回答,即使它解决了您的问题,包括如何为您的每一行代码获得 100% 的覆盖率。通过不接受我的回答,您实际上拒绝了我提供的一半赏金。以后如果别人能很好地回答你的问题,请采纳最合适的答案
  • @DanielLane 感谢您的回复...我试过了,但没有用...您能回复这个吗----->>>>>嘿,当我使用您的代码时在 Visual Studio for $ 中抛出此错误 -----> [ts] 类型“()=> void”不可分配给类型“JQueryStatic”。 [ts] 类型 '() => void' 不可分配给类型 'JQueryStatic'。类型“() => void”中缺少属性“ajax”。
  • 问题是覆盖 TypeScript 中的 JQuery $ 变量。我在未经测试的示例中存根的方法没有返回正确的类型,TypeScript 正在强制执行此操作。您可以使用 Jasmine 间谍来实现相同的目的。我会为你更新答案。但是请注意,stackoverflow 上的人会在这里提供帮助。我们不是来为您工作的,您不应该在不了解代码的方式、原因和意图的情况下复制和粘贴代码。

标签: javascript html angularjs typescript jasmine


【解决方案1】:

请记住,单元测试的关键是编写小的可测试代码单元。Unit Testing - Wikipedia

在调用“跳转”函数之前,您大部分时间都在正确的轨道上,对 FileReader 等进行存根。这非常适合测试依赖于另一个外部库/函数/框架的代码。单元测试状态的维基百科页面的相关部分

因为某些类可能会引用其他类,所以测试一个类经常会溢出到测试另一个类。一个常见的例子是依赖于数据库的类:为了测试类,测试人员经常编写与数据库交互的代码。这是一个错误,因为单元测试通常不应超出其自身的类边界,尤其不应跨越此类进程/网络边界,因为这会给单元测试套件带来不可接受的性能问题。

但事情是这样的,当您创建虚拟 FileReader 或模拟时,它永远不会调用“onloadend”,因为模拟/存根没有实现该事件和事件系统。这意味着您的模拟是不完整的。维基百科状态

相反,软件开发人员应该围绕数据库查询创建一个抽象接口,然后用他们自己的模拟对象实现该接口。通过从代码中抽象出这个必要的附件(暂时减少净有效耦合)

在您的情况下,您将模拟 FileReader loadend 事件,而不是数据库。

从测试的角度来看,您当前的代码需要进行小的重构才能变得可测试。单元测试的总体目标是单独测试小单元的功能。

单元测试的目标是隔离一个单元并验证其正确性。

“跳跃”函数依赖于附加到 onloadend 的嵌套箭头函数。您的代码直接调用了测试中已注释掉的代码,老实说,我有点惊讶于您的代码覆盖率并没有提高,并建议您确保您的代码覆盖率工具,如果您是伊斯坦布尔,则可能是使用 Jasmine 配置正确。

除了上述之外,您应该重构该嵌套函数,而是创建一个命名函数,然后您可以直接调用该函数进行单元测试。

这是实现您的功能的更好方法的一个(在我这边未经测试)示例。

jumping(inputValue: any): void {
    var that = this;
    var file: File = inputValue.files[0];

    var seat: FileReader = new FileReader();
    // bind the arguments for the event handler, first arg will be 'this' of the
    // loaded named function
    // second is 'that' variable, seat is seat and the final 'e' variable is
    // implicit and shouldn't be specified.
    seat.onloadend = loaded.bind(seat, that, seat); 

    $('.running').show();
    if (inputValue.files.length > 0) {
        var wholeQuantity = 0;

        wholeQuantity = inputValue.files[0].size / 1048576; //size in mb

        if (wholeQuantity > 5) {
            $('.stars').show();
            $("#laptop").val('');
            this.fileSelect = "";
        }

        seat.readAsDataURL(file);
    }
}

loaded(that: any, seat: any, e: any): void { // now a testable named function
    this.encodeBase64 = seat.result;
    that.fileSelect = $("#laptop").val().replace(/^.*\\/, "");
    if (that.fileSelect == '') {
        that.dragDrop = that.swimming;
    } else {
        that.dragDrop = "";
        that.dragDrop = that.fileSelect;
    }
}

一个覆盖上面写的'loaded'函数的所有代码行的测试示例如下:

describe('test suite', function () {
var old$ = $;

afterEach(function () {
    $ = old$;
});

it('covers all lines and else path on if but does not actually test anything', function () {
    $ = function () {
        val: function () {
            return 'Untitled-2.txt';
        }
    }; // stub JQuery

    var seat = {
        result: 'Base64encoded'
    };
    var scope = {};
    var that = {
        swimming: false,
        dragDrop: null
    };

    BasketballChainComponent.kickmobiles.loaded.call(scope, that, seat, null);
});

it('covers all lines and on if but not else and does not actually test anything', function () {
    $ = function () {
        val: function () {
            return '';
        }
    }; // stub JQuery

    var seat = {
        result: 'Base64encoded'
    };

    var scope = {};

    var that = {
        swimming: false,
        dragDrop: null
    };

    BasketballChainComponent.kickmobiles.loaded.call(scope, that, seat, null);
});

});

现在请注意,在现实世界中,您永远不应该仅仅为了代码覆盖率而编写测试,而实际上并没有测试给定的函数。它会让您产生一种错误的安全感,而不是真正测试您的代码。 MSDN 有话要说:

单元测试的主要目标是获取应用程序中最小的可测试软件,将其与代码的其余部分隔离,并确定其行为是否完全符合您的预期。

你正在做的一个类比如下:

您是一名汽车碰撞测试员。你的工作是验证汽车在碰撞中是否安全。所以一辆汽车以 10 公里/小时的速度发生碰撞,你需要检查一下。

您需要确认的事项清单。因此,在 10 公里/小时的碰撞中,您只希望油漆会被划伤。所以你看一下油漆,如果油漆被划伤但没有其他损坏,则测试通过。如果汽车有凹痕,则测试失败。

总体而言,这是一个很好的测试,因为它正在测试可量化的东西并且正在测试意图。

您在不实际测试功能的情况下尝试实现 100% 的代码覆盖率所做的事情是让汽车崩溃,然后什么都不验证。

您的意思是“我把车撞坏了,我真的不需要检查它在撞车事故中是否完成了它应该做的事情,只要我撞了它,对吗?”。

当然,您可以通过查看汽车获得 100% 的碰撞覆盖率,但如果不实际测试它,您可能甚至都没有费心。代码覆盖率是发现未经测试的代码的有用工具,它不用于实现获得完整代码覆盖率的任意指标。可以在Broken promise of 100% code coverage 阅读有关此内容的进一步阅读和出色的文章。

关键是

虽然测量代码覆盖率很容易,但这并不是一个好的衡量标准。即使您的代码覆盖率为 100%,您也可能会遇到麻烦。

我已经省略了中篇文章中的代码,但它继续说明:

此单元测试为 elementAtIndex: 函数生成了完美的 100% 测试覆盖率。 是否证明该功能正常工作?显然,答案是否定的。当我们超出数组边界时会发生什么? 为什么会这样?当您尝试关注代码覆盖率指标时,您编写的代码会查看测试函数/方法的实现。但该实现尚未被证明是正确的。这就是我们想要测试它的原因。 即使有这么简单的功能代码覆盖率,也不能作为衡量单元测试质量的好指标。

此外,上面我声明您应该测试代码的意图。 Medium 的文章也说明了这一点。

该怎么做呢?不要看方法的实际实现,而是看合约。精确查看任何特定输入的函数/方法的输出。查看此函数执行或使用的副作用。考虑可能存在的边缘情况。列出这些信息并据此进行测试。

记住 100% 的代码覆盖率并不意味着您的代码是 100% 正确的。

我希望这可以帮助您更好地理解单元测试这个概念。

【讨论】:

  • 你能在我的测试用例中更新一下吗...太混乱了:(
  • @texirv 是的,我可以并且会更新您的测试用例,但请仔细阅读此评论,它将为您省去未来的痛苦。在这样做之前,您应该尝试了解如何以及为什么要更新测试用例。你具体在挣扎什么?你到底想测试什么,为什么?此外,您是否只是想获得 100% 的代码覆盖率?如果是这样,为什么?请记住,在编写测试时,您不应该测试代码的实现。相反,您应该测试代码的意图。获得 100% 的代码覆盖率很容易,实际测试代码才是最难的部分。
  • 在我的测试用例中,我像这样调用了加载的方法---->BasketballChainComponent.kickmobiles.loaded('','',''); -----> 但我收到这样的错误 ------> `TypeError: Cannot create property 'fileSelect' on string '' ` 我试图理解....我想通过编写测试用例对于加载的方法,我会有更多的了解......你能帮我吗
  • @texirv 啊,如果您查看我的回答中加载的函数调用,您会注意到它将您的名为 seat 的变量作为其第一个参数。如果您查看“已加载”函数方法签名,您会注意到它的第一个参数是一个名为“that”(无引号)的变量。变量“that”通常用于访问其他函数的范围。我认为您现在的问题可能是缺乏 JavaScript 语言的基础知识。您可以像这样测试加载的函数:loaded.call(seat, that, seat, null);
  • 嘿,它会覆盖加载方法中的每一行...谢谢您的回复??
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-23
  • 2018-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多