.NET Birim Testi: Daha Sonra Tasarruf Etmek İçin Peşin Harcama

Yayınlanan: 2022-03-11

Paydaşlar ve müşterilerle tartışırken genellikle birim testiyle ilgili çok fazla kafa karışıklığı ve şüphe vardır. Birim testi bazen bir çocuğa diş ipi kullanmanın "Dişlerimi zaten fırçalıyorum, bunu neden yapmam gerekiyor?" gibi geliyor.

Birim testi önermek, test yöntemlerini ve kullanıcı kabul testini yeterince güçlü bulan kişiler için genellikle gereksiz bir masraf gibi görünür.

Ancak Birim Testleri çok güçlü bir araçtır ve düşündüğünüzden daha basittir. Bu yazıda, birim testine ve DotNet'te Microsoft.VisualStudio.TestTools ve Moq gibi hangi araçların mevcut olduğuna bir göz atacağız.

Fibonacci dizisindeki n'inci terimi hesaplayacak basit bir sınıf kitaplığı oluşturmaya çalışacağız. Bunu yapmak için, sayıları bir araya toplayan özel bir matematik sınıfına bağlı olan Fibonacci dizilerini hesaplamak için bir sınıf oluşturmak isteyeceğiz. Ardından, programımızın beklendiği gibi çalıştığından emin olmak için .NET Testing Framework'ü kullanabiliriz.

Birim Testi Nedir?

Birim testi, programı, genellikle işlev düzeyindeki en küçük kod bitine böler ve işlevin beklenen değeri döndürmesini sağlar. Birim test çerçevesi kullanarak, birim testleri, daha sonra program oluşturulurken program üzerinde otomatik testler çalıştırabilen ayrı bir varlık haline gelir.

 [TestClass] public class FibonacciTests { [TestMethod] //Check the first value we calculate public void Fibonacci_GetNthTerm_Input2_AssertResult1() { //Arrange int n = 2; //setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert Assert.AreEqual(result, 1); } }

Matematik kitaplığımızın 2 + 2'yi doğru bir şekilde ekleyebildiği Arrange, Act, Assert metodolojisini kullanan basit bir birim testi.

Birim testleri ayarlandıktan sonra, kodda bir değişiklik yapılırsa, program ilk geliştirildiğinde bilinmeyen ek bir koşulu hesaba katmak için, örneğin, birim testleri, tüm durumların beklenen değerlerle eşleşip eşleşmediğini gösterecektir. fonksiyon tarafından çıktı.

Birim testi, entegrasyon testi değildir . Uçtan uca test değildir . Bunların her ikisi de güçlü metodolojiler olsa da, ikame olarak değil, birim testi ile birlikte çalışmalıdırlar.

Birim Testinin Faydaları ve Amacı

Birim testinin anlaşılması en zor, ancak en önemlisi, değiştirilen kodu anında yeniden test etme yeteneğidir. Bunu anlamanın bu kadar zor olmasının nedeni, pek çok geliştiricinin kendi kendine "Bu işleve bir daha asla dokunmayacağım" veya "İşim bittiğinde yeniden test edeceğim" diye düşünmesidir. Ve paydaşlar, "Bu parça zaten yazılmışsa, neden tekrar test etmem gerekiyor?"

Gelişim yelpazesinin her iki tarafında da bulunmuş biri olarak, her ikisini de söyledim. İçimdeki geliştirici neden tekrar test etmemiz gerektiğini biliyor.

Günlük olarak yaptığımız değişikliklerin büyük etkileri olabilir. Örneğin:

  • Anahtarınız, girdiğiniz yeni bir değeri uygun şekilde hesaba katıyor mu?
  • O anahtarı kaç kez kullandığını biliyor musun?
  • Büyük/küçük harfe duyarlı olmayan dize karşılaştırmalarını doğru bir şekilde hesaba kattınız mı?
  • Boş değerleri uygun şekilde kontrol ediyor musunuz?
  • Bir atma istisnası beklediğiniz gibi işleniyor mu?

Birim testi, bu soruları alır ve bunları kodda ve bu soruların her zaman yanıtlanmasını sağlamak için bir süreçte hafızaya alır. Yeni hatalar eklemediğinizden emin olmak için bir derlemeden önce birim testleri çalıştırılabilir. Birim testleri atomik olacak şekilde tasarlandığından, çok hızlı, genellikle test başına 10 milisaniyeden daha kısa sürede çalıştırılırlar. Çok büyük bir uygulamada bile tam bir test paketi bir saatten kısa sürede gerçekleştirilebilir. UAT süreciniz bununla eşleşebilir mi?

Test edilecek bir sınıf içinde bir sınıfı veya yöntemi kolayca aramak için ayarlanmış bir adlandırma kuralı örneği.
İlk çalıştırma olan ve kurulum süresini içeren Fibonacci_GetNthTerm_Input2_AssertResult1 dışındaki tüm birim testleri 5 ms'nin altında çalışır. Buradaki adlandırma kuralım, test etmek istediğim bir sınıf içinde bir sınıfı veya yöntemi kolayca aramak için ayarlandı

Yine de bir geliştirici olarak, belki bu sizin için daha fazla iş gibi geliyor. Evet, yayınladığınız kodun iyi olduğu konusunda içiniz rahat olsun. Ancak birim testi, tasarımınızın nerede zayıf olduğunu görme fırsatı da sunar. İki kod parçası için aynı birim testlerini mi yazıyorsunuz? Bunun yerine tek bir kod parçası üzerinde mi olmalılar?

Kodunuzu birim test edilebilir hale getirmek, tasarımınızı geliştirmenin bir yoludur. Ve hiç birim testi yapmamış veya kodlamadan önce tasarımı düşünmek için fazla zaman ayırmayan çoğu geliştirici için, tasarımınızın ne kadar geliştiğini birim testine hazır hale getirerek anlayabilirsiniz.

Kod Biriminiz Test Edilebilir mi?

DRY'nin yanı sıra başka düşüncelerimiz de var.

Yöntemleriniz veya İşlevleriniz Çok Fazla Şey Yapmaya mı Çalışıyor?

Beklediğinizden daha uzun süren aşırı karmaşık birim testleri yazmanız gerekiyorsa, yönteminiz çok karmaşık olabilir ve birden çok yöntem olarak daha uygun olabilir.

Bağımlılık Enjeksiyonundan Doğru Şekilde Yararlanıyor musunuz?

Test edilen yönteminiz başka bir sınıf veya işlev gerektiriyorsa, buna bağımlılık diyoruz. Birim testinde, bağımlılığın başlık altında ne yaptığıyla ilgilenmiyoruz; test edilen yöntemin amacı için bir kara kutudur. Bağımlılığın, davranışının düzgün çalışıp çalışmadığını belirleyen kendi birim testleri vardır.

Bir testçi olarak, bu bağımlılığı simüle etmek ve belirli durumlarda hangi değerlerin döndürüleceğini söylemek istersiniz. Bu size test senaryolarınız üzerinde daha fazla kontrol sağlayacaktır. Bunu yapmak için, bu bağımlılığın kukla (veya daha sonra göreceğimiz gibi, alay edilmiş) bir versiyonunu enjekte etmeniz gerekecek.

Bileşenleriniz Beklediğiniz Gibi Birbiriyle Etkileşime Giriyor mu?

Bağımlılıklarınızı ve bağımlılık enjeksiyonunuzu çözdükten sonra, kodunuza döngüsel bağımlılıklar eklediğinizi görebilirsiniz. A Sınıfı, B Sınıfına bağlıysa, bu da A Sınıfına bağlıdır, tasarımınızı yeniden gözden geçirmelisiniz.

Bağımlılık Enjeksiyonunun Güzelliği

Fibonacci örneğimizi ele alalım. Patronunuz size C#'ta mevcut olan ekleme operatöründen daha verimli ve doğru olan yeni bir sınıfları olduğunu söylüyor.

Bu özel örnek gerçek dünyada pek olası olmasa da, kimlik doğrulama, nesne eşleme ve hemen hemen her türlü algoritmik işlem gibi diğer bileşenlerde benzer örnekler görüyoruz. Bu makalenin amacı için, istemcinizin yeni ekleme işlevinin bilgisayarlar icat edildiğinden bu yana en yeni ve en iyisi olduğunu farz edelim.

Bu nedenle, patronunuz size tek bir Math sınıfına sahip bir kara kutu kitaplığı verir ve o sınıfta tek bir fonksiyon Add . Bir Fibonacci hesaplayıcısı uygulama işiniz muhtemelen şuna benzer:

 public int GetNthTerm(int n) { Math math = new Math(); int nMinusTwoTerm = 1; int nMinusOneTerm = 1; int newTerm = 0; for (int i = 2; i < n; i++) { newTerm = math.Add(nMinusOneTerm, nMinusTwoTerm); nMinusTwoTerm = nMinusOneTerm; nMinusOneTerm = newTerm; } return newTerm; }

Bu korkunç değil. Yeni bir Math sınıfını başlatırsınız ve bir sonrakini elde etmek için önceki iki terimi eklemek için bunu kullanırsınız. Bu yöntemi, 100 terimi hesaplayarak, 1000'inci terimi, 10.000'inci terimi hesaplayarak, vb., metodolojinizin iyi çalıştığından memnun olana kadar, normal test piliniz boyunca çalıştırırsınız. Sonra gelecekte bir kullanıcı, 501. terimin beklendiği gibi çalışmadığından şikayet eder. Akşamı kodunuza bakarak ve bu köşe vakasının neden çalışmadığını anlamaya çalışarak geçiriyorsunuz. En yeni ve en iyi Math sınıfının patronunuzun düşündüğü kadar harika olmadığından şüphelenmeye başlıyorsunuz. Ama bu bir kara kutu ve bunu gerçekten kanıtlayamazsınız – içsel olarak bir çıkmaza girersiniz.

Buradaki sorun, bağımlılık Math Fibonacci hesap makinenize enjekte edilmemesidir. Bu nedenle, testlerinizde Fibonacci'yi test etmek için her zaman Math mevcut, test edilmemiş ve bilinmeyen sonuçlarına güvenirsiniz. Math ile ilgili bir sorun varsa, Fibonacci her zaman yanlış olacaktır (501. terim için özel bir durum kodlamadan).

Bu sorunu düzeltme fikri, Math sınıfını Fibonacci hesap makinenize enjekte etmektir. Ancak daha da iyisi, Math sınıfı için genel yöntemleri (bizim durumumuzda Add ) tanımlayan bir arabirim oluşturmak ve arabirimi Math sınıfımıza uygulamaktır.

 public interface IMath { int Add(int x, int y); } public class Math : IMath { public int Add(int x, int y) { //super secret implementation here } } }

Math sınıfını Fibonacci'ye enjekte etmek yerine, IMath arayüzünü Fibonacci'ye enjekte edebiliriz. Buradaki fayda, doğru olduğunu bildiğimiz kendi OurMath sınıfımızı tanımlayabilmemiz ve hesap makinemizi buna karşı test edebilmemizdir. Daha da iyisi, Moq kullanarak Math.Add ne döndürdüğünü basitçe tanımlayabiliriz. Bir dizi toplam tanımlayabiliriz veya Math.Add x + y döndürmesini söyleyebiliriz.

 private IMath _math; public Fibonacci(IMath math) { _math = math; }

IMath arabirimini Fibonacci sınıfına enjekte edin

 //setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y);

Math.Add ne döndürdüğünü tanımlamak için Moq kullanma.

Şimdi, iki sayı eklemek için denenmiş ve doğru (peki, C#'da bu + operatörü yanlışsa daha büyük sorunlarımız var) yöntemimiz var. Yeni Mocked IMath kullanarak, 501'inci dönemimiz için bir birim testi kodlayabilir ve uygulamamızda hata yapıp yapmadığımızı veya özel Math sınıfının biraz daha çalışmaya ihtiyacı olup olmadığını görebiliriz.

Bir Yöntemin Çok Fazla Şey Yapmaya Çalışmasına İzin Vermeyin

Bu örnek aynı zamanda çok fazla şey yapan bir yöntem fikrine de işaret ediyor. Elbette, ekleme, işlevselliğini GetNthTerm yöntemimizden soyutlamaya gerek duymadan oldukça basit bir işlemdir. Ama ya operasyon biraz daha karmaşıksa? Eklemek yerine, belki de model doğrulama, üzerinde çalışacak bir nesne elde etmek için bir fabrikayı aramak veya bir depodan gerekli ek verileri toplamaktı.

Çoğu geliştirici, bir yöntemin tek bir amacı olduğu fikrine bağlı kalmaya çalışacaktır. Birim testinde, birim testlerinin atomik yöntemlere uygulanması gerektiği ilkesine bağlı kalmaya çalışıyoruz ve bir yönteme çok fazla işlem ekleyerek onu test edilemez hale getiriyoruz. Fonksiyonumuzu doğru bir şekilde test etmek için çok fazla test yazmamız gereken bir sorun yaratabiliriz.

Bir metoda eklediğimiz her parametre, parametrenin karmaşıklığına göre katlanarak yazmamız gereken testlerin sayısını arttırır. Mantığınıza bir boole eklerseniz, şimdi doğru ve yanlış durumları mevcut testlerinizle birlikte kontrol etmeniz gerektiğinden, yazılacak test sayısını iki katına çıkarmanız gerekir. Model doğrulama durumunda, birim testlerimizin karmaşıklığı çok hızlı bir şekilde artabilir.

Mantığa bir boole eklendiğinde ihtiyaç duyulan artırılmış testlerin diyagramı.

Hepimiz bir yönteme biraz fazladan eklemekten suçluyuz. Ancak bu daha büyük, daha karmaşık yöntemler, çok fazla birim testi ihtiyacı yaratır. Ve birim testleri yazdığınızda, yöntemin çok fazla yapmaya çalıştığı çabucak ortaya çıkıyor. Girdi parametrelerinizden çok fazla olası sonucu test etmeye çalıştığınızı düşünüyorsanız, yönteminizin bir dizi daha küçük sonuca bölünmesi gerektiğini düşünün.

Kendinizi Tekrar Etmeyin

En sevdiğimiz programlama kiracılarından biri. Bu oldukça düz ileri olmalıdır. Kendinizi aynı testleri birden fazla yazarken buluyorsanız, kodu birden fazla kez kullanmışsınızdır. Kullanmaya çalıştığınız her iki örnek için de erişilebilir olan ortak bir sınıfa çalışan yeniden düzenlemeniz size fayda sağlayabilir.

Hangi Birim Test Araçları Mevcuttur?

DotNet bize kutudan çıktığı gibi çok güçlü bir birim test platformu sunuyor. Bunu kullanarak, Arrange, Act, Assert metodolojisi olarak bilinen yöntemi uygulayabilirsiniz. İlk düşüncelerinizi düzenlersiniz, test edilen yönteminizle bu koşullara göre hareket eder ve ardından bir şeyin olduğunu iddia edersiniz. Bu aracı daha da güçlü hale getirerek her şeyi iddia edebilirsiniz. Bir yöntemin belirli sayıda çağrıldığını, yöntemin belirli bir değer döndürdüğünü, belirli bir istisna türünün atıldığını veya aklınıza gelebilecek başka herhangi bir şeyi iddia edebilirsiniz. Daha gelişmiş bir çerçeve arayanlar için NUnit ve Java muadili JUnit uygun seçeneklerdir.

 [TestMethod] //Test To Verify Add Never Called on the First Term public void Fibonacci_GetNthTerm_Input0_AssertAddNeverCalled() { //Arrange int n = 0; //setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny<int>(), It.IsAny<int>()), Times.Never); }

Fibonacci Yöntemimizin bir istisna atarak negatif sayıları ele aldığını test etmek. Birim testleri, istisnanın atıldığını doğrulayabilir.

Bağımlılık eklemeyi işlemek için DotNet platformunda hem Ninject hem de Unity bulunur. İkisi arasında çok az fark vardır ve bu, yapılandırmaları Akıcı Sözdizimi veya XML Yapılandırması ile yönetmek isteyip istemediğiniz meselesi haline gelir.

Bağımlılıkları simüle etmek için Moq'u öneririm. Moq, elinizin altında olmak için zor olabilir, ancak işin özü, bağımlılıklarınızın alaylı bir versiyonunu yaratmanızdır. Ardından, bağımlılığa belirli koşullar altında neyin döndürüleceğini söylersiniz. Örneğin, tam sayının karesini alan Square(int x) adında bir yönteminiz varsa, bunu x = 2 olduğunda, 4 döndürdüğünde söyleyebilirsiniz. Ayrıca, herhangi bir tamsayı için x^2 döndürmesini de söyleyebilirsiniz. Veya x = 2 olduğunda 5 döndürmesini söyleyebilirsiniz. Neden son durumu gerçekleştiresiniz? Testin rolü altındaki yöntemin bağımlılıktan gelen yanıtı doğrulaması durumunda, hatayı doğru şekilde yakaladığınızdan emin olmak için geçersiz yanıtları geri dönmeye zorlamak isteyebilirsiniz.

 [TestMethod] //Test To Verify Add Called Three times on the fifth Term public void Fibonacci_GetNthTerm_Input4_AssertAddCalledThreeTimes() { //Arrange int n = 4; //setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(3)); }

IMath arayüzüne test altında Add nasıl işleneceğini söylemek için Moq'u kullanma. It.Is ile açık durumları veya It.Is ile bir aralık It.IsInRange .

DotNet için Birim Test Çerçeveleri

Microsoft Birim Test Çerçevesi

Microsoft Unit Testing Framework, Microsoft'un kullanıma hazır birim test çözümüdür ve Visual Studio'ya dahildir. VS ile birlikte geldiği için, onunla güzel bir şekilde bütünleşir. Bir projeye başladığınızda, Visual Studio size uygulamanızın yanında bir Birim Test Kitaplığı oluşturmak isteyip istemediğinizi soracaktır.

Microsoft Unit Testing Framework, test prosedürlerinizi daha iyi analiz etmenize yardımcı olacak bir dizi araçla birlikte gelir. Ayrıca, Microsoft'a ait olduğu ve Microsoft tarafından yazıldığı için, varlığının ileriye dönük olarak bir miktar istikrar duygusu vardır.

Ancak Microsoft araçlarıyla çalışırken size verdiklerini alırsınız. Microsoft Unit Testing Framework'ü entegre etmek zahmetli olabilir.

NUnit

NUnit kullanmanın benim için en büyük avantajı parametreli testler. Yukarıdaki Fibonacci örneğimizde, bir dizi test durumu girebilir ve bu sonuçların doğru olduğundan emin olabiliriz. Ve 501'inci problemimiz durumunda, yeni bir test yöntemine ihtiyaç duymadan testin her zaman çalışmasını sağlamak için her zaman yeni bir parametre seti ekleyebiliriz.

NUnit'in en büyük dezavantajı onu Visual Studio'ya entegre etmesidir. Microsoft sürümüyle birlikte gelen özelliklerden yoksundur ve kendi araç setinizi indirmeniz gerekeceği anlamına gelir.

xUnit.Net

xUnit, mevcut .NET ekosistemiyle çok güzel bir şekilde bütünleştiği için C#'ta çok popülerdir. Nuget, birçok xUnit uzantısına sahiptir. Ayrıca, Team Foundation Server ile güzel bir şekilde bütünleşir, ancak kaç tane .NET geliştiricisinin çeşitli Git uygulamaları üzerinde hala TFS kullandığından emin değilim.

Olumsuz tarafı, birçok kullanıcı xUnit'in belgelerinin biraz eksik olduğundan şikayet ediyor. Yeni kullanıcılar için birim testi, bu büyük bir baş ağrısına neden olabilir. Ek olarak, xUnit'in genişletilebilirliği ve uyarlanabilirliği, öğrenme eğrisini NUnit veya Microsoft'un Birim Test Çerçevesinden biraz daha dik hale getirir.

Test Odaklı Tasarım/Geliştirme

Test odaklı tasarım/geliştirme (TDD), kendi gönderisini hak eden biraz daha gelişmiş bir konudur. Ancak bir tanıtım yapmak istedim.

Buradaki fikir, birim testlerinizle başlamak ve birim testlerinize neyin doğru olduğunu söylemektir. Ardından, kodunuzu bu testlerin etrafına yazabilirsiniz. Teoride, konsept kulağa basit geliyor, ancak pratikte beyninizi uygulama hakkında geriye doğru düşünmek için eğitmek çok zor. Ancak yaklaşımın yerleşik avantajı, olaydan sonra birim testlerinizi yazmanız gerekmemesidir. Bu, daha az yeniden düzenleme, yeniden yazma ve sınıf karışıklığına yol açar.

TDD son yıllarda biraz moda oldu ancak benimseme yavaş oldu. Kavramsal doğası, onaylanmayı zorlaştıran paydaşlar için kafa karıştırıcıdır. Ancak bir geliştirici olarak sürece alışmak için TDD yaklaşımını kullanarak küçük bir uygulama bile yazmanızı tavsiye ederim.

Neden Çok Fazla Birim Testi Yapamıyorsunuz?

Birim testi, geliştiricilerin emrinde olan en güçlü test araçlarından biridir. Uygulamanızın tam bir testi için hiçbir şekilde yeterli değildir, ancak regresyon testi, kod tasarımı ve amacın belgelenmesindeki faydaları eşsizdir.

Çok fazla birim testi yazmak diye bir şey yoktur. Her uç durum, yazılımınızda ileride büyük sorunlar önerebilir. Bulunan hataları birim testleri olarak anmak, bu hataların sonraki kod değişiklikleri sırasında yazılımınıza geri dönmenin yollarını bulmamasını sağlayabilir. Projenizin ön bütçesine %10-20 oranında ekleme yapabilirsiniz, ancak eğitim, hata düzeltmeleri ve belgelerde bundan çok daha fazlasını kaydedebilirsiniz.

Bu makalede kullanılan Bitbucket deposunu burada bulabilirsiniz.