Tìm kiếm trang web

Cách viết kiểm thử đơn vị cho Angular/TypeScript cho các phương thức riêng tư với Jasmine


Làm thế nào để kiểm thử một hàm riêng tư trong 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;
    }

}

Giải pháp mà tôi tìm thấy

  1. Đặt chính mã kiểm tra bên trong closure hoặc Thêm mã bên trong closure lưu trữ các tham chiếu đến các biến cục bộ trên các đối tượng hiện có trong phạm vi bên ngoài.

    Sau đó loại bỏ mã kiểm tra bằng cách sử dụng một công cụ. http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/

Xin vui lòng gợi ý cho tôi một cách tốt hơn để giải quyết vấn đề này nếu bạn đã từng làm điều đó?

Tái bút

  1. Hầu hết các câu trả lời cho những câu hỏi loại tương tự như thế này không đưa ra giải pháp cho vấn đề, đó là lý do tại sao tôi lại hỏi câu hỏi này

  2. Hầu hết các nhà phát triển đều nói rằng bạn không nên kiểm thử các hàm riêng tư, nhưng tôi không nói họ đúng hay sai, chỉ là trong trường hợp của tôi thì cần thiết phải kiểm thử các hàm riêng tư.

Câu trả lời #1

Tôi đồng ý với bạn, mặc dù việc "chỉ unit test API công khai" là một mục tiêu tốt, nhưng có những lúc điều đó không đơn giản như vậy và bạn cảm thấy mình đang phải lựa chọn giữa việc thỏa hiệp API hoặc unit test. Bạn đã biết điều này rồi, vì đó chính xác là điều bạn đang hỏi để làm, nên tôi sẽ không đi sâu vào vấn đề này. :)

Trong TypeScript, tôi đã phát hiện một vài cách bạn có thể truy cập các thành viên riêng tư nhằm mục đích kiểm thử đơn vị. Hãy xem xét lớp này:

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; }

}

Mặc dù TS hạn chế truy cập các thành viên lớp bằng cách sử dụng private, protected, public, nhưng JS biên dịch không có thành viên riêng tư, vì điều này không tồn tại trong JS. Nó chỉ được sử dụng cho trình biên dịch TS. Do đó:

  1. Bạn có thể ép kiểu sang any và thoát khỏi cảnh báo của trình biên dịch về các hạn chế truy cập:

    (thing as any)._name = "Unit Test";
    (thing as any)._count = 123;
    (thing as any).init("Unit Test", 123);
    

    Vấn đề với cách tiếp cận này là trình biên dịch hoàn toàn không biết bạn đang làm gì ngay sau any, vì vậy bạn sẽ không nhận được lỗi kiểu mong muốn:

    (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
    

    Điều này rõ ràng sẽ làm việc tái cấu trúc trở nên khó khăn hơn.

  2. Bạn có thể sử dụng truy cập mảng ([]) để lấy các thành viên riêng tư:

    thing["_name"] = "Unit Test";
    thing["_count"] = 123;
    thing["init"]("Unit Test", 123);
    

    Mặc dù trông có phần lạ lùng, TSC thực sự sẽ xác thực các kiểu như thể bạn truy cập chúng trực tiếp:

    thing["_name"] = 123; // type error
    thing["_count"] = "Unit Test"; // type error
    thing["init"](0, "123"); // argument error
    

    Thành thật mà nói, tôi không biết tại sao điều này lại hiệu quả. Đây rõ ràng là một "lối thoát" cố ý để cho phép bạn truy cập vào các thành viên riêng tư mà không mất an toàn kiểu. Đây chính xác là những gì tôi nghĩ bạn muốn cho việc kiểm thử đơn vị của mình.

Đây là một ví dụ hoạt động trong TypeScript Playground.

Chỉnh sửa cho TypeScript 2.6

Một lựa chọn khác mà một số người thích là sử dụng // @ts-ignore (đã được thêm vào trong TS 2.6) mà đơn giản là bỏ qua tất cả lỗi trên dòng tiếp theo:

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

Vấn đề với điều này là, à, nó chặn tất cả các lỗi trên dòng tiếp theo:

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

Cá nhân tôi coi @ts-ignore là dấu hiệu xấu trong mã, và như tài liệu nói:

chúng tôi khuyên bạn nên sử dụng các chú thích này rất hạn chế. [nhấn mạnh theo nguyên bản]

Câu trả lời #2

Bạn CÓ THỂ gọi các phương thức riêng tư!

Nếu bạn gặp phải lỗi sau:

expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/)
// TS2341: Property 'initFooBar' is private and only accessible within class 'FooBar'

chỉ cần sử dụng // @ts-ignore:

// @ts-ignore
expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/)

Cảm ơn @Moff452 về bình luận của anh/chị ấy. Bạn cũng có thể viết:

expect(new FooBar(/*...*/)['initFooBar']()).toEqual(/*...*/)

CẬP NHẬT :

@ts-expect-error là một lựa chọn tốt hơn cho @ts-ignore. Xem: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#ts-ignore-or-ts-expect-error

Câu trả lời #3

Điều này đã hiệu quả với tôi:

Thay vì:

sut.myPrivateMethod();

Cái này :

sut['myPrivateMethod']();

Câu trả lời số 4

Vì hầu hết các nhà phát triển không khuyến nghị kiểm thử các hàm riêng tư, tại sao không thử kiểm thử nó?

Ví dụ

LopCuaBan.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'](); 
...
}

Cảm ơn @Aaron, @Thierry Templier.

Câu trả lời số 5

Gọi phương thức riêng tư bằng cách sử dụng dấu ngoặc vuông.

Tập tin Ts

class Calculate{
  private total;
  private add(a: number) {
      return a + total;
  }
}

tệp spect.ts

it('should return 5 if input 3 and 2', () => {
    component['total'] = 2;
    let result = component['add'](3);
    expect(result).toEqual(5);
});

Bài viết liên quan