Birim testinde beklenen sonuçların kodlanması gerekir mi?


29

Bir birim testin beklenen sonuçları kodlanmış mı yoksa başlangıç ​​durumuna getirilmiş değişkenlere bağlı mı olabilir? Kodlanmış veya hesaplanmış sonuçlar birim testine hata verme riskini arttırır mı? Göz önünde bulundurmadığım başka faktörler var mı?

Örneğin, bu ikisinden hangisi daha güvenilir bir formattır?

[TestMethod]
public void GetPath_Hardcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

[TestMethod]
public void GetPath_Softcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

DÜZENLEME 1: DXM'in cevabına cevaben, seçenek 3 tercih edilen bir çözüm mü?

[TestMethod]
public void GetPath_Option3()
{
    string field1 = "fields";
    string field2 = "that later";
    string field3 = "determine";
    string field4 = "a folder";
    MyClass target = new MyClass(field1, field2, field3, field4);
    string expected = "C:\\Output Folder\\" + string.Join("\\", field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

2
İkisinide yap. Ciddi anlamda. Testler üst üste gelebilir ve geçmelidir. Ayrıca, kendinizi kodlanmış değerlerle uğraşırken bulursanız, bazı veri tabanlı testlere bakın.
İş

Üçüncü seçeneğin kullanmak istediğim şey olduğuna katılıyorum. Derleme işlemlerini elimine ettiğiniz için 1. seçeneğin zarar vereceğini sanmıyorum.
kwelch

Her iki seçeneğiniz de yine de
kodlamayı kullanır

Yanıtlar:


27

Beklenen değerin hesaplanmasının daha sağlam ve esnek test durumlarında sonuçlandığını düşünüyorum. Ayrıca, beklenen sonucu hesaplayan ifadede iyi değişken adları kullanarak, beklenen sonucun ilk sırada nereden geldiği çok daha açıktır.

Bunu söyledikten sonra, sizin özel örneğinizde "Softcoded" metoduna güvenmeyeceğim, çünkü SUT'unuzu (test edilen sistem) hesaplamalarınız için girdi olarak kullanıyor. MyClass'ta alanların doğru şekilde depolanmadığı bir hata varsa, testiniz gerçekten geçecektir, çünkü beklenen değer hesaplamanız tıpkı target.GetPath () gibi yanlış bir dize kullanacaktır.

Benim önerim, mantıklı olduğu yerde beklenen değeri hesaplamak olacaktır, ancak hesaplamanın SUT'un kendisinden herhangi bir koda bağlı olmadığından emin olun.

OP’nin cevabımdaki güncellemesine cevap olarak:

Evet, bilgilerime dayanarak, ancak TDD yaparken biraz sınırlı deneyime sahip olursam, 3. seçeneği tercih ederim.


1
İyi bir nokta! Testteki doğrulanmamış nesneye güvenmeyin.
El-E-Yiyecek

SUT kodunun tekrarı değil mi?
Abyx

1
bir şekilde, ancak SUT'un çalıştığını doğrulayın. Aynı kodu kullanırsak ve bozulursa asla bilemezsiniz. Tabii ki, hesaplamayı yapmak için, çok fazla SUT kopyalamanız gerekiyorsa, o zaman belki # 1 seçeneği daha iyi olur, sadece değeri sabitler.
DXM

16

Ya kod aşağıdaki gibi olsaydı:

MyTarget() // constructor
{
   Field1 = Field2 = Field3 = Field4 = "";
}

İkinci örneğiniz hatayı yakalayamaz, ancak ilk örnek olur.

Genelde, yumuşak kodlamaya karşı öneriyorum, çünkü hataları gizleyebilir. Örneğin:

string expected = "C:\\Output Folder" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);

Sorunu tespit edebilir misin? Aynı hatayı kodlanmış bir sürümde yapmazsınız. Hesaplamaları sabit kodlanmış değerlerden daha doğru yapmak zordur. Bu yüzden yumuşak kodlu olanlardan çok kodlanmış değerlerle çalışmayı tercih ediyorum.

Ancak istisnalar da var. Ya kodunuzun Windows ve Linux'ta çalışması gerekiyorsa? Sadece yol farklı olmak zorunda kalmayacak, farklı yol ayırıcılar kullanması gerekecek! Aradaki farkı soyutlayan işlevleri kullanarak yolu hesaplamak bu bağlamda anlamlı olabilir.


Ne dediğini duyuyorum ve bu bana düşünecek bir şey veriyor. Yazılım kodlaması, diğer test durumlarıma (ConstructorShouldCorrectlyInitialiseFields gibi) geçmesine güveniyor. Tanımladığınız hata, başarısız olan diğer birim testleriyle çapraz referans verilebilir.
El-E-Yiyecek

@ El-E-Gıda, nesnelerinizin bireysel yöntemleri üzerinde testler yazıyormuşsunuz gibi geliyor. Yapma. Tek tek yöntemlerin değil, tüm nesnenizin doğruluğunu kontrol eden testler yazmalısınız. Aksi takdirde testleriniz nesnenin içindeki değişikliklere göre kırılgan olacaktır.
Winston Ewert,

Takip ettiğimden emin değilim. Verdiğim örnek tamamen varsayımsaldı, senaryoyu anlamak kolaydı. Sınıfların ve nesnelerin genel üyelerini test etmek için birim testleri yazıyorum. Onları kullanmanın doğru yolu bu mu?
El-E-Yiyecek

@ El-E-Gıda, sizi doğru anlarsam, ConstructShouldCorrectlyInitialiseFields testiniz yapıcıyı çağırır ve ardından alanların doğru ayarlandığını iddia eder. Ama bunu yapmamalısın. İç alanların ne yaptığını umursamaman gerekir. Yalnızca nesnenin dış davranışının doğru olduğunu iddia etmelisiniz. Aksi halde, iç uygulamayı değiştirmeniz gerektiğinde gün gelebilir. İç durum hakkında iddialarda bulunduysanız, tüm testleriniz bozulur. Ancak, yalnızca dış davranış hakkında iddialarda bulunduysanız, her şey çalışmaya devam eder.
Winston Ewert,

@ Winston - Aslında, xUnit Test Patterns kitabıyla ve daha önce Birim Testini Tamamlama aşamasını sürme sürecindeyim. Neden bahsettiğimi biliyormuş gibi davranmayacağım, ama o kitaplardan bir şey aldığımı düşünmek istiyorum. Her iki kitap da, her test yönteminin mutlak minimum testi yapmasını ve tüm nesneyi test etmek için birçok test vakasına sahip olmanızı şiddetle tavsiye ediyor. Bu şekilde arayüzler veya işlevler değiştiğinde, çoğu değil, yalnızca birkaç test yöntemini çözmeyi beklemelisiniz. Küçük oldukları için değişiklikler daha kolay olmalı.
DXM

4

Bence her iki önerin de idealin altında. Bunu yapmanın ideal yolu şudur:

[TestMethod]
public void GetPath_Hardcoded()
{
    const string f1 = "fields"; const string f2 = "that later"; 
    const string f3 = "determine"; const string f4 = "a folder";

    MyClass target = new MyClass( f1, f2, f3, f4 );
    string expected = "C:\\Output Folder\\" + string.Join("\\", f1, f2, f3, f4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

Başka bir deyişle, test yalnızca nesnenin giriş ve çıkışına dayanarak çalışmalı ve nesnenin iç durumuna dayanmamalıdır. Nesne kara kutu olarak değerlendirilmelidir. (Ben sadece bir örnek olduğu için string.Join yerine string.Join kullanmanın uygunsuzluğu gibi diğer sorunları göz ardı ediyorum.)


1
Tüm yöntemler işlevsel değildir - çoğu, bazı nesnelerin veya nesnelerin durumunu değiştiren yan etkilere sahiptir. Yan etkileri olan bir yöntem için bir birim testinin muhtemelen yöntemden etkilenen nesnelerin durumunu değerlendirmesi gerekir.
Matthew Flynn,

O zaman bu durum yöntemin çıktısı olarak kabul edilir. Bu örnek testin amacı, MyClass kurucusunu değil GetPath () yöntemini kontrol etmektir. @ DXM'in cevabını oku, kara kutu yaklaşımını benimsemek için çok iyi bir sebep sunuyor.
Mike Nakis,

@ MatthewFlynn, o zaman bu durumdan etkilenen yöntemleri test etmelisin. Kesin iç durum bir uygulama detayıdır ve testin hiçbiri iş değildir.
Winston Ewert,

@MatthewFlynn, açıklığa kavuşturmak için, gösterilen örnekle veya başka birim testlerinde göz önünde bulundurulması gereken başka bir şeyle mi ilgili? Bunun gibi target.Dispose(); Assert.IsTrue(target.IsDisposed);bir şeyin önemini görebiliyordum (çok basit bir örnek.)
El-E-Yemek

Bu durumda bile, IsDisposed özelliği sınıfın genel arayüzünün vazgeçilmez bir parçasıdır (veya bir uygulama detayıdır). (IDispose arayüzü böyle bir özellik sağlamaz, ancak bu talihsizdir.)
Mike Nakis

2

Tartışmanın iki yönü var:

1. Test senaryosu için hedefin kendisinin kullanılması
İlk soru, test saplamasında güvenilen ve yapılan çalışmanın bir parçası olarak sınıfın kendisini kullanabilir mi? - Cevap HAYIR’dır çünkü genel olarak test ettiğiniz kod hakkında hiçbir zaman varsayımda bulunmamanız gerekir. Bu doğru şekilde yapılmazsa, zaman içinde hatalar bazı ünite testlerine karşı bağışıklık kazanır.

2. Kodlama zor kod
olmalı ? Yine cevap hayır . Çünkü herhangi bir yazılımda olduğu gibi - o bilginin zor kodlanması işler geliştiğinde zorlaşır. Örneğin, yukarıdaki yolun tekrar değiştirilmesini istediğinizde, ek birim yazmanız veya düzenlemeye devam etmeniz gerekir. Daha iyi bir yöntem, kolaylıkla uyarlanabilen ayrı konfigürasyondan türetilmiş girdi ve değerlendirme tarihini tutmaktır.

Mesela burada test saplamasını nasıl doğrulardım.

[TestMethod]
public void GetPath_Tested(int CaseId)
{
    testParams = GetTestConfig(caseID,"testConfig.txt"); // some wrapper that does read line and chops the field. 
    MyClass target = new MyClass(testParams.field1, testParams.field2);
    string expected = testParams.field5;
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

0

Çok fazla kavram var, aradaki farkı görmek için bazı örnekler

[TestMethod]
public void GetPath_Softcoded()
{
    //Hardcoded since you want to see what you expect is most simple and clear
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";

    //If this test should also use a mocked filesystem it might be that you want to use
    //some base directory, which you could set in the setUp of your test class
    //that is usefull if you you need to run the same test on different environments
    string expected = this.outputPath + "fields\\that later\\determine\\a folder";


    //another readable way could be interesting if you have difficult variables needed to test
    string fields = "fields";
    string thatLater = "that later";
    string determine = "determine";
    string aFolder = "a folder";
    string expected = this.outputPath + fields + "\\" + thatLater + "\\" + determine + "\\" + aFolder;
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    //in general testing with real words is not needed, so code could be shorter on that
    //for testing difficult folder names you write a separate test anyway
    string f1 = "f1";
    string f2 = "f2";
    string f3 = "f3";
    string f4 = "f4";
    string expected = this.outputPath + f1 + "\\" + f2 + "\\" + f3 + "\\" + f4;
    MyClass target = new MyClass(f1, f2, f3, f4);

    //so here we start to see a structure, it looks more like an array of fields
    //so what would make testing more interesting with lots of variables is the use of a data provider
    //the data provider will re-use your test with many different kinds of inputs. That will reduce the amount of duplication of code for testing
    //http://msdn.microsoft.com/en-us/library/ms182527.aspx


    The part where you compare already seems correct
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

Özetlemek gerekirse: Genel olarak ilk sadece kodlanmış testiniz benim için en mantıklı olanıdır, çünkü basit, doğrudan konuya vb.

Gelecekte daha fazla yapılandırılmış test için, veri kaynaklarına bakmaya gideceğim, böylece daha fazla test durumuna ihtiyacınız varsa, sadece daha fazla veri satırı ekleyebilirsiniz.


0

Modern test çerçeveleri, yönteminize parametreler sağlamanıza izin verir. Bunları kaldıracağım:

[TestCase("fields", "that later", "determine", "a folder", @"C:\Output Folder\fields\that later\determine\a folder")]
public void GetPathShouldReturnFullDirectoryPathBasedOnItsFields(
    string field1, string field2, string field3, string field,
    string expected)
{
    MyClass target = new MyClass(field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

Benim görüşüme göre, bunun birkaç avantajı var:

  1. Geliştiriciler genellikle görünüşte basit olan kod bölümlerini SUT'lerinden birim testlerine kopyalamaya teşvik edilirler. Winston'un belirttiği gibi , bunlarda hala gizli böcekler bulunabilir. Beklenen sonuç "zor kodlama", orijinal kodunuzun yanlış olması nedeniyle test kodunuzun yanlış olduğu durumlardan kaçınmaya yardımcı olur. Ancak gereksinimlerdeki bir değişiklik sizi düzinelerce test yönteminin içine gömülmüş sabit kodlu dizileri izlemeye zorlarsa, bu can sıkıcı olabilir. Tüm kodlanmış değerlerin tek bir yerde, test mantığınızın dışında olması size her iki dünyanın da en iyisini sunar.
  2. Tek bir kod satırı ile farklı girişler ve beklenen çıkışlar için testler ekleyebilirsiniz. Bu, daha fazla test yazmanızı sağlar, test kodunuzu DRY tutarken ve bakımı kolaydır. Testler eklemek çok ucuz olduğu için, zihnim, onlar için tamamen yeni bir yöntem yazmak zorunda kalsaydım, hiç düşünmeyeceğim yeni test vakalarına açıktı. Örneğin, girdilerden birinin içinde nokta olması durumunda hangi davranışı beklerdim? Bir ters eğik çizgi? Ya biri boşsa? Veya boşluk? Ya da boşlukla mı başladı veya bitti?
  3. Test çerçevesi, her bir TestCase'i kendi testi olarak görecek, hatta sağlanan giriş ve çıkışları test adına koyacak. Tüm TestCases bir taneden geçerse, hangisinin kırıldığını ve diğerlerinden nasıl farklı olduğunu görmek çok kolaydır.
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.