Miras alınan yöntemleri test etmeli miyim?


30

Çalışan bir temel sınıf Çalışan türetilmiş bir sınıf Yöneticim olduğunu ve bu Çalışanın Yönetici tarafından miras alınan bir getEmail () yöntemine sahip olduğunu varsayalım . Bir yöneticinin getEmail () yönteminin davranışının bir çalışanın davranışlarıyla aynı olduğunu test etmeli miyim ?

Bu testler yazıldığı zaman davranış aynı olacaktır, fakat elbette gelecekte bir noktada birileri bu yöntemi geçersiz kılabilir, davranışını değiştirebilir ve bu nedenle başvurumu bozabilir. Ancak karışma kodunun yokluğunu esasen test etmek biraz garip görünüyor .

(Test Manager :: getEmail () yönteminin, Manager :: getEmail () oluşturulup geçersiz kılınana kadar kod kapsamını iyileştirmediğini (veya diğer kod kalite ölçütlerini (?)) İyileştirmediğini unutmayın .)

(Eğer cevap "Evet" ise, baz ve türetilmiş sınıflar arasında paylaşılan testleri yönetme konusunda bazı bilgiler faydalı olacaktır.)

Sorunun eşdeğer bir formülasyonu:

Türetilmiş bir sınıf, bir temel sınıftan bir yöntemi miras alırsa, miras alınan yöntemi şu şekilde beklemenizi isteyip istemediğinizi nasıl ifade edersiniz?

  1. Tabanın şu anda olduğu gibi davranın (bazın davranışı değişirse, türetilmiş yöntemin davranışı değişmez);
  2. Her zaman için tam olarak aynı davranışta bulunun (eğer temel sınıfın davranışı değişirse, türetilmiş sınıfın davranışı da değişir); veya
  3. Ancak istediği gibi davran (bu yöntemin davranışını umursamıyorsun çünkü asla onu çağırmıyorsun).

1
Bir Managersınıftan IMO türetmek Employeeilk büyük hataydı.
CodesInChaos

4
@CodesInChaos Evet, bu kötü bir örnek olabilir. Ancak aynı problem mirasınız olduğunda da geçerlidir.
mjs

1
Yöntemi geçersiz kılmanıza bile gerek yok. Temel sınıf yöntemi, geçersiz kılınan diğer örnek yöntemlerini çağırabilir ve yöntem için aynı kaynak kodun çalıştırılması yine de farklı davranışlar oluşturur.
gnasher729

Yanıtlar:


23

Pragmatik yaklaşımı burada ele alırdım: Gelecekte birisi, Manager :: getMail'i geçersiz kılarsa, yeni yöntem için test kodu sağlamak bu geliştiricinin sorumluluğundadır.

Elbette bu sadece, Manager::getEmailgerçekten aynı kod yoluna sahipse geçerlidir Employee::getEmail! Yöntem geçersiz kılmamış olsa bile, farklı davranabilir:

  • Employee::getEmailBazı korumalı sanal diyebiliriz getInternalEmailki edilmektedir geçersiz Manager.
  • Employee::getEmail_emailiki uygulamada farklı olabilecek bazı dahili duruma (örneğin, bazı alanlara ) erişebilir: Örneğin, varsayılan uygulama Employeebunun _emailher zaman olmasını sağlar firstname.lastname@example.com, ancak Managerposta adreslerini atama konusunda daha esnektir.

Bu gibi durumlarda, yönteminManager::getEmail uygulaması aynı olsa bile , bir hatanın sadece kendini göstermesi mümkündür . Bu durumda ayrı ayrı test yapmak mantıklı olabilir.Manager::getEmail


14

İsterim.

Eğer "iyi, gerçekten sadece Employee :: getEmail () 'i çağırıyor, çünkü onu geçersiz kılmadım, bu yüzden Manager :: getEmail ()' ü test etmeme gerek yok, o zaman gerçekten davranışını test etmiyorsun Yöneticisi :: getEmail ().

Sadece Manager :: getEmail () 'in ne yapması gerektiğini, miras almamış veya geçersiz kılmamış olsun olmasın düşünürdüm. Manager :: getEmail () davranışı, Çalışan: getMail () ne döndürürse döndürsün, o zaman test budur. Davranış "pink@unicorns.com" döndürmek ise, o zaman test budur. Miras yoluyla mı yoksa geçersiz kılındığından mı fark edilir.

Önemli olan, eğer gelecekte değişecek olursa, testiniz onu yakalar ve bir şeyin ya kırıldığını ya da yeniden gözden geçirilmesi gerektiğini biliyorsunuzdur.

Bazı insanlar kontrolün geri kalanı ile aynı fikirde olmayabilir, ancak bunun karşılığını benimsemiş olsanız da olmasanız da, çalışan :: getMail () ve Manager :: getMail () davranışlarını test ediyor olmalısınız. geçersiz kılınan. Gelecekteki bir geliştiricinin Manager :: getMail () davranışını değiştirmesi gerekiyorsa, testleri de güncellemeleri gerekir.

Yine de görüşler değişebilir, bence Digger ve Heinzi bunun aksini için makul gerekçeler verdi.


1
Davranışsal testlerin sınıfın oluşma biçimine dik olduğu argümanını seviyorum. Kalıtımdan tamamen vazgeçmemek biraz komik görünüyor. (Ve eğer çok fazla kalıtımınız varsa, paylaşılan testleri nasıl
yönetirsiniz

1
Peki. Yöneticinin kalıtsal bir yöntem kullanması nedeniyle, hiçbir şekilde test edilmemesi gerektiği anlamına gelir. Bir keresinde büyük bir kurşun, "Test edilmediyse, kırılmış" dedi. Bu amaçla, kod girişinde gerekli olan Çalışan ve Yönetici için testler yaparsanız, devralınan yöntemin davranışını değiştirebilecek Yönetici için yeni kodu kontrol eden geliştiricinin testi yeni davranışı yansıtacağından emin olacaksınız. . Ya da kapını çalmaya gel.
kasırgaMitch

2
Gerçekten Manager sınıfını test etmek istiyorsun. Çalışanın bir alt sınıfı olması ve getMail () uygulamasının mirasa dayalı olarak uygulanması, birim testleri oluştururken göz ardı etmeniz gereken bir uygulama detayıdır. Gelecek ay, Employee Manager'dan Miras almanın kötü bir fikir olduğunu ve tüm miras yapısını değiştirip tüm yöntemleri yeniden uyguladığınızı anlıyorsunuz. Birim testleriniz kodunuzu sorunsuz test etmeye devam etmelidir.
gnasher729

5

Test etmeyin. İşlevsel / kabul testi yapın.

Ünite testleri her uygulamayı test etmelidir, eğer yeni bir uygulama sağlamıyorsanız DRY prensibine uyun. Burada biraz çaba harcamak istiyorsanız, orijinal birim testini geliştirebilirsiniz. Sadece yöntemi geçersiz kılarsanız, bir birim testi yazmalısınız.

Aynı zamanda, işlevsel / kabul testleri, günün sonunda tüm kodunuzun olması gerekeni yaptığını ve kalıtımdan gelen herhangi bir tuhaflığı yakalayacağından emin olmalıdır.


4

Robert Martin'in TDD kuralları :

  1. Başarısız ünite testini geçmediği sürece herhangi bir üretim kodu yazmanıza izin verilmez.
  2. Başarısız olmak için yeterli bir ünite testi yazmanıza izin verilmez; ve derleme hataları, başarısızlıklardır.
  3. Bir başarısız ünite testini geçmek için yeterli olandan daha fazla üretim kodu yazmanıza izin verilmez.

Bu kuralları izlerseniz, bu soruyu soracak bir pozisyona giremezsiniz. Eğer getEmailyöntem ihtiyaçları test edilecek, bu test edilmiş olurdu.


3

Gelecekte onlar Evet, çünkü miras yöntemleri test etmelidir olabilir geçersiz kılınan olsun. Ayrıca, miras alınan yöntemler , geçersiz kılınan mirassız yöntemin davranışını değiştirecek olan geçersiz kılınan sanal yöntemler olarak da adlandırılabilir .

Bunu test etmenin yolu, (muhtemelen soyut) taban sınıfını veya arayüzünü test etmek için soyut bir sınıf oluşturmaktır (NUnit kullanarak C # dilinde):

public abstract class EmployeeTests
{
    protected abstract Employee CreateInstance(string name, int age);

    [Test]
    public void GetEmail_ReturnsValidEmailAddress()
    {
        // Given
        var sut = CreateInstance("John Doe", 20);

        // When
        string email = sut.GetEmail();

        // Then
        Assert.IsTrue(Helper.IsValidEmail(email));
    }
}

O zaman, kendine özgü testler içeren bir sınıfım var Managerve çalışanın testlerini şu şekilde entegre ettim :

[TestFixture]
public class ManagerTests
{
    // Other tests.

    [TestFixture]
    public class ManagerEmployeeTests : EmployeeTests
    {
        protected override Employee CreateInstance(string name, int age);
        {
            return new Manager(name, age);
        }
    }
}

Bunu yapabilmemin sebebi Liskov'un ikame ilkesidir: Testleri, Employeebir Managernesneyi geçtiği zaman , türetildiği için geçmelidir Employee. Bu yüzden testlerimi yalnızca bir kez yazmam gerekiyor ve bunların arayüz veya temel sınıfın tüm olası uygulamaları için çalıştığını doğrulayabiliyorum.


İyi ki Liskov'un ikame prensibi, eğer geçerli olursa, türetilmiş sınıfın tüm temel sınıfın sınavlarını geçeceği konusunda haklısın. Ancak, LSP, xUnit'in setUp()yönteminin kendisi de dahil olmak üzere, sıklıkla ihlal edilmektedir ! Ve hemen hemen bir "index" yöntemini geçersiz kılmayı içeren her web MVC çerçevesi de temelde hepsi olan LSP'yi bozar.
mjs

Bu kesinlikle doğru cevap - "Soyut Test Deseni" dediğini duydum. Meraktan, neden sadece testlere katılmak yerine iç içe bir sınıf kullanıyorsunuz class ManagerTests : ExployeeTests? (örn. test edilen sınıfların kalıtımını yansıtmak.) Sonuçları görmeye yardımcı olmak temel olarak estetik midir?
Luke Usherwood

2

Bir yöneticinin getEmail () yönteminin davranışının bir çalışanın davranışlarıyla aynı olduğunu test etmeli miyim?

Çalışan testlerinde bir kez test edeceğime göre, tekrarlanan bir test olacağı için hayır diyebilirim.

Bu testler yazıldığı zaman davranış aynı olacaktır, ancak gelecekte bir noktada birileri bu yöntemi geçersiz kılabilir, davranışını değiştirebilir

Yöntem geçersiz kılınırsa, geçersiz kılma davranışını kontrol etmek için yeni testlere ihtiyacınız olacaktır. Bu, geçersiz kılma getEmail()yöntemini uygulayan kişinin işidir .


1

Hayır, kalıtsal yöntemleri test etmeniz gerekmez. Davranış değiştiyse, bu yönteme dayanan sınıflar ve test durumları yine de kırılacakManager .

Aşağıdaki senaryoyu düşünün: E-posta adresi Firstname.Lastname@example.com olarak toplandı:

class Employee{
    String firstname, lastname;
    String getEmail() { 
        return firstname + "." + lastname + "@example.com";
    }
}

Bunu test etmiş birimin var ve senin için iyi çalışıyor Employee. Ayrıca bir sınıf yarattınız Manager:

class Manager extends Employee { /* nothing different in email generation */ }

Artık ManagerSorte-posta adreslerini temel alarak bir listedeki yöneticileri sıralayan bir sınıfa sahipsiniz . Sizin e-postanızın, şu şekilde olduğu gibi olduğunu varsayalım Employee:

class ManagerSort {
    void sortManagers(Manager[] managerArrayToBeSorted)
        // sort based on email address omitted
    }
}

Sizin için bir test yazıyorsunuz ManagerSort:

void testManagerSort() {
    Manager[] managers = ... // build random manager list
    ManagerSort.sortManagers(managers);

    Manager[] expected = ... // expected result
    assertEquals(expected, managers); // check the result
}

Her şey iyi çalışıyor. Şimdi birisi gelir ve getEmail()yöntemi geçersiz kılar :

class Manager extends Employee {
    String getEmail(){
        // managers should have their lastname and firstname order changed
        return lastname + "." + firstname + "@example.com";
    }
}

Şimdi ne olacak? Sizin testManagerSort()çünkü başarısız olur getEmail()ve Managergeçersiz oldu. Bu konuda araştırma yapacak ve sebebini bulacaksınız. Ve hepsi miras alınan yöntem için ayrı bir test çantası yazmadan.

Bu nedenle, kalıtsal yöntemleri test etmeniz gerekmez.

Aksi takdirde, örneğin Java'da, her sınıftaki Objectbenzerlerinden toString(), equals()vb. Devralınan tüm yöntemleri test etmeniz gerekir .


Birim testlerinde zeki olmamanız gerekiyor. "X'i sınamaya ihtiyacım yok çünkü X başarısız olduğunda, Y başarısız" diyorsunuz. Ancak birim testleri, kodunuzdaki hataların varsayımlarına dayanmaktadır. Kodunuzdaki hatalarla, testler arasındaki karmaşık ilişkilerin çalışmalarını beklediğiniz gibi çalıştığını neden düşünüyorsunuz?
gnasher729

@ gnasher729 "Alt sınıftaki X testine ihtiyacım yok çünkü zaten üst sınıf için birim testlerinde test edildi " dedim . Elbette, eğer X'in uygulamasını değiştirirseniz, bunun için uygun testleri yazmanız gerekir.
Uooo
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.