如何在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

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


当前回答

这招对我很管用:

而不是:

sut.myPrivateMethod();

这样的:

sut['myPrivateMethod']();

其他回答

我采用的这种方法是在类外部创建函数,并将函数分配给我的私有方法。

export class MyClass {
  private _myPrivateFunction = someFunctionThatCanBeTested;
}

function someFunctionThatCanBeTested() {
  //This Is Testable
}

现在我不知道我破坏了哪种类型的OOP规则,但是为了回答这个问题,这是我测试私有方法的方法。我欢迎任何人就利弊提出建议。

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

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进行单元测试”是一个很好的目标,但有时它看起来并不那么简单,你会觉得你在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是一种代码气味,正如文档所说:

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

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

您应该测试类的公共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(公共接口)。 您应该将所讨论的逻辑移到它自己的类中,以使测试更容易。

“不要测试私有方法”的重点实际上是像使用它的人那样测试类。

如果你有一个带有5个方法的公共API,你的类的任何消费者都可以使用这些方法,因此你应该测试它们。使用者不应该访问类的私有方法/属性,这意味着在公共公开功能保持不变的情况下,可以更改私有成员。


如果依赖内部可扩展功能,请使用protected而不是private。 注意protected仍然是一个公共API(!),只是使用方式不同。

class OverlyComplicatedCalculator {
    public add(...numbers: number[]): number {
        return this.calculate((a, b) => a + b, numbers);
    }
    // can't be used or tested via ".calculate()", but it is still part of your public API!
    protected calculate(operation, operands) {
        let result = operands[0];
        for (let i = 1; i < operands.length; operands++) {
            result = operation(result, operands[i]);
        }
        return result;
    }
}

单元测试保护的属性与消费者使用它们的方式相同,通过子类化:

it('should be extensible via calculate()', () => {
    class TestCalculator extends OverlyComplicatedCalculator {
        public testWithArrays(array: any[]): any[] {
            const concat = (a, b) => [].concat(a, b);
            // tests the protected method
            return this.calculate(concat, array);
        }
    }
    let testCalc = new TestCalculator();
    let result = testCalc.testWithArrays([1, 'two', 3]);
    expect(result).toEqual([1, 'two', 3]);
});