如何在angular 2中测试私有函数?

class FooBar {

    private _status: number;

    constructor( private foo : Bar ) {
        this.initFooBar();

    }

    private initFooBar(){
        this.foo.bar( "data" );
        this._status = this.fooo.foo();
    }

    public get status(){
        return this._status;
    }

}

我找到了解决办法

将测试代码本身放在闭包中,或者在闭包中添加代码,以存储外部作用域中现有对象上局部变量的引用。 稍后使用工具提取测试代码。 http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/

如果你做过这个问题,请给我一个更好的解决方法。

P.S

大多数类似类型的问题的答案都没有给出问题的解决方案,这就是我问这个问题的原因 大多数开发人员说不要测试私有函数,但我不会说它们是错的还是对的,但我的案例中有必要测试私有函数。


当前回答

不要为私有方法编写测试。这就破坏了单元测试的意义。

您应该测试类的公共API 你不应该测试类的实现细节

例子

class SomeClass {

  public addNumber(a: number, b: number) {
      return a + b;
  }
}

如果之后实现发生变化,但公共API的行为保持不变,则该方法的测试不需要更改。

class SomeClass {

  public addNumber(a: number, b: number) {
      return this.add(a, b);
  }

  private add(a: number, b: number) {
       return a + b;
  }
}

不要仅仅为了测试而将方法和属性设为公共。这通常意味着:

您正在尝试测试实现,而不是API(公共接口)。 您应该将所讨论的逻辑移到它自己的类中,以使测试更容易。

其他回答

正如许多人已经指出的那样,尽管您想要测试私有方法,但您不应该通过修改代码或编译器来让它为您工作。现代的TypeScript会拒绝人们迄今为止提供的大部分hack。


解决方案

TLDR;如果一个方法需要测试,那么您应该将代码解耦到一个类中,以便将该方法公开以供测试。

你拥有私有方法的原因是因为功能不一定属于那个类,因此如果功能不属于那里,它应该解耦到它自己的类中。

例子

我偶然看到了这篇文章,它很好地解释了应该如何处理私有方法的测试。它甚至涵盖了这里的一些方法,以及为什么它们是糟糕的实现。

https://patrickdesjardins.com/blog/how-to-unit-test-private-method-in-typescript-part-2

注意:这段代码摘自上面链接的博客(我复制了以防链接后面的内容发生变化)

之前

class User {
    public getUserInformationToDisplay() {
        //...
        this.getUserAddress();
        //...
    }
 
    private getUserAddress() {
        //...
        this.formatStreet();
        //...
    }

    private formatStreet() {
        //...
    }
}

class User {
    private address: Address;

    public getUserInformationToDisplay() {
        //...
        address.format();
        //...
    }
}

class Address {
    private format: StreetFormatter;

    public format() {
        //...
        format.toString();
        //...
    }
}

class StreetFormatter {
    public toString() {
        // ...
    }
}

结束笔记

You can implicitly test your private methods by making sure that conditions are met such that the code is called through the public interface. If the public interface does not call out to the private methods then that code is not providing any function and should be removed. In the example above there should be some effect that calling the private method should return ie: an object with an address. If there's not, for example the code emits an event in a private method, then you should start looking to decouple that so that it can be tested -- even in that example you would likely listen/subscribe to that event and would be able to test it that way. Decoupling leads to better testability and easier code maintenance later down the road as well.

我同意你的观点,尽管“只对公共API进行单元测试”是一个很好的目标,但有时它看起来并不那么简单,你会觉得你在API和单元测试之间做出选择。你已经知道了,因为这正是你想要做的,所以我就不多说了。:)

在TypeScript中,我发现了一些为了单元测试而访问私有成员的方法。考虑这个类:

class MyThing {

    private _name:string;
    private _count:number;

    constructor() {
        this.init("Test", 123);
    }

    private init(name:string, count:number){
        this._name = name;
        this._count = count;
    }

    public get name(){ return this._name; }

    public get count(){ return this._count; }

}

尽管TS使用private、protected、public来限制对类成员的访问,但编译后的JS没有private成员,因为这在JS中是不存在的。它纯粹用于TS编译器。因此:

You can assert to any and escape the compiler from warning you about access restrictions: (thing as any)._name = "Unit Test"; (thing as any)._count = 123; (thing as any).init("Unit Test", 123); The problem with this approach is that the compiler simply has no idea what you are doing right of the any, so you don't get desired type errors: (thing as any)._name = 123; // wrong, but no error (thing as any)._count = "Unit Test"; // wrong, but no error (thing as any).init(0, "123"); // wrong, but no error This will obviously make refactoring more difficult. You can use array access ([]) to get at the private members: thing["_name"] = "Unit Test"; thing["_count"] = 123; thing["init"]("Unit Test", 123); While it looks funky, TSC will actually validate the types as if you accessed them directly: thing["_name"] = 123; // type error thing["_count"] = "Unit Test"; // type error thing["init"](0, "123"); // argument error To be honest I don't know why this works. This is apparently an intentional "escape hatch" to give you access to private members without losing type safety. This is exactly what I think you want for your unit-testing.

下面是TypeScript Playground中的一个工作示例。

编辑为TypeScript 2.6

另一个选项是使用// @ts-ignore(在TS 2.6中添加),它简单地抑制了下面一行的所有错误:

// @ts-ignore
thing._name = "Unit Test";

这样做的问题是,它抑制了下面一行的所有错误:

// @ts-ignore
thing._name(123).this.should.NOT.beAllowed("but it is") = window / {};

我个人认为@ts-ignore是一种代码气味,正如文档所说:

我们建议您尽量少使用这些注释。(强调原创)

由于大多数开发人员不建议测试私有函数,为什么不测试它呢?

Eg.

YourClass.ts

export class FooBar {
  private _status: number;

  constructor( private foo : Bar ) {
    this.initFooBar({});
  }

  private initFooBar(data){
    this.foo.bar( data );
    this._status = this.foo.foo();
  }
}

TestYourClass.spec.ts

describe("Testing foo bar for status being set", function() {

...

//Variable with type any
let fooBar;

fooBar = new FooBar();

...
//Method 1
//Now this will be visible
fooBar.initFooBar();

//Method 2
//This doesn't require variable with any type
fooBar['initFooBar'](); 
...
}

感谢@Aaron, @Thierry Templier。

不要为私有方法编写测试。这就破坏了单元测试的意义。

您应该测试类的公共API 你不应该测试类的实现细节

例子

class SomeClass {

  public addNumber(a: number, b: number) {
      return a + b;
  }
}

如果之后实现发生变化,但公共API的行为保持不变,则该方法的测试不需要更改。

class SomeClass {

  public addNumber(a: number, b: number) {
      return this.add(a, b);
  }

  private add(a: number, b: number) {
       return a + b;
  }
}

不要仅仅为了测试而将方法和属性设为公共。这通常意味着:

您正在尝试测试实现,而不是API(公共接口)。 您应该将所讨论的逻辑移到它自己的类中,以使测试更容易。

我同意@toskv的观点:我不建议这么做:-)

但是如果你真的想测试你的私有方法,你可以意识到TypeScript的相应代码对应于构造函数原型的一个方法。这意味着它可以在运行时使用(然而您可能会有一些编译错误)。

例如:

export class FooBar {
  private _status: number;

  constructor( private foo : Bar ) {
    this.initFooBar({});
  }

  private initFooBar(data){
    this.foo.bar( data );
    this._status = this.foo.foo();
  }
}

将转化为:

(function(System) {(function(__moduleName){System.register([], function(exports_1, context_1) {
  "use strict";
  var __moduleName = context_1 && context_1.id;
  var FooBar;
  return {
    setters:[],
    execute: function() {
      FooBar = (function () {
        function FooBar(foo) {
          this.foo = foo;
          this.initFooBar({});
        }
        FooBar.prototype.initFooBar = function (data) {
          this.foo.bar(data);
          this._status = this.foo.foo();
        };
        return FooBar;
      }());
      exports_1("FooBar", FooBar);
    }
  }
})(System);

查看这个plunkr: https://plnkr.co/edit/calJCF?p=preview。