Jasmine ile özel yöntemler için Angular / TypeScript için birim testi yazma


197

Açısal 2'de özel bir işlevi nasıl test edersiniz?

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

}

Bulduğum çözüm

  1. Test kodunun kendisini kapağın içine yerleştirin veya dış değişkente varolan nesnelerdeki yerel değişkenlere başvuruları depolayan kapağın içine kod ekleyin.

    Daha sonra bir araç kullanarak test kodunu çıkarın. http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/

Yaptıysanız lütfen bu sorunu çözmenin daha iyi bir yolunu önerin?

PS

  1. Bunun gibi benzer bir sorunun cevabının çoğu soruna bir çözüm vermiyor, bu yüzden bu soruyu soruyorum

  2. Geliştiricilerin çoğu, özel işlevleri test etmediğinizi söylüyorlar, ancak yanlış veya doğru olduklarını söylemiyorum, ancak durumumun özel olarak test edilmesi için gereksinimler var.


11
testler özel uygulamayı değil, yalnızca genel arayüzü test etmelidir. Genel arayüzde yaptığınız testler özel bölümü de kapsamalıdır.
toskv

16
Cevapların yarısının aslında yorum olması gerektiğini seviyorum. OP soru sorar, nasıl X? Kabul edilen cevap aslında size X'in nasıl yapılacağını söyler. Sonra geri kalanların çoğu dönüp söyler, sadece X'e söylemeyeceğim (ki bu açıkça mümkündür) ama Y yapmalısınız. Çoğu birim test aracı (Ben değilim) sadece JavaScript hakkında konuşmak) özel işlevleri / yöntemleri test etme yeteneğine sahiptir. Neden olduğunu açıklamaya devam edeceğim çünkü JS ülkesinde kaybolmuş gibi görünüyor (görünüşe göre, cevapların yarısı verildi).
Kuaterniyon

13
Bir sorunu yönetilebilir görevlere bölmek iyi bir programlama uygulamasıdır, bu nedenle "foo (x: type)" işlevi özel işlevleri a (x: type), b (x: type), c (y: another_type) ve d ( z: yet_another_type). Foo, çağrıları yönettiği ve bir araya getirdiği için, bir akıntıdaki kayaların arka tarafları gibi, tüm aralıkların test edilmesini sağlamak gerçekten zor olan gölgeler gibi bir tür türbülans yaratır. Bu nedenle, her bir alt aralık kümesinin geçerli olduğundan emin olmak daha kolaydır, yalnızca ana "foo" yu test etmeye çalışırsanız, aralık testi durumlarda çok karmaşık hale gelir .
Kuaterniyon

18
Bu, genel arayüzü test etmediğiniz anlamına gelmez, açıkçası yaparsınız, ancak özel yöntemleri test etmek, bir dizi kısa yönetilebilir parçayı test etmenize izin verir (bunları ilk başta yazmanızın nedeni de neden geri alıyorsunuz? bu, test söz konusu olduğunda) ve genel arayüzlerdeki testlerin geçerli olması (belki de çağrı fonksiyonu giriş aralıklarını kısıtlıyor), daha gelişmiş mantık eklediğinizde ve bunları diğerlerinden çağırdığınızda özel yöntemlerin kusurlu olmadığı anlamına gelmez. yeni ebeveyn işlevleri,
Kuaterniyon

5
TDD ile düzgün bir şekilde test ederseniz, daha sonra ne zaman doğru bir şekilde test etmeniz gerektiğini anlamaya çalışmayacaksınız.
Kuaterniyon

Yanıtlar:


345

"Sadece ortak API'yı birim test etmek" için iyi bir hedef olsa da, bu kadar basit görünmediği zamanlar var ve API veya birim testlerinden ödün vermek arasında seçim yaptığınızı hissediyorum. Bunu zaten biliyorsunuz, çünkü tam olarak yapmak istediğiniz şey bu, bu yüzden içine girmeyeceğim. :)

TypeScript'te, birim testi için özel üyelere erişmenin birkaç yolunu keşfettim. Bu sınıfı düşünün:

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

}

Sınıf üyelerine TS kısıtlar erişim kullanarak rağmen private, protected, publicbu JS bir şey olmadığı için, derlenmiş JS, hiçbir özel üyesi vardır. Tamamen TS derleyicisi için kullanılır. şöyle ki:

  1. anyDerleyiciye erişim kısıtlamaları konusunda sizi uyarabilir ve ondan kaçabilirsiniz:

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

    Bu yaklaşımla ilgili sorun, derleyicinin doğru ne yaptığınızı bilmemesi any, bu nedenle istediğiniz tür hataları almamanızdır:

    (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

    Bu, yeniden düzenlemeyi daha zor hale getirecektir.

  2. []Özel üyelere ulaşmak için dizi erişimini ( ) kullanabilirsiniz:

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

    Korkak gibi görünse de TSC, türleri doğrudan erişmiş gibi onaylar:

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

    Dürüst olmak gerekirse bunun neden işe yaradığını bilmiyorum. Görünüşe göre bu, tip güvenliğini kaybetmeden özel üyelere erişim sağlamak için kasıtlı bir "kaçış kapağı" dır . Ünite testiniz için tam olarak istediğinizi düşünüyorum.

İşte TypeScript Playground'da çalışan bir örnek .

TypeScript 2.6 için düzenleme

Bazılarının sevdiği bir başka seçenek de , aşağıdaki satırdaki tüm hataları bastıran kullanımı // @ts-ignore( TS 2.6'da eklenmiştir ):

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

Bununla ilgili sorun, aşağıdaki satırdaki tüm hataları bastırmasıdır:

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

Ben şahsen @ts-ignorebir kod kokusu olduğunu düşünüyorum ve dokümanlar söylediği gibi:

bu yorumları çok az kullanmanızı öneririz . [vurgu orijinal]


45
Standart test cihazı dogma yerine gerçek bir çözümle birlikte birim testinde gerçekçi bir duruş duymak çok güzel.
d512

2
Davranışın bazı "resmi" açıklaması (hatta bir kullanım örneği olarak birim testine atıfta bulunur): github.com/microsoft/TypeScript/issues/19335
Aaron Beall

1
Aşağıda belirtildiği gibi `// @ ts-ignore` öğesini kullanın. özel erişimci görmezden linter söyler
Tommaso

1
@Tommaso Evet, bu başka bir seçenek, ama aynı dezavantajı var as any: tüm tip kontrolünü kaybedersiniz.
Aaron Beall

2
Bir süredir gördüğüm en iyi cevap, teşekkürler @AaronBeall. Ve ayrıca, orijinal soruyu sorduğunuz için teşekkürler tymspy.
nicolas.leblanc

27

Özel yöntemleri arayabilirsiniz . Aşağıdaki hatayla karşılaştıysanız:

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

sadece kullanın // @ts-ignore:

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

bu üstte olmalı!
jsnewbie

2
Bu kesinlikle başka bir seçenek. as anyHerhangi bir tür denetimini kaybetmenizle aynı sorundan muzdariptir , aslında tüm satırda herhangi bir tür denetimini kaybedersiniz.
Aaron Beall

20

Geliştiricilerin çoğu özel işlevi test etmeyi önermediğinden, neden test etmiyorsunuz?

Örneğin.

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 sayesinde.


1
Ben özel / korumalı bir yöntemi çağırmaya çalıştığınızda patlama yazı linting hataları verir düşünüyorum.
Gudgip

1
@Gudgip yazım hataları verir ve derlenmez. :)
tymspy

10

Özel yöntemler için testler yazmayın. Bu, birim testlerin noktasını yener.

  • Sınıfınızın herkese açık API'sını test ediyor olmalısınız
  • Sınıfınızın alıştırma ayrıntılarını test ETMEMELİSİNİZ

Misal

class SomeClass {

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

Daha sonra uygulama değişirse, ancak behaviourgenel API'nin aynı kalması durumunda bu yöntemin testinin değiştirilmesi gerekmez .

class SomeClass {

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

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

Yöntemleri ve özellikleri yalnızca test etmek için herkese açık hale getirmeyin. Bu genellikle şunlardan biri anlamına gelir:

  1. API (genel arayüz) yerine uygulamayı test etmeye çalışıyorsunuz.
  2. Testi kolaylaştırmak için söz konusu mantığı kendi sınıfına taşımalısınız.

3
Belki de yorum yapmadan önce yazıyı okuyun. Test ayrıcalıklarının, davranıştan ziyade, uygulamaların test edilmesinin bir kokusu olduğunu açıkça gösteriyor ve kırılgan testlere yol açıyor.
Martin

1
Size 0 ile x özel özelliği arasında rastgele bir sayı veren bir nesne düşünün. X'in kurucu tarafından doğru bir şekilde ayarlanıp ayarlanmadığını bilmek istiyorsanız, x değerini test etmek, aldığınız sayıların doğru aralıkta olup olmadığını kontrol etmek için yüz test yapmaktan çok daha kolaydır.
Galdor

1
@ user3725805 Bu, davranışı değil uygulamayı test etme örneğidir. Özel numaranın nereden geldiğini izole etmek daha iyi olurdu: bir sabit, bir yapılandırma, yapıcı - ve oradan test. Eğer özel olan başka bir kaynaktan gelmezse, o zaman “sihirli sayı” karşıtlığına düşer.
Martin

1
Ve uygulamayı test etmesine neden izin verilmiyor? Birim testleri, beklenmedik değişiklikleri tespit etmek için iyidir. Herhangi bir nedenle kurucu numarayı ayarlamayı unuttuğunda, test hemen başarısız olur ve beni uyarır. Birisi uygulamayı değiştirdiğinde test de başarısız olur, ancak saptanamayan bir hataya sahip olmaktan ziyade bir test yapmayı tercih ederim.
Galdor

2
+1. Mükemmel cevap. @TimJames Doğru uygulamayı söylemek ya da hatalı yaklaşımı göstermek SO'nun amacıdır. OP'nin istediği her şeyi başarmak için hileli kırılgan bir yol bulmak yerine.
Syed Aqeel Ashiq

4

"Özel yöntemleri test etme" nin asıl amacı sınıfı kullanan biri gibi sınamaktır .

5 yöntemle herkese açık bir API'nız varsa, sınıfınızın herhangi bir tüketicisi bunları kullanabilir ve bu nedenle bunları test etmeniz gerekir. Tüketici, sınıfınızın özel yöntemlerine / özelliklerine erişmemelidir; başka bir deyişle, herkese açık durumdaki işlevler aynı kaldığında özel üyeleri değiştirebilirsiniz.


Dahili genişletilebilir işlevselliğe güveniyorsanız, protectedyerine kullanın private. Hala farklı bir şekilde kullanılan herkese açık bir API (!) Olduğunu
unutmayın .protected

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

Korumalı özellikleri, bir tüketicinin bunları kullanacağı şekilde alt sınıflandırma yoluyla birim test edin:

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

3

Bu benim için çalıştı:

Onun yerine:

sut.myPrivateMethod();

Bu:

sut['myPrivateMethod']();

2

Bu yazıdaki nekro için özür dilerim, ama dokunulmamış gibi görünen bazı şeyleri tartmak zorunda hissediyorum.

Her şeyden önce - birim testi sırasında kendimizi bir sınıfa özel üyelere erişmeye ihtiyaç duyduğumuzda, genellikle stratejik veya taktik yaklaşımımızda göz ardı ettiğimiz ve yanlışlıkla tek sorumluluk prensibini zorlayarak ihlal ettiğimiz büyük, şişman bir kırmızı bayraktır. ait olmadığı davranış. Bir inşaat prosedürünün yalıtılmış bir altyordamından başka bir şey olmayan yöntemlere erişim ihtiyacını hissetmek, bunun en yaygın olaylarından biridir; ancak, patronunuz sizi çalışmaya hazır olarak görünmenizi bekliyor ve aynı zamanda sizi bu duruma sokmak için hangi sabah rutini yaşadığınızı bilmeye ihtiyaç duyuyor ...

Bu durumun en yaygın diğer örneği, kendinizi meşhur "tanrı sınıfını" denemeye çalışırken bulmanızdır. Bu, kendi başına özel bir tür problemdir, ancak bir prosedürün samimi ayrıntılarını bilmeye ihtiyaç duymakla aynı temel sorundan muzdariptir - ancak bu konudan çıkıyor.

Bu özel örnekte, Bar nesnesini FooBar sınıfının yapıcısına tam olarak başlatma sorumluluğunu etkin bir şekilde belirledik. Nesne yönelimli programlamada, temel kiracılardan biri, kurucunun "kutsal" olması ve kendi iç durumunu geçersiz kılacak ve aşağı akışta başka bir yerde başarısız olmaya başlayabilecek geçersiz verilere karşı korunması gerektiğidir. boru hattı.)

Burada FooBar nesnesinin, FooBar'ın oluşturulduğu sırada hazır olmayan bir Çubuğu kabul etmesine izin vererek başarısız olduk ve FooBar nesnesinin konuları kendi haline getirmesi gibi bir tür "hackleme" ile telafi ettiler. eller.

Bu, nesne yönelimli başka bir programa (Bar durumunda) uymamanın bir sonucudur, bu da bir nesnenin durumunun tamamen başlatılmalı ve oluşturulduktan hemen sonra 'genel üyelerine gelen çağrıları işlemeye hazır olmalıdır. Şimdi, bu, tüm durumlarda kurucu çağrıldıktan hemen sonra anlamına gelmez. Birçok karmaşık inşaat senaryosuna sahip bir nesneniz olduğunda, ayarlayıcıları isteğe bağlı üyelerine bir tasarım tasarım modeline (Fabrika, Oluşturucu vb.) Göre uygulanan bir nesneye maruz bırakmak daha iyidir. ikinci durumlar,

Örneğinizde, Çubuğun "durum" özelliği, bir FooBar'ın kabul edebileceği geçerli bir durumda görünmüyor - bu nedenle FooBar bu sorunu düzeltmek için bir şeyler yapıyor.

Gördüğüm ikinci sorun, test odaklı geliştirme uygulamak yerine kodunuzu test etmeye çalıştığınızdır. Bu, kesinlikle bu noktada benim görüşüm; ancak, bu tür testler gerçekten bir anti-kalıptır. Yapmanız gereken şey, ihtiyacınız olan testleri yazmak ve daha sonra testleri programlamak yerine, kodunuzun gerçekte sonra test edilebilir olmasını önleyen temel tasarım sorunlarınız olduğunu fark etme tuzağına düşmektir. Her iki durumda da sorunla karşılaşırsanız, gerçekten bir SOLID uygulaması elde etmiş olsanız bile aynı sayıda test ve kod satırıyla karşılaşmalısınız. Öyleyse, neden geliştirme çabalarınızın başlangıcında konuyu ele alabildiğinizde neden test edilebilir koda dönüştürebilirsiniz?

Bunu yapmış olsaydınız, tasarımınızı test etmek için oldukça icky kod yazmak zorunda kalacağınızı çok daha önce fark etmiş olacaktınız ve davranışı uygulamalara katarak yaklaşımınızı yeniden hizalama fırsatını daha önce bulabildiniz. kolayca test edilebilir.


2

Katılıyorum @toskv: Bunu tavsiye etmem :-)

Ancak özel yönteminizi gerçekten test etmek istiyorsanız, TypeScript için karşılık gelen kodun yapıcı işlev prototipinin bir yöntemine karşılık geldiğinin farkında olabilirsiniz. Bu, çalışma zamanında kullanılabileceği anlamına gelir (oysa muhtemelen bazı derleme hatalarınız olacaktır).

Örneğin:

export class FooBar {
  private _status: number;

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

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

aktarılacak:

(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);

Şu plunkr'a bakın: https://plnkr.co/edit/calJCF?p=preview .


1

Birçok kişinin belirttiği gibi, özel yöntemleri test etmek istediğiniz kadar, sizin için çalışmasını sağlamak için kodunuzu veya transpiler'ınızı hacklememelisiniz. Günümüz TypeScript, insanların bugüne kadar sağladığı tüm saldırıları reddedecek.


Çözüm

TLDR ; bir yöntemin sınanması gerekiyorsa, kodu sınanacak genel bir yöntemle karşılaştırabileceğiniz bir sınıfa ayırmanız gerekir.

Özel yöntemi kullanmanızın nedeni, işlevsellik mutlaka bu sınıfa maruz kalmaya ait değildir ve bu nedenle işlevsellik oraya ait değilse, kendi sınıfına ayrılmalıdır.

Misal

Özel yöntemlerin test edilmesiyle nasıl başa çıkmanız gerektiğini açıklayan harika bir iş çıkaran bu makaleyle karşılaştım. Hatta buradaki bazı yöntemleri ve bunların neden kötü uygulamalar olduklarını da kapsıyor.

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

Not : Bu kod yukarıda bağlantılı blogdan kaldırılmıştır (bağlantının arkasındaki içerik değişirse çoğalıyorum)

Önce
class User{
    public getUserInformationToDisplay(){
        //...
        this.getUserAddress();
        //...
    }

    private getUserAddress(){
        //...
        this.formatStreet();
        //...
    }
    private formatStreet(){
        //...
    }
}
Sonra
class User{
    private address:Address;
    public getUserInformationToDisplay(){
        //...
        address.getUserAddress();
        //...
    }
}
class Address{
    private format: StreetFormatter;
    public format(){
        //...
        format.ToString();
        //...
    }
}
class StreetFormatter{
    public toString(){
        // ...
    }
}

1

köşeli parantez kullanarak özel yöntemi çağırın

Ts dosyası

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

spect.ts dosyası

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

0

Aaron'un cevabı en iyisi ve benim için çalışıyor :) Oy verdim ama ne yazık ki yapamam (ününü kaybeden).

Özel yöntemleri test etmek onları kullanmak ve diğer tarafta temiz kod var tek yolu olduğunu söylemek gerekir.

Örneğin:

class Something {
  save(){
    const data = this.getAllUserData()
    if (this.validate(data))
      this.sendRequest(data)
  }
  private getAllUserData () {...}
  private validate(data) {...}
  private sendRequest(data) {...}
}

Tüm bu yöntemleri bir kerede test etmemek çok mantıklı, çünkü alay edemeyeceğimiz bu özel yöntemleri alay etmemiz gerekecek çünkü onlara erişemiyoruz. Bu, bir bütün olarak test etmek için bir birim testi için çok fazla yapılandırmaya ihtiyacımız olduğu anlamına gelir.

Bu, yukarıdaki yöntemi tüm bağımlılıklarla test etmenin en iyi yolunun bir uçtan uca test olduğunu söyledi, çünkü burada bir entegrasyon testi gerekiyor, ancak TDD (Test Odaklı Geliştirme) uygularsanız E2E testi size yardımcı olmaz, ancak test herhangi bir yöntem olacaktır.


0

Bu rota, sınıfın dışında işlevler oluşturduğum ve bu işlevi özel yöntemime atadığım bir rota.

export class MyClass {
  private _myPrivateFunction = someFunctionThatCanBeTested;
}

function someFunctionThatCanBeTested() {
  //This Is Testable
}

Şimdi ne tür OOP kurallarını ihlal ettiğimi bilmiyorum, ama soruyu cevaplamak için özel yöntemleri bu şekilde test ediyorum. Artılarını ve eksilerini bu tavsiye herkes hoş geldiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.