Özel bir kurucuya test kapsamı nasıl eklenir?


110

Kod bu:

package com.XXX;
public final class Foo {
  private Foo() {
    // intentionally empty
  }
  public static int bar() {
    return 1;
  }
}

Bu test:

package com.XXX;
public FooTest {
  @Test 
  void testValidatesThatBarWorks() {
    int result = Foo.bar();
    assertEquals(1, result);
  }
  @Test(expected = java.lang.IllegalAccessException.class)
  void testValidatesThatClassFooIsNotInstantiable() {
    Class cls = Class.forName("com.XXX.Foo");
    cls.newInstance(); // exception here
  }
}

İyi çalışıyor, sınıf test edildi. Ancak Cobertura, sınıfın özel kurucusunun sıfır kod kapsamı olduğunu söylüyor. Böyle özel bir kurucuya nasıl test kapsamı ekleyebiliriz?


Bana sanki Singleton modelini uygulamaya çalışıyormuşsunuz gibi geliyor. Öyleyse, dp4j.com'u beğenebilirsiniz (tam olarak bunu yapar)
simpatico

"kasıtlı olarak boş", istisna atma ile değiştirilmemeli mi? Bu durumda, belirli bir mesajla bu özel istisnayı bekleyen bir test yazabilirsiniz, hayır? bunun
abartılı

Yanıtlar:


85

Pekala, potansiyel olarak yansımayı kullanabileceğiniz yollar var - ama buna gerçekten değer mi? Bu asla çağrılmaması gereken bir kurucu , değil mi?

Cobertura'nın çağrılmayacağını anlamasını sağlamak için sınıfa ekleyebileceğiniz bir not veya benzeri bir şey varsa, bunu yapın: Yapay olarak kapsama eklemek için çemberlerden geçmeye değeceğini sanmıyorum.

DÜZENLEME: Bunu yapmanın bir yolu yoksa, sadece biraz azaltılmış kapsama alanıyla yaşayın. Kapsamın sizin için yararlı olan bir şey olduğunu unutmayın - tam tersi değil, araçtan siz sorumlu olmalısınız.


18
Sırf bu kurucu yüzünden tüm projede "kapsamı biraz azaltmak" istemiyorum ..
yegor256

36
@Vincenzo: O halde IMO, basit bir sayıya çok yüksek bir değer koyuyorsunuz. Kapsam, testin bir göstergesidir . Bir alete köle olmayın. Kapsama noktası, size bir güven düzeyi sağlamak ve ekstra testler için alanlar önermektir. Başka türlü kullanılmayan bir kurucuyu yapay olarak çağırmak bu noktalardan hiçbirine yardımcı olmaz.
Jon Skeet

19
@JonSkeet: "Bir aletin kölesi olma" fikrine tamamen katılıyorum, ancak her projedeki her "kusur sayısını" hatırlamak güzel kokmuyor. 7/9 sonucunun programcı değil Cobertura sınırlaması olduğundan nasıl emin olunur? Yeni bir programcı, sınıf bazında kontrol etmek için her başarısızlığı (büyük projelerde çok fazla olabilir) girmelidir.
Eduardo Costa

5
Bu soruya cevap vermiyor. ve bu arada, bazı yöneticiler kapsam sayılarına bakıyor. Nedenini umursamıyorlar. % 85'in% 75'ten daha iyi olduğunu biliyorlar.
ACV

2
Başka türlü erişilemeyen kodu test etmek için pratik bir kullanım durumu,% 100 test kapsamına ulaşmaktır, böylece hiç kimse o sınıfa tekrar bakmaz. Kapsam% 95'te takılı kaldıysa, birçok geliştirici bunun nedenini bulmaya çalışabilir, sadece bu sorunla tekrar tekrar karşılaşabilir.
thisismydesign

140

Jon Skeet'e tamamen katılmıyorum. Bence, kapsama alanı sağlamak ve haber raporunuzdaki gürültüyü gidermek için kolay bir kazanç elde edebiliyorsanız, o zaman yapmalısınız. Ya kapsama aracınıza kurucuyu görmezden gelmesini söyleyin ya da idealizmi bir kenara bırakın ve aşağıdaki testi yazın ve onunla bitirin:

@Test
public void testConstructorIsPrivate() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  Constructor<Foo> constructor = Foo.class.getDeclaredConstructor();
  assertTrue(Modifier.isPrivate(constructor.getModifiers()));
  constructor.setAccessible(true);
  constructor.newInstance();
}

25
Ancak bu, test paketine gürültü ekleyerek kapsama raporundaki gürültüyü ortadan kaldırıyor . "İdealizmi bir kenara koyun" cümlesini yeni bitirirdim. :)
Christopher Orr

11
Bu teste herhangi bir anlam vermek için, muhtemelen kurucunun erişim düzeyinin olmasını beklediğiniz gibi olduğunu da iddia etmelisiniz.
Jeremy

Kötü yansıma artı Jeremy'nin fikirleri artı "testIfConstructorIsPrivateWithoutRaisingExceptions" gibi anlamlı bir isim ekleyerek, sanırım bu "THE" cevap.
Eduardo Costa

1
Bu sözdizimsel olarak yanlış değil mi? Nedir constructor? Olmamalı Constructorham türü parametreli ve edilmeyecektir?
Adam Parkin

2
Bu yanlış: constructor.isAccessible()genel bir kurucuda bile her zaman yanlış döndürür. Kullanmalı assertTrue(Modifier.isPrivate(constructor.getModifiers()));.
timomeinen

78

Kapsam için zorunlu olmasa da, yardımcı program sınıfının iyi tanımlandığını ve biraz da kapsama aldığını doğrulamak için bu yöntemi yarattım.

/**
 * Verifies that a utility class is well defined.
 * 
 * @param clazz
 *            utility class to verify.
 */
public static void assertUtilityClassWellDefined(final Class<?> clazz)
        throws NoSuchMethodException, InvocationTargetException,
        InstantiationException, IllegalAccessException {
    Assert.assertTrue("class must be final",
            Modifier.isFinal(clazz.getModifiers()));
    Assert.assertEquals("There must be only one constructor", 1,
            clazz.getDeclaredConstructors().length);
    final Constructor<?> constructor = clazz.getDeclaredConstructor();
    if (constructor.isAccessible() || 
                !Modifier.isPrivate(constructor.getModifiers())) {
        Assert.fail("constructor is not private");
    }
    constructor.setAccessible(true);
    constructor.newInstance();
    constructor.setAccessible(false);
    for (final Method method : clazz.getMethods()) {
        if (!Modifier.isStatic(method.getModifiers())
                && method.getDeclaringClass().equals(clazz)) {
            Assert.fail("there exists a non-static method:" + method);
        }
    }
}

Tam kodu ve örnekleri https://github.com/trajano/maven-jee6/tree/master/maven-jee6-test'e yerleştirdim


11
+1 Bu, yalnızca aracı kandırmadan sorunu çözmekle kalmaz, aynı zamanda bir yardımcı program sınıfı oluşturmanın kodlama standartlarını tam olarak test eder. Erişilebilirlik testini , bazı durumlarda özel oluşturucular için geri döndüğü Modifier.isPrivategibi kullanmak için değiştirmem gerekti (kitaplık müdahalesi alay ediyor mu?). isAccessibletrue
David Harkness

4
Bunu JUnit'in Assert sınıfına gerçekten eklemek istiyorum, ancak çalışmanız için kredi almak istemiyorum. Bence çok iyi Assert.utilityClassWellDefined()JUnit 4.12+ sürümüne sahip olmak harika olurdu . Bir çekme talebini düşündünüz mü?
Visionary Software Solutions

setAccessible()Yapıcıyı erişilebilir kılmak için kullanmanın Sonar'ın kod kapsama aracı için sorunlara neden olduğunu unutmayın (bunu yaptığımda sınıf Sonar'ın kod kapsamı raporlarından kaybolur).
Adam Parkin

Teşekkürler, yine de erişilebilir bayrağı sıfırladım. Belki de Sonar'ın kendisindeki bir hatadır?
Arşimet Trajano

Batik maven eklentimle ilgili kapsama için Sonar raporuma baktım, doğru şekilde kapsıyor gibi görünüyor. site.trajano.net/batik-maven-plugin/cobertura/index.html
Arşimet Trajano

19

CheckStyle'ı tatmin etmek için, statik fayda fonksiyonları sınıfımın yapıcısı özel yaptım. Ama orijinal afiş gibi, Cobertura da testten şikayet etti. İlk başta bu yaklaşımı denedim, ancak bu, kapsama raporunu etkilemiyor çünkü kurucu aslında hiçbir zaman çalıştırılmıyor. Yani gerçekten tüm bu testler, kurucunun gizli kalmasıdır - ve bu, sonraki testteki erişilebilirlik kontrolü tarafından gereksiz hale getirilir.

@Test(expected=IllegalAccessException.class)
public void testConstructorPrivate() throws Exception {
    MyUtilityClass.class.newInstance();
    fail("Utility class constructor should be private");
}

Javid Jamae'nin önerisiyle gittim ve yansımayı kullandım, ancak test edilen sınıfla uğraşan herkesi yakalamak için iddialar ekledim (ve testi Yüksek Seviyelerde Kötüyü belirtmek için adlandırdım).

@Test
public void evilConstructorInaccessibilityTest() throws Exception {
    Constructor[] ctors = MyUtilityClass.class.getDeclaredConstructors();
    assertEquals("Utility class should only have one constructor",
            1, ctors.length);
    Constructor ctor = ctors[0];
    assertFalse("Utility class constructor should be inaccessible", 
            ctor.isAccessible());
    ctor.setAccessible(true); // obviously we'd never do this in production
    assertEquals("You'd expect the construct to return the expected type",
            MyUtilityClass.class, ctor.newInstance().getClass());
}

Bu çok abartılı, ancak% 100 yöntem kapsamının sıcak bulanık hissini sevdiğimi itiraf etmeliyim.


Aşırı öldürme olabilir, ancak Unitils veya benzeri bir yerde olsaydı, kullanırdım
Stewart

+1 İyi bir başlangıç, ancak Arşimet'in daha eksiksiz testine gittim .
David Harkness

İlk örnek çalışmıyor - IllegalAccesException, kurucunun hiçbir zaman çağrılmadığı anlamına gelir, bu nedenle kapsam kaydedilmez.
Tom McIntyre

IMO, ilk kod parçacığındaki çözüm, bu tartışmadaki en temiz ve en basit çözümdür. Sadece çizgiye geçmek fail(...)gerekli değildir.
Piotr Wittchen

9

Java 8 ile başka bir çözüm bulmak mümkün.

Birkaç public static yöntemle fayda sınıfı oluşturmak istediğinizi varsayıyorum. Java 8 kullanabiliyorsanız, interfaceonun yerine kullanabilirsiniz .

package com.XXX;

public interface Foo {

  public static int bar() {
    return 1;
  }
}

Yapıcı yok ve Cobertura'dan şikayetçi yok. Şimdi yalnızca gerçekten önem verdiğiniz satırları test etmeniz gerekiyor.


1
Ancak ne yazık ki, arayüzü "nihai" olarak ilan edemezsiniz ve herhangi birinin onu alt sınıflara almasını engelleyemezsiniz - aksi takdirde en iyi yaklaşım bu olacaktır.
Michael Berry

5

Hiçbir şey yapmayan kodu test etmenin ardındaki mantık,% 100 kod kapsamına ulaşmak ve kod kapsamının ne zaman düştüğünü fark etmektir. Aksi takdirde kişi her zaman şöyle düşünebilirdi, hey artık% 100 kod kapsamım yok ama MUHTEMELEN özel kurucularımdan dolayı. Bu, sadece özel bir kurucu olup olmadığını kontrol etmek zorunda kalmadan test edilmemiş yöntemleri tespit etmeyi kolaylaştırır. Kod tabanınız büyüdükçe, aslında% 99 yerine% 100'e bakarak güzel bir sıcaklık hissedeceksiniz.

IMO burada yansımayı kullanmak en iyisidir, çünkü aksi takdirde ya bu oluşturucuları yok sayan daha iyi bir kod kapsama aracı edinmeniz ya da bir şekilde kod kapsama aracına yöntemi görmezden gelmesini söylemeniz gerekir (belki bir Ek Açıklama veya bir yapılandırma dosyası) çünkü o zaman takılıp kalırsınız belirli bir kod kapsama aracı ile.

Kusursuz bir dünyada tüm kod kapsama araçları, son bir sınıfa ait olan özel kurucuları yok sayar çünkü kurucu orada "güvenlik" olarak başka hiçbir şeyi ölçmez :)
Bu kodu kullanırdım:

    @Test
    public void callPrivateConstructorsForCodeCoverage() throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
    {
        Class<?>[] classesToConstruct = {Foo.class};
        for(Class<?> clazz : classesToConstruct)
        {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            assertNotNull(constructor.newInstance());
        }
    }
Ve sonra sadece diziye sınıfları ekleyin.


5

Cobertura'nın daha yeni sürümleri, önemsiz alıcıları / ayarlayıcıları / kurucuları yok saymak için yerleşik desteğe sahiptir:

https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference#ignore-trivial

Önemsizliği Yoksay

Önemsizliği yoksay, bir satır kod içeren yapıcıları / yöntemleri dışlama yeteneğine izin verir. Bazı örnekler yalnızca bir süper yorumcunun çağrısını, alıcı / ayarlayıcı yöntemlerini vb. İçerir. Önemsiz yok sayma bağımsız değişkenini dahil etmek için aşağıdakileri ekleyin:

<cobertura-instrument ignoreTrivial="true" />

veya bir Gradle yapısında:

cobertura {
    coverageIgnoreTrivial = true
}

4

Yapma. Boş bir kurucuyu test etmenin amacı nedir? Cobertura 2.0, bu tür önemsiz durumları (ayarlayıcılar / alıcılarla birlikte) göz ardı etme seçeneği olduğundan, cobertura maven eklentisine yapılandırma bölümü ekleyerek maven'de etkinleştirebilirsiniz:

<configuration>
  <instrumentation>
    <ignoreTrivial>true</ignoreTrivial>                 
  </instrumentation>
</configuration>

Alternatif kullanabilirsiniz Kapsama Ek Açıklamalar : @CoverageIgnore.


3

Sonunda bir çözüm var!

public enum Foo {;
  public static int bar() {
    return 1;
  }
}

Peki bu soruda yayınlanan sınıfı nasıl test ediyor? Özel kurucuya sahip her sınıfı bir enum'a dönüştürebileceğinizi veya istediğinizi varsaymamalısınız.
Jon Skeet

@JonSkeet Söz konusu sınıf için yapabilirim. Ve sadece bir sürü statik metoda sahip olan fayda sınıflarının çoğu. Aksi takdirde, tek özel kurucuya sahip bir sınıfın hiçbir anlamı yoktur.
kan

1
Özel yapıcısı ile A sınıfı olabilir elbette daha sonra bunu kapsama almak kolay olsa, kamu statik yöntemleri başlatılamaz. Ama temelde Enum<E>, gerçekten bir sıralama haline gelen herhangi bir sınıfı tercih ederim ... Bunun niyeti daha iyi ortaya çıkardığına inanıyorum.
Jon Skeet

4
Vay canına, oldukça rastgele bir sayı yerine mantıklı olan kodu kesinlikle tercih ederim. (Kapsama kalite garantisi değildir, ne de her durumda% 100 kapsama mümkün olduğunu Testleriniz olmalıdır. Kılavuzluk en iyi kodunuzu -. Tuhaf niyet bir uçurumun üzerine yönlendirmek değil)
Jon Skeet

1
@Kan: Aracı blöf yapmak için kurucuya sahte bir çağrı eklemek amaç olmamalı. Projenin refahını belirlemek için tek bir ölçüye güvenen herkes zaten yıkım yolundadır.
Apoorv Khurasia

1

Cobertura hakkında bilgim yok ama Clover kullanıyorum ve bu, desen eşleştirme istisnaları ekleme yoluna sahip. Örneğin, apache-commons-logging satırlarını kapsam dışında bırakan desenlerim var.


1

Diğer bir seçenek, aşağıdaki koda benzer bir statik başlatıcı oluşturmaktır.

class YourClass {
  private YourClass() {
  }
  static {
     new YourClass();
  }

  // real ops
}

Bu şekilde özel kurucu test edilmiş sayılır ve çalışma zamanı ek yükü temelde ölçülemez. Bunu EclEmma kullanarak% 100 kapsama elde etmek için yapıyorum, ancak muhtemelen her kapsama aracı için işe yarıyor. Bu çözümün dezavantajı, elbette, üretim kodunu (statik başlatıcı) yalnızca test amacıyla yazmanızdır.


Bunu biraz yapıyorum. Ucuz olduğu kadar ucuz, kirli olduğu kadar ucuz ama etkili.
pholser

Sonar ile bu, sınıfın tamamen kod kapsamı tarafından gözden kaçırılmasına neden olur.
Adam Parkin

1

ClassUnderTest testClass = Whitebox.invokeConstructor (ClassUnderTest.class);


Sorulanı tam olarak cevapladığı için bu doğru cevap olmalıydı.
Chakian

0

Bazen Cobertura, yürütülmesi amaçlanmayan kodu 'kapsam dışı' olarak işaretler, bunda yanlış bir şey yoktur. Neden 99%bunun yerine teminat almakla ilgileniyorsunuz 100%?

Teknik olarak, yine de bu kurucuyu yansıma ile çağırabilirsiniz, ancak bu bana çok yanlış geliyor (bu durumda).


0

Sorunuzun amacını tahmin etseydim derdim:

  1. Gerçek iş yapan özel müteahhitler için makul kontroller istiyorsunuz ve
  2. Clover'ın kullanım sınıfları için boş kurucuları dışlamasını istiyorsunuz.

1 için, tüm başlatmanın fabrika yöntemleriyle yapılmasını istediğiniz açıktır. Bu gibi durumlarda, testleriniz kurucunun yan etkilerini test edebilmelidir. Bu, normal özel yöntem testi kategorisine girmelidir. Yöntemleri küçültün, böylece yalnızca sınırlı sayıda belirleyici şey (ideal olarak, yalnızca bir şey ve bir şey iyi) yapın ve sonra onlara dayanan yöntemleri test edin.

Örneğin, [private] kurucum sınıfımın örnek alanlarını aiçin ayarlarsa 5. O zaman test edebilirim (veya daha doğrusu test etmeliyim):

@Test
public void testInit() {
    MyClass myObj = MyClass.newInstance(); //Or whatever factory method you put
    Assert.assertEquals(5, myObj.getA()); //Or if getA() is private then test some other property/method that relies on a being 5
}

2 için, Util sınıfları için belirlenmiş bir adlandırma modeliniz varsa, clover'ı Util oluşturucularını hariç tutacak şekilde yapılandırabilirsiniz. Örneğin, kendi projemde buna benzer bir şey kullanıyorum (çünkü tüm Util sınıflarının isimlerinin Util ile bitmesi gerektiği kuralına uyuyoruz):

<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
</clover-setup>

Ben kasıtlı olarak dışarı bırakmış .*aşağıdaki) çünkü bu tür kurucular istisnalar atmak anlamına gelmez (hiçbir şey yapmaları amaçlanmamıştır).

Elbette, fayda sınıfı olmayan bir sınıf için boş bir kurucuya sahip olmak isteyebileceğiniz üçüncü bir durum olabilir. Bu gibi durumlarda, methodContextkurucunun tam imzasıyla bir koymanızı tavsiye ederim .

<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
    <methodContext name="myExceptionalClassCtor" regexp="^private MyExceptionalClass()$"/>
</clover-setup>

Bu kadar çok istisnai sınıfınız varsa, önerdiğim genelleştirilmiş özel kurucu reg-ex'i değiştirmeyi ve kaldırmayı seçebilirsiniz. Util ondan . Bu durumda, kurucunuzun yan etkilerinin hala test edildiğinden ve sınıfınızdaki / projenizdeki diğer yöntemlerle kaplandığından emin olmanız gerekecektir.

<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+ *( *) .*"/>
</clover-setup>

0
@Test
public void testTestPrivateConstructor() {
    Constructor<Test> cnt;
    try {
        cnt = Test.class.getDeclaredConstructor();
        cnt.setAccessible(true);

        cnt.newInstance();
    } catch (Exception e) {
        e.getMessage();
    }
}

Test.java, özel oluşturucunuzu içeren kaynak dosyanızdır


Bu yapının kapsama neden yardımcı olduğunu açıklamak güzel olurdu.
Markus

Doğru ve ikincisi: testinizde neden bir istisna yakalayasınız? Atılan istisna, aslında testin başarısız olmasına neden olmalıdır.
Jordi

0

Aşağıdakiler, otomatik olarak özel bir kurucu ekleyen Lombok ek açıklaması @UtilityClass ile oluşturulan bir sınıfta çalıştı.

@Test
public void testConstructorIsPrivate() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
    Constructor<YOUR_CLASS_NAME> constructor = YOUR_CLASS_NAME.class.getDeclaredConstructor();
    assertTrue(Modifier.isPrivate(constructor.getModifiers())); //this tests that the constructor is private
    constructor.setAccessible(true);
    assertThrows(InvocationTargetException.class, () -> {
        constructor.newInstance();
    }); //this add the full coverage on private constructor
}

Constructor.setAccessible (true), özel kurucu manuel olarak yazıldığında çalışmasına rağmen, Lombok ek açıklaması onu zorladığından çalışmaz. Constructor.newInstance () aslında yapıcının çağrıldığını test eder ve bu, oluşturucunun kendi kapsamını tamamlar. AssertThrows ile testin başarısız olmasını engellersiniz ve istisnayı yönetirsiniz çünkü tam olarak beklediğiniz hata budur. Bu bir geçici çözüm olmasına ve "kapsama alanı" ile "işlevsellik / davranış kapsamı" kavramını takdir etmeme rağmen, bu testte bir anlam bulabiliriz. Aslında, Fayda Sınıfının aslında reflaction yoluyla da çağrıldığında bir istisnayı doğru şekilde atan özel bir Kurucuya sahip olduğundan eminsiniz. Bu yardımcı olur umarım.


Merhaba @ShanteshwarInde. Çok teşekkürler. Girişim, önerilerinize göre düzenlendi ve tamamlandı. Saygılarımızla.
Riccardo Solimena

0

2019'da tercih ettiğim seçenek: lombok kullan.

Özellikle, @UtilityClassek açıklama . (Yazma sırasında ne yazık ki yalnızca "deneysel", ancak gayet iyi çalışıyor ve olumlu bir görünümü var, bu nedenle yakında kararlı hale getirilmesi muhtemel.)

Bu açıklama, başlatmayı önlemek için özel kurucuyu ekleyecek ve sınıfı son hale getirecektir. lombok.addLombokGeneratedAnnotation = trueIn ile birleştirildiğinde lombok.config, hemen hemen tüm test çerçeveleri, test kapsamını hesaplarken otomatik olarak oluşturulan kodu göz ardı ederek, otomatik olarak oluşturulan kodun kapsamını hiçbir hack veya yansıma olmadan atlamanıza olanak tanır.


-2

Yapamazsın.

Görünüşe göre, yalnızca statik yöntemler içermesi amaçlanan bir sınıfın somutlaştırılmasını önlemek için özel oluşturucuyu oluşturuyorsunuz. Bu kurucunun kapsamını almaya çalışmak yerine (bu, sınıfın somutlaştırılmasını gerektirir), ondan kurtulmalı ve geliştiricilerinizin sınıfa örnek yöntemleri eklemeyeceğine güvenmelisiniz.


3
Bu yanlış; bunu yukarıda belirtildiği gibi yansıma yoluyla somutlaştırabilirsiniz.
theotherian

Bu kötü, varsayılan genel kurucunun görünmesine asla izin vermeyin, aramayı önlemek için özel olanı eklemelisiniz.
Lho Ben
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.