각도 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/
이 문제를 해결하는 더 좋은 방법을 제안하십시오.
추신
-
이와 비슷한 유형의 질문에 대한 대부분의 대답은 문제에 대한 해결책을 제공하지 않으므로이 질문을하는 이유입니다.
-
대부분의 개발자는 개인 기능을 테스트하지 않는다고 말하지만 기능이 잘못되었거나 옳다고 말하지는 않지만 개인의 경우를 테스트해야 할 필요성이 있습니다.
답변
“공용 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있는 것은 아니기 때문에, 컴파일 된 JS, 아니 개인 회원이 없습니다. TS 컴파일러에만 사용됩니다. 그 때문에:
-
any
액세스 제한에 대해 경고하지 않도록 컴파일러를 주장 하고 이스케이프 처리 할 수 있습니다 .(thing as any)._name = "Unit Test"; (thing as any)._count = 123; (thing as any).init("Unit Test", 123);
이 접근법의 문제점은 컴파일러가 단순히 당신이 무엇을하고 있는지 전혀 알지
any
못하므로 원하는 유형 오류가 발생하지 않는다는 것입니다.(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
리팩토링이 더 어려워 질 것입니다.
-
배열 액세스 (
[]
)를 사용 하여 개인 멤버를 확보 할 수 있습니다 .thing["_name"] = "Unit Test"; thing["_count"] = 123; thing["init"]("Unit Test", 123);
펑키하게 보이지만 TSC는 실제로 직접 액세스 한 것처럼 유형을 확인합니다.
thing["_name"] = 123; // type error thing["_count"] = "Unit Test"; // type error thing["init"](0, "123"); // argument error
솔직히 말해서 이것이 왜 효과가 있는지 모르겠습니다.이것은 의도적 인 “탈출 해치” 로, 유형 안전을 잃지 않으면 서 개인 회원에게 액세스 할 수 있습니다. 이것이 바로 당신이 단위 테스트를 원하는 것입니다.
다음은 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
으로 코드 냄새를 고려 하고 문서에서 말하는 것처럼 :
이 주석은 매우 드물게 사용하는 것이 좋습니다 . 【강조 오리지날】
답변
개인 메소드를 호출 할 수 있습니다 . 다음 오류가 발생한 경우 :
expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/);
// TS2341: Property 'initFooBar' is private and only accessible within class 'FooBar'
그냥 사용하십시오 // @ts-ignore
:
// @ts-ignore
expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/);
답변
대부분의 개발자 는 private function 테스트를 권장하지 않으므로 테스트하지 않겠 습니까?.
예 :
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;
}
}
나중에 구현이 변경되지만 behaviour
퍼블릭 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]);
});
답변
이것은 나를 위해 일했다 :
대신에:
sut.myPrivateMethod();
이:
sut['myPrivateMethod']();
답변
이 게시물의 괴상한 점에 대해 죄송하지만 만지지 않은 것 몇 가지에 무게를 두어야한다고 생각합니다.
가장 먼저-단위 테스트 중 클래스의 개인 멤버에 액세스해야하는 경우 전략적 또는 전술적 접근 방식에서 실수를했으며 부주의하게 단일 책임 원칙을 위반하여 큰 책임을지었습니다. 속하지 않는 행동. 실제로 구축 절차의 분리 된 서브 루틴에 지나지 않는 메소드에 액세스 할 필요성을 느끼는 것이 가장 흔한 일 중 하나입니다. 그러나 상사는 당신이 출근 준비를 위해 출근하기를 기대하고 그 주에 당신을 데려 오기 위해 어떤 아침 일과를 겪어야했는지에 대해 약간의 괴로운 필요성을 느끼는 것과 같습니다.
이런 일이 발생하는 가장 일반적인 예는 자신이 속담 “신 클래스”를 테스트하려고 할 때입니다. 그것은 그 자체로는 특별한 종류의 문제이지만 절차의 세부적인 내용을 알아야하는 것과 동일한 기본 문제로 어려움을 겪지 만 주제를 벗어납니다.
이 특정 예제에서는 FooBar 클래스의 생성자에 Bar 객체를 완전히 초기화하는 책임을 효과적으로 할당했습니다. 객체 지향 프로그래밍에서 핵심 테넌트 중 하나는 생성자가 “신성한”상태이며 자체 내부 상태를 무효화하고 다운 스트림 다른 곳에서 실패 할 가능성이있는 잘못된 데이터로부터 보호해야한다는 것입니다. 관로.)
우리는 FooBar 객체가 FooBar가 생성 될 때 준비되지 않은 Bar를 받아들이고 FooBar 객체가 자체적으로 문제를 해결하기 위해 일종의 “해킹”에 의해 보상 된 바를 받아 들여서이를 수행하지 못했습니다. 소유.
이는 객체 지향 프로그래밍의 다른 테넌트 (Bar의 경우)를 준수하지 못한 결과로, 객체의 상태가 완전히 초기화되고 생성 된 직후 공개 멤버에게 들어오는 호출을 처리 할 준비가되어 있어야합니다. 이제 이것은 모든 인스턴스에서 생성자가 호출 된 직후를 의미하지는 않습니다. 복잡한 구성 시나리오가 많은 개체가있는 경우 생성자 디자인 패턴 (Factory, Builder 등)에 따라 구현 된 개체에 선택적 멤버의 설정자를 노출시키는 것이 좋습니다. 후자의 경우
귀하의 예에서 Bar의 “status”속성은 FooBar가이를 승인 할 수있는 유효한 상태가 아닌 것 같습니다. 따라서 FooBar는 해당 문제를 해결하기 위해 무언가를 수행합니다.
내가보고있는 두 번째 문제는 테스트 중심 개발을 연습하지 않고 코드를 테스트하려고한다는 것입니다. 이 시점에서 이것은 내 자신의 의견이다. 그러나 이러한 유형의 테스트는 실제로 반 패턴입니다. 결국 테스트는 필요한 테스트를 작성하고 테스트에 프로그래밍하는 것이 아니라 실제로 테스트 후에 코드를 테스트 할 수 없도록하는 핵심 디자인 문제가 있다는 사실을 깨닫게됩니다. 어떤 방식 으로든 문제가 발생하더라도 SOLID 구현을 실제로 달성 한 경우에도 동일한 수의 테스트와 코드 줄을 사용해야합니다. 그렇다면 개발 노력을 시작할 때 문제를 해결할 수있을 때 왜 테스트 가능한 코드로 리버스 엔지니어링하려고합니까?
그렇게했다면 디자인을 테스트하기 위해 다소 이상한 코드를 작성해야한다는 사실을 훨씬 일찍 깨달았을 것입니다. 행동을 구현으로 전환하여 접근 방식을 재조정 할 수있는 기회를 일찍 얻었을 것입니다. 쉽게 테스트 할 수 있습니다.