Aracılığıyla paylaş


Otomatik Birim Testini Etkinleştirme

Microsoft tarafından

PDF’yi İndir

Bu, ASP.NET MVC 1 kullanarak küçük ama eksiksiz bir web uygulaması oluşturmayı gösteren ücretsiz bir "NerdDinner" uygulama öğreticisinin 12. adımıdır.

12. Adım, NerdDinner işlevselliğimizi doğrulayan ve gelecekte uygulamada değişiklikler ve geliştirmeler yapma konusunda bize güven veren bir otomatik birim testi paketinin nasıl geliştirileceğini gösterir.

ASP.NET MVC 3 kullanıyorsanız , MVC 3 ile Çalışmaya Başlama veya MVC Müzik Deposu öğreticilerini izlemenizi öneririz.

NerdDinner 12. Adım: Birim Testi

Şimdi NerdDinner işlevselliğimizi doğrulayan ve gelecekte uygulamada değişiklikler ve geliştirmeler yapma güveni sağlayacak bir otomatik birim testleri paketi geliştirelim.

Neden Birim Testi?

Bir sabah işe sürücüde üzerinde çalıştığınız bir uygulama hakkında ani bir ilham yanıp söner. Uygulamayı önemli ölçüde daha iyi hale getirmek için uygulayabileceğiniz bir değişiklik olduğunu fark ediyorsunuz. Kodu temizleyen, yeni bir özellik ekleyen veya bir hatayı düzelten bir yeniden düzenleme olabilir.

Bilgisayarınıza ulaştığınızda karşınıza çıkan soru şudur: "Bu geliştirmeyi yapmak ne kadar güvenli?" Değişikliği yapmanın yan etkileri varsa veya bir şeyi bozuyorsa ne olur? Değişikliğin uygulanması basit olabilir ve yalnızca birkaç dakika sürebilir, ancak tüm uygulama senaryolarının el ile test edilmesi saatler sürerse ne olur? Bir senaryoya göz atmayı unutursanız ve bozuk bir uygulama üretime geçerse ne olur? Bu geliştirmeyi yapmak gerçekten tüm çabaya değer mi?

Otomatik birim testleri, uygulamalarınızı sürekli olarak geliştirmenize ve üzerinde çalıştığınız koddan korkmaktan kaçınmanıza olanak tanıyan bir güvenlik ağı sağlayabilir. İşlevselliği hızla doğrulayan otomatikleştirilmiş testlere sahip olmak güvenle kod oluşturmanıza olanak tanır ve aksi takdirde rahat hissetmediğiniz geliştirmeler yapmanıza olanak tanır. Ayrıca daha sürdürülebilir ve daha uzun ömürlü çözümler oluşturmaya da yardımcı olur ve bu da yatırım getirisini çok daha fazla artırır.

ASP.NET MVC Framework, birim testi uygulama işlevselliğini kolaylaştırır ve doğal hale getirir. Ayrıca, test öncelikli tabanlı geliştirmeyi etkinleştiren bir Test Temelli Geliştirme (TDD) iş akışı sağlar.

NerdDinner.Tests Projesi

Bu öğreticinin başında NerdDinner uygulamamızı oluşturduğumuzda, uygulama projesiyle birlikte ilerleyecek bir birim testi projesi oluşturmak isteyip istemediğimiz soruldu:

Birim Testi Projesi Oluştur iletişim kutusunun ekran görüntüsü. Evet, birim testi projesi oluştur seçili. İnek Akşam Yemeği nokta Testleri, Test projesi adı olarak yazılır.

Çözümümüze bir "NerdDinner.Tests" projesinin eklenmesiyle sonuçlanan "Evet, birim testi projesi oluştur" radyo düğmesini seçili tuttuk:

Çözüm Gezgini gezinti ağacının ekran görüntüsü. İnek Akşam Yemeği noktası Testler seçilir.

NerdDinner.Tests projesi NerdDinner uygulama projesi derlemesine başvurur ve uygulama işlevselliğini doğrulayan otomatikleştirilmiş testleri kolayca eklememizi sağlar.

Akşam Yemeği Modeli Sınıfımız için Birim Testleri Oluşturma

Model katmanımızı oluştururken oluşturduğumuz Dinner sınıfını doğrulayan NerdDinner.Tests projemize bazı testler ekleyelim.

Başlangıç olarak test projemizde modelle ilgili testlerimizi yerleştireceğimiz "Modeller" adlı yeni bir klasör oluşturacağız. Ardından klasöre sağ tıklayıp Yeni Test Ekle> menü komutunu seçeceğiz. Bu, "Yeni Test Ekle" iletişim kutusunu açar.

"Birim Testi" oluşturmayı ve "DinnerTest.cs" olarak adlandırmayı seçeceğiz:

Yeni Test Ekle iletişim kutusunun ekran görüntüsü. Birim Testi vurgulanır. Akşam Yemeği Testi nokta c s, Test Adı olarak yazılır.

"Tamam" düğmesine tıkladığımızda Visual Studio projeye bir DinnerTest.cs dosyası ekler (ve açar):

Visual Studio'da Akşam Yemeği Testi nokta c s dosyasının ekran görüntüsü.

Varsayılan Visual Studio birim testi şablonunun içinde biraz karışık bulduğum bir grup kazan plakası kodu var. Şimdi yalnızca aşağıdaki kodu içerecek şekilde temizleyelim:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NerdDinner.Models;

namespace NerdDinner.Tests.Models {
 
    [TestClass]
    public class DinnerTest {

    }
}

Yukarıdaki DinnerTest sınıfındaki [TestClass] özniteliği, bunu hem testleri hem de isteğe bağlı test başlatma ve kaldırma kodunu içerecek bir sınıf olarak tanımlar. Üzerinde [TestMethod] özniteliği olan genel yöntemler ekleyerek içindeki testleri tanımlayabiliriz.

Aşağıda, Dinner sınıfımızdaki bu alıştırmayı ekleyeceğimiz iki testin ilki yer almaktadır. İlk test, tüm özellikler doğru şekilde ayarlanmadan yeni bir Akşam Yemeği oluşturulursa Akşam Yemeğimizin geçersiz olduğunu doğrular. İkinci test, bir Akşam Yemeği'nin tüm özellikleri geçerli değerlerle ayarlandığında Akşam Yemeğimizin geçerli olduğunu doğrular:

[TestClass]
public class DinnerTest {

    [TestMethod]
    public void Dinner_Should_Not_Be_Valid_When_Some_Properties_Incorrect() {

        //Arrange
        Dinner dinner = new Dinner() {
            Title = "Test title",
            Country = "USA",
            ContactPhone = "BOGUS"
        };

        // Act
        bool isValid = dinner.IsValid;

        //Assert
        Assert.IsFalse(isValid);
    }

    [TestMethod]
    public void Dinner_Should_Be_Valid_When_All_Properties_Correct() {
        
        //Arrange
        Dinner dinner = new Dinner {
            Title = "Test title",
            Description = "Some description",
            EventDate = DateTime.Now,
            HostedBy = "ScottGu",
            Address = "One Microsoft Way",
            Country = "USA",
            ContactPhone = "425-703-8072",
            Latitude = 93,
            Longitude = -92,
        };

        // Act
        bool isValid = dinner.IsValid;

        //Assert
        Assert.IsTrue(isValid);
    }
}

Yukarıda, test adlarımızın çok açık (ve biraz ayrıntılı) olduğunu göreceksiniz. Bunu, yüzlerce veya binlerce küçük test oluşturacağımız için yapıyoruz ve her birinin amacını ve davranışını hızla belirlemeyi kolaylaştırmak istiyoruz (özellikle bir test çalıştırıcısında hataların listesini incelediğimizde). Test adları, test ettikleri işlevden sonra adlandırılmalıdır. Yukarıda "Noun_Should_Verb" adlandırma deseni kullanıyoruz.

Testleri "Düzenleme, Eylem, Onay" anlamına gelen "AAA" test desenini kullanarak yapılandırıyoruz:

  • Düzenleme: Test edilen birimi ayarlama
  • Eylem: Test ve yakalama sonuçları altında üniteyi alıştırma
  • Onay: Davranışı doğrulama

Testleri yazarken tek tek testlerin çok fazla işlem yapmasını önlemek isteriz. Bunun yerine her test yalnızca tek bir kavramı doğrulamalıdır (bu da hataların nedenini belirlemeyi çok daha kolay hale getirir). İyi bir kılavuz, denemek ve her test için yalnızca tek bir onay deyimine sahip olmaktır. Bir test yönteminde birden fazla assert deyiminiz varsa, hepsinin aynı kavramı test etmek için kullanıldığından emin olun. Şüpheye düşersen, başka bir test yap.

Testleri Çalıştırma

Visual Studio 2008 Professional (ve üzeri sürümler), IDE içinde Visual Studio Birim Testi projelerini çalıştırmak için kullanılabilecek yerleşik bir test çalıştırıcısı içerir. Tüm birim testlerimizi çalıştırmak için Çözüm menüsündeKi Tüm Testleri Çalıştır> komutunu seçebilir> (veya Ctrl R, A yazabilirsiniz). Alternatif olarak, imlecimizi belirli bir test sınıfı veya test yöntemi içinde konumlandırabilir ve birim testlerinin bir alt kümesini çalıştırmak için Geçerli Bağlamda Test Çalıştır>> menü komutunu (veya Ctrl R, T yazın) kullanabiliriz.

Şimdi imlecimizi DinnerTest sınıfının içine yerleştirelim ve az önce tanımladığımız iki testi çalıştırmak için "Ctrl R, T" yazalım. Bunu yaptığımızda Visual Studio'da bir "Test Sonuçları" penceresi görünür ve test çalıştırmamızın sonuçlarının bu pencere içinde listelendiğini görürüz:

Visual Studio'da Test Sonuçları penceresinin ekran görüntüsü. Test çalıştırmasının sonuçları içinde listelenir.

Not: VS test sonuçları penceresi varsayılan olarak Sınıf Adı sütununu göstermez. Test Sonuçları penceresinde sağ tıklayıp Sütun Ekle/Kaldır menü komutunu kullanarak bunu ekleyebilirsiniz.

İki testimizin çalıştırılması saniyenin yalnızca bir bölümünü aldı ve gördüğünüz gibi ikisi de başarılı oldu. Artık belirli kural doğrulamalarını doğrulayan ek testler oluşturarak ve Dinner sınıfına eklediğimiz iki yardımcı yöntemi (IsUserHost() ve IsUserRegistered() ) kapsayan ek testler oluşturarak bunları genişletebiliriz. Dinner sınıfı için tüm bu testlerin yapılması, gelecekte yeni iş kuralları ve doğrulamalar eklemeyi çok daha kolay ve güvenli hale getirecektir. Yeni kural mantığımızı Akşam Yemeği'ne ekleyebilir ve saniyeler içinde önceki mantık işlevlerimizin hiçbirini bozmadığını doğrulayabiliriz.

Açıklayıcı bir test adı kullanmanın, her testin neyi doğruladığını hızlı bir şekilde anlamayı nasıl kolaylaştırdığını fark edin. Araçlar-Seçenekler> menü komutunu kullanmanızı, Test Araçları-Test> Yürütme yapılandırma ekranını açmanızı ve "Başarısız veya yetersiz birim testi sonucuna çift tıklanması testte hata noktasını görüntüler" onay kutusunu işaretlemenizi öneririz. Bu, test sonuçları penceresinde bir hataya çift tıklamanıza ve onay hatasına hemen atlamanıza olanak sağlar.

DinnersController Birim Testleri Oluşturma

Şimdi DinnersController işlevimizi doğrulayan bazı birim testleri oluşturalım. İlk olarak Test projemizdeki "Denetleyiciler" klasörüne sağ tıklayıp Yeni Test Ekle> menü komutunu seçeceğiz. Bir "Birim Testi" oluşturacak ve bunu "DinnersControllerTest.cs" olarak adlandıracağız.

DinnersController üzerinde Details() eylem yöntemini doğrulayan iki test yöntemi oluşturacağız. İlki, mevcut bir Akşam Yemeği istendiğinde görünümün döndürüldüğünü doğrular. İkincisi, mevcut olmayan bir Akşam Yemeği istendiğinde "NotFound" görünümünün döndürüldüğünü doğrular:

[TestClass]
public class DinnersControllerTest {

    [TestMethod]
    public void DetailsAction_Should_Return_View_For_ExistingDinner() {

        // Arrange
        var controller = new DinnersController();

        // Act
        var result = controller.Details(1) as ViewResult;

        // Assert
        Assert.IsNotNull(result, "Expected View");
    }

    [TestMethod]
    public void DetailsAction_Should_Return_NotFoundView_For_BogusDinner() {

        // Arrange
        var controller = new DinnersController();

        // Act
        var result = controller.Details(999) as ViewResult;

        // Assert
        Assert.AreEqual("NotFound", result.ViewName);
    } 
}

Yukarıdaki kod temiz bir şekilde derlenmiş. Ancak testleri çalıştırdığımızda ikisi de başarısız olur:

Kodun ekran görüntüsü. Her iki test de başarısız oldu.

Hata iletilerine baktığımızda, testlerin başarısız olmasının nedeninin DinnersRepository sınıfımızın veritabanına bağlanamaması olduğunu görürüz. NerdDinner uygulamamız, NerdDinner uygulama projesinin \App_Data dizininde bulunan yerel bir SQL Server Express dosyasına bağlantı dizesi kullanıyor. NerdDinner.Tests projemiz, uygulama projesinden sonra farklı bir dizinde derlenip çalıştırıldığından, bağlantı dizemizin göreli yol konumu yanlıştır.

Sql Express veritabanı dosyasını test projemize kopyalayıp test projemizin App.config uygun bir test bağlantı dizesi ekleyerek bunu düzeltebiliriz . Bu, yukarıdaki testlerin engeli kaldırılır ve çalışır duruma getirilir.

Ancak gerçek bir veritabanı kullanarak birim testi kodu bir dizi zorluğu beraberinde getirir. Özellikle:

  • Birim testlerinin yürütme süresini önemli ölçüde yavaşlatır. Testleri çalıştırmak ne kadar uzun sürerse, bunları sık sık yürütme olasılığınız da o kadar düşüktür. İdeal olarak birim testlerinizin saniyeler içinde çalıştırılabilmesini ve projenin derlenmesi kadar doğal bir şekilde gerçekleştirilmesini istersiniz.
  • Testlerdeki kurulum ve temizleme mantığını karmaşıklaştırır. Her birim testinin yalıtılmasını ve diğerlerinden bağımsız olmasını istiyorsunuz (yan etkileri veya bağımlılıkları olmadan). Gerçek bir veritabanında çalışırken durum bilgisi sahibi olmanız ve testler arasında veritabanını sıfırlamanız gerekir.

Şimdi bu sorunları çözmemize yardımcı olabilecek ve testlerimizle gerçek bir veritabanı kullanma gereksinimini önleyebilecek "bağımlılık ekleme" adlı tasarım desenine göz atalım.

Bağımlılık Ekleme

Şu anda DinnersController, DinnerRepository sınıfıyla sıkı bir şekilde "eşleştirilmiştir". "Bağlama", bir sınıfın çalışmak için açıkça başka bir sınıfa bağlı olduğu bir durumu ifade eder:

public class DinnersController : Controller {

    DinnerRepository dinnerRepository = new DinnerRepository();

    //
    // GET: /Dinners/Details/5

    public ActionResult Details(int id) {

        Dinner dinner = dinnerRepository.FindDinner(id);

        if (dinner == null)
            return View("NotFound");

        return View(dinner);
    }

DinnerRepository sınıfı bir veritabanına erişim gerektirdiğinden, DinnersController sınıfının DinnerRepository üzerindeki sıkı bir şekilde bağlanmış bağımlılığı, DinnersController eylem yöntemlerinin test edilmesi için bir veritabanı oluşturmamızı gerektirir.

Bağımlılıkların (veri erişimi sağlayan depo sınıfları gibi) artık bunları kullanan sınıflar içinde örtük olarak oluşturulmadığı bir yaklaşım olan "bağımlılık ekleme" adlı bir tasarım deseni kullanarak bunu aşabiliriz. Bunun yerine bağımlılıklar, oluşturucu bağımsız değişkenleri kullanılarak bunları kullanan sınıfa açıkça geçirilebilir. Bağımlılıklar arabirimler kullanılarak tanımlanıyorsa, birim testi senaryoları için "sahte" bağımlılık uygulamalarını geçirme esnekliğine sahibiz. Bu, veritabanına gerçekten erişim gerektirmeyen teste özgü bağımlılık uygulamaları oluşturmamızı sağlar.

Bunu uygulamada görmek için DinnersController ile bağımlılık ekleme uygulayalım.

IDinnerRepository arabirimini ayıklama

İlk adımımız, denetleyicilerimizin Akşam Yemekleri almak ve güncelleştirmek için ihtiyaç duyan depo sözleşmesini kapsülleyen yeni bir IDinnerRepository arabirimi oluşturmak olacaktır.

\Models klasörüne sağ tıklayıp Yeni Öğe Ekle> menü komutunu seçerek ve IDinnerRepository.cs adlı yeni bir arabirim oluşturarak bu arabirim sözleşmesini el ile tanımlayabiliriz.

Alternatif olarak, mevcut DinnerRepository sınıfımızdan bizim için otomatik olarak bir arabirim ayıklamak ve oluşturmak için yerleşik Visual Studio Professional (ve üzeri sürümler) yeniden düzenleme araçlarını kullanabiliriz. VS kullanarak bu arabirimi ayıklamak için imleci DinnerRepository sınıfındaki metin düzenleyicisine getirin ve sağ tıklayıp Arabirimi Yeniden Düzenle-Ayıkla> menü komutunu seçin:

Yeniden düzenleme alt menüsünde Seçilen Arabirimi Ayıkla seçeneğini gösteren ekran görüntüsü.

Bu işlem "Arabirimi Ayıkla" iletişim kutusunu başlatır ve oluşturulacak arabirimin adını sorar. Varsayılan olarak IDinnerRepository olur ve arabirime eklemek için mevcut DinnerRepository sınıfındaki tüm genel yöntemleri otomatik olarak seçer:

Visual Studio'da Test Sonuçları penceresinin ekran görüntüsü.

"Tamam" düğmesine tıkladığımızda Visual Studio uygulamamıza yeni bir IDinnerRepository arabirimi ekler:

public interface IDinnerRepository {

    IQueryable<Dinner> FindAllDinners();
    IQueryable<Dinner> FindByLocation(float latitude, float longitude);
    IQueryable<Dinner> FindUpcomingDinners();
    Dinner             GetDinner(int id);

    void Add(Dinner dinner);
    void Delete(Dinner dinner);
    
    void Save();
}

Mevcut DinnerRepository sınıfımız, arabirimini uygulayacak şekilde güncelleştirilecektir:

public class DinnerRepository : IDinnerRepository {
   ...
}

Oluşturucu ekleme desteği için DinnersController güncelleştiriliyor

Şimdi DinnersController sınıfını yeni arabirimi kullanacak şekilde güncelleştireceğiz.

Şu anda DinnersController, "dinnerRepository" alanının her zaman bir DinnerRepository sınıfı olması için sabit kodlanmıştır:

public class DinnersController : Controller {

    DinnerRepository dinnerRepository = new DinnerRepository();

    ...
}

Bunu değiştirerek "dinnerRepository" alanının DinnerRepository yerine IDinnerRepository türünde olması sağlanır. Ardından iki genel DinnersController oluşturucusu ekleyeceğiz. Oluşturuculardan biri, bir IDinnerRepository'nin bağımsız değişken olarak geçirilmesini sağlar. Diğeri, mevcut DinnerRepository uygulamamızı kullanan varsayılan bir oluşturucudur:

public class DinnersController : Controller {

    IDinnerRepository dinnerRepository;

    public DinnersController()
        : this(new DinnerRepository()) {
    }

    public DinnersController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
    ...
}

MVC ASP.NET varsayılan oluşturucuları kullanarak denetleyici sınıfları oluşturduğundan, çalışma zamanında DinnersController veri erişimi gerçekleştirmek için DinnerRepository sınıfını kullanmaya devam eder.

Ancak artık parametre oluşturucusunu kullanarak "sahte" bir akşam yemeği deposu uygulaması geçirmek için birim testlerimizi güncelleştirebiliriz. Bu "sahte" akşam yemeği deposu gerçek bir veritabanına erişim gerektirmez ve bunun yerine bellek içi örnek verileri kullanır.

FakeDinnerRepository sınıfı oluşturma

Şimdi bir FakeDinnerRepository sınıfı oluşturalım.

İlk olarak NerdDinner.Tests projemizde bir "Fakes" dizini oluşturacak ve ardından buna yeni bir FakeDinnerRepository sınıfı ekleyeceğiz (klasöre sağ tıklayıp Yeni Sınıf Ekle'yi> seçin):

Yeni Sınıf Ekle menü öğesinin ekran görüntüsü. Yeni Öğe Ekle vurgulanmış.

FakeDinnerRepository sınıfının IDinnerRepository arabirimini uygulaması için kodu güncelleştireceğiz. Ardından sağ tıklayıp "Arabirimi uygulama IDinnerRepository" bağlam menüsü komutunu seçebiliriz:

Arabirim uygulama I Akşam Yemeği Deposu bağlam menüsü komutunun ekran görüntüsü.

Bu, Visual Studio'nun tüm IDinnerRepository arabirim üyelerini varsayılan "saplama" uygulamalarıyla FakeDinnerRepository sınıfımıza otomatik olarak eklemesine neden olur:

public class FakeDinnerRepository : IDinnerRepository {

    public IQueryable<Dinner> FindAllDinners() {
        throw new NotImplementedException();
    }

    public IQueryable<Dinner> FindByLocation(float lat, float long){
        throw new NotImplementedException();
    }

    public IQueryable<Dinner> FindUpcomingDinners() {
        throw new NotImplementedException();
    }

    public Dinner GetDinner(int id) {
        throw new NotImplementedException();
    }

    public void Add(Dinner dinner) {
        throw new NotImplementedException();
    }

    public void Delete(Dinner dinner) {
        throw new NotImplementedException();
    }

    public void Save() {
        throw new NotImplementedException();
    }
}

Ardından FakeDinnerRepository uygulamasını, bir oluşturucu bağımsız değişkeni olarak geçirilen bellek içi Bir Liste<Akşam Yemeği> koleksiyonundan çalışacak şekilde güncelleştirebiliriz:

public class FakeDinnerRepository : IDinnerRepository {

    private List<Dinner> dinnerList;

    public FakeDinnerRepository(List<Dinner> dinners) {
        dinnerList = dinners;
    }

    public IQueryable<Dinner> FindAllDinners() {
        return dinnerList.AsQueryable();
    }

    public IQueryable<Dinner> FindUpcomingDinners() {
        return (from dinner in dinnerList
                where dinner.EventDate > DateTime.Now
                select dinner).AsQueryable();
    }

    public IQueryable<Dinner> FindByLocation(float lat, float lon) {
        return (from dinner in dinnerList
                where dinner.Latitude == lat && dinner.Longitude == lon
                select dinner).AsQueryable();
    }

    public Dinner GetDinner(int id) {
        return dinnerList.SingleOrDefault(d => d.DinnerID == id);
    }

    public void Add(Dinner dinner) {
        dinnerList.Add(dinner);
    }

    public void Delete(Dinner dinner) {
        dinnerList.Remove(dinner);
    }

    public void Save() {
        foreach (Dinner dinner in dinnerList) {
            if (!dinner.IsValid)
                throw new ApplicationException("Rule violations");
        }
    }
}

Artık veritabanı gerektirmeyen sahte bir IDinnerRepository uygulamamız var ve bunun yerine Bellek içi Dinner nesnelerinin listesini çalıştırabiliyoruz.

Birim Testleri ile FakeDinnerRepository Kullanma

Veritabanı kullanılamadığından daha önce başarısız olan DinnersController birim testlerine geri dönelim. Aşağıdaki kodu kullanarak test yöntemlerini, örnek bellek içi Akşam Yemeği verileriyle doldurulmuş bir FakeDinnerRepository kullanacak şekilde DinnersController'a güncelleştirebiliriz:

[TestClass]
public class DinnersControllerTest {

    List<Dinner> CreateTestDinners() {

        List<Dinner> dinners = new List<Dinner>();

        for (int i = 0; i < 101; i++) {

            Dinner sampleDinner = new Dinner() {
                DinnerID = i,
                Title = "Sample Dinner",
                HostedBy = "SomeUser",
                Address = "Some Address",
                Country = "USA",
                ContactPhone = "425-555-1212",
                Description = "Some description",
                EventDate = DateTime.Now.AddDays(i),
                Latitude = 99,
                Longitude = -99
            };
            
            dinners.Add(sampleDinner);
        }
        
        return dinners;
    }

    DinnersController CreateDinnersController() {
        var repository = new FakeDinnerRepository(CreateTestDinners());
        return new DinnersController(repository);
    }

    [TestMethod]
    public void DetailsAction_Should_Return_View_For_Dinner() {

        // Arrange
        var controller = CreateDinnersController();

        // Act
        var result = controller.Details(1);

        // Assert
        Assert.IsInstanceOfType(result, typeof(ViewResult));
    }

    [TestMethod]
    public void DetailsAction_Should_Return_NotFoundView_For_BogusDinner() {

        // Arrange
        var controller = CreateDinnersController();

        // Act
        var result = controller.Details(999) as ViewResult;

        // Assert
        Assert.AreEqual("NotFound", result.ViewName);
    }
}

Ve şimdi bu testleri çalıştırdığımızda ikisi de geçer:

Her iki testin de başarılı olduğu birim testlerinin ekran görüntüsü.

En iyisi, çalıştırmak için saniyenin yalnızca bir bölümünü alır ve karmaşık bir kurulum/temizleme mantığı gerektirmez. Artık gerçek bir veritabanına bağlanmaya gerek kalmadan tüm DinnersController eylem yöntemi kodumuzu (listeleme, sayfalama, ayrıntılar, oluşturma, güncelleştirme ve silme dahil) birim testi gerçekleştirebiliriz.

Yan Konu: Bağımlılık Ekleme Çerçeveleri
El ile bağımlılık ekleme işlemi (yukarıda olduğu gibi) düzgün çalışır, ancak bir uygulamadaki bağımlılık ve bileşen sayısı arttıkça bakımı zorlaşır. .NET için daha da fazla bağımlılık yönetimi esnekliği sağlamaya yardımcı olabilecek çeşitli bağımlılık ekleme çerçeveleri vardır. Bazen "Denetimin Ters Çevrilmesi" (IoC) kapsayıcıları olarak da adlandırılan bu çerçeveler, çalışma zamanında bağımlılıkları belirtmek ve nesnelere geçirmek için ek bir yapılandırma desteği düzeyi sağlayan mekanizmalar sağlar (çoğunlukla oluşturucu ekleme kullanılarak). .NET'teki en popüler OSS Bağımlılık Ekleme / IOC çerçevelerinden bazıları şunlardır: AutoFac, Ninject, Spring.NET, StructureMap ve Windsor. ASP.NET MVC, geliştiricilerin denetleyicilerin çözümlenmesine ve örneklenmesine katılmasını sağlayan ve Bağımlılık Ekleme / IoC çerçevelerinin bu işlem içinde temiz bir şekilde tümleştirilmesini sağlayan genişletilebilirlik API'lerini kullanıma sunar. DI/IOC çerçevesi kullanmak, DinnersController'dan varsayılan oluşturucuyu kaldırmamıza da olanak tanır ve bu da bu da onunla DinnerRepository arasındaki bağlantıyı tamamen kaldırır. NerdDinner uygulamamızla bağımlılık ekleme / IOC çerçevesi kullanmayacağız. Ancak Bu, NerdDinner kod tabanı ve özellikleri büyüdüyse gelecekte göz önünde bulunduracağımız bir şeydir.

Eylem Birimi Testlerini Düzenle Oluşturma

Şimdi DinnersController'ın Düzenle işlevini doğrulayan bazı birim testleri oluşturalım. İlk olarak Düzenle eylemimizin HTTP-GET sürümünü test edeceğiz:

//
// GET: /Dinners/Edit/5

[Authorize]
public ActionResult Edit(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    return View(new DinnerFormViewModel(dinner));
}

Geçerli bir akşam yemeği istendiğinde DinnerFormViewModel nesnesinin yedeklediği bir Görünümün geri işlendiğini doğrulayan bir test oluşturacağız:

[TestMethod]
public void EditAction_Should_Return_View_For_ValidDinner() {

    // Arrange
    var controller = CreateDinnersController();

    // Act
    var result = controller.Edit(1) as ViewResult;

    // Assert
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
}

Ancak testi çalıştırdığımızda, Edit yöntemi Dinner.IsHostedBy() denetimini gerçekleştirmek için User.Identity.Name özelliğine eriştiğinde null başvuru özel durumu oluştuğundan başarısız olduğunu göreceğiz.

Denetleyici temel sınıfındaki User nesnesi oturum açmış kullanıcı hakkındaki ayrıntıları kapsüller ve çalışma zamanında denetleyiciyi oluştururken ASP.NET MVC tarafından doldurulur. DinnersController'ı bir web sunucusu ortamı dışında test ettiğimizden User nesnesi ayarlanmadı (bu nedenle null başvuru özel durumu).

User.Identity.Name özelliğinin sahtesini oluşturma

Sahte çerçeveler, testlerimizi destekleyen bağımlı nesnelerin sahte sürümlerini dinamik olarak oluşturmamızı sağlayarak testi kolaylaştırır. Örneğin, Edit eylem testimizdeki bir sahte çerçeveyi kullanarak DinnersController'ımızın sanal bir kullanıcı adını ararken kullanabileceği bir User nesnesini dinamik olarak oluşturabiliriz. Bu, testimizi çalıştırdığımızda null başvuru oluşmasını önler.

ASP.NET MVC ile kullanılabilecek birçok .NET sahte çerçevesi vardır (bunların listesini burada görebilirsiniz: http://www.mockframeworks.com/).

İndirildikten sonra Moq.dll derlemesine NerdDinner.Tests projemize bir başvuru ekleyeceğiz:

İnek Akşam Yemeği gezinti ağacının ekran görüntüsü. Moq vurgulanır.

Ardından test sınıfımıza kullanıcı adını parametre olarak alan ve ardından DinnersController örneğindeki User.Identity.Name özelliğini "taklit eden" bir "CreateDinnersControllerAs(username)" yardımcı yöntemi ekleyeceğiz:

DinnersController CreateDinnersControllerAs(string userName) {

    var mock = new Mock<ControllerContext>();
    mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
    mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);

    var controller = CreateDinnersController();
    controller.ControllerContext = mock.Object;

    return controller;
}

Yukarıda, MVC'nin User, Request, Response ve Session gibi çalışma zamanı nesnelerini kullanıma sunma amacıyla Denetleyici sınıflarına ASP.NET bir ControllerContext nesnesi taklit eden bir Sahte nesne oluşturmak için Moq kullanıyoruz. ControllerContext üzerindeki HttpContext.User.Identity.Name özelliğinin yardımcı yöntemine geçirdiğimiz kullanıcı adı dizesini döndürmesi gerektiğini belirtmek için Sahte üzerinde "SetupGet" yöntemini çağırıyoruz.

İstediğiniz sayıda ControllerContext özelliği ve yöntemiyle dalga geçebiliriz. Bunu göstermek için Request.IsAuthenticated özelliği için bir SetupGet() çağrısı da ekledim (aslında aşağıdaki testler için gerekli değildir; ancak bu, İstek özellikleriyle nasıl dalga geçebileceğinizi göstermeye yardımcı olur). İşiniz bittiğinde, yardımcı yöntemimizin döndürdüğü DinnersController öğesine ControllerContext sahtesinin bir örneğini atarız.

Artık farklı kullanıcılar içeren Düzenleme senaryolarını test etmek için bu yardımcı yöntemi kullanan birim testleri yazabiliriz:

[TestMethod]
public void EditAction_Should_Return_EditView_When_ValidOwner() {

    // Arrange
    var controller = CreateDinnersControllerAs("SomeUser");

    // Act
    var result = controller.Edit(1) as ViewResult;

    // Assert
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
}

[TestMethod]
public void EditAction_Should_Return_InvalidOwnerView_When_InvalidOwner() {

    // Arrange
    var controller = CreateDinnersControllerAs("NotOwnerUser");

    // Act
    var result = controller.Edit(1) as ViewResult;

    // Assert
    Assert.AreEqual(result.ViewName, "InvalidOwner");
}

Ve şimdi testleri çalıştırdığımızda şu testleri geçerler:

Yardımcı yöntemi kullanan birim testlerinin ekran görüntüsü. Testler geçti.

UpdateModel() senaryolarını test etme

Düzenle eyleminin HTTP-GET sürümünü kapsayan testler oluşturduk. Şimdi Düzenle eyleminin HTTP-POST sürümünü doğrulayan bazı testler oluşturalım:

//
// POST: /Dinners/Edit/5

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit (int id, FormCollection collection) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    try {
        UpdateModel(dinner);

        dinnerRepository.Save();

        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch {
        ModelState.AddModelErrors(dinner.GetRuleViolations());
        
        return View(new DinnerFormViewModel(dinner));
    }
}

Bu eylem yöntemini desteklememiz için ilginç yeni test senaryosu, Denetleyici temel sınıfında UpdateModel() yardımcı yönteminin kullanımıdır. Form-post değerlerini Dinner nesne örneğimize bağlamak için bu yardımcı yöntemi kullanıyoruz.

Aşağıda UpdateModel() yardımcı yönteminin kullanması için form olarak gönderilen değerleri nasıl sağlayabildiğimizi gösteren iki test yer almaktadır. Bunu yapmak için bir FormCollection nesnesi oluşturup dolduracağız ve ardından bunu Denetleyicideki "ValueProvider" özelliğine atayacağız.

İlk test, başarılı bir kaydetmede tarayıcının ayrıntılar eylemine yeniden yönlendirildiğini doğrular. İkinci test, geçersiz giriş gönderildiğinde eylemin düzenleme görünümünü bir hata iletisiyle yeniden görüntülediğini doğrular.

[TestMethod]
public void EditAction_Should_Redirect_When_Update_Successful() {

    // Arrange      
    var controller = CreateDinnersControllerAs("SomeUser");

    var formValues = new FormCollection() {
        { "Title", "Another value" },
        { "Description", "Another description" }
    };

    controller.ValueProvider = formValues.ToValueProvider();
    
    // Act
    var result = controller.Edit(1, formValues) as RedirectToRouteResult;

    // Assert
    Assert.AreEqual("Details", result.RouteValues["Action"]);
}

[TestMethod]
public void EditAction_Should_Redisplay_With_Errors_When_Update_Fails() {

    // Arrange
    var controller = CreateDinnersControllerAs("SomeUser");

    var formValues = new FormCollection() {
        { "EventDate", "Bogus date value!!!"}
    };

    controller.ValueProvider = formValues.ToValueProvider();

    // Act
    var result = controller.Edit(1, formValues) as ViewResult;

    // Assert
    Assert.IsNotNull(result, "Expected redisplay of view");
    Assert.IsTrue(result.ViewData.ModelState.Count > 0, "Expected errors");
}

Test Wrap-Up

Birim testi denetleyicisi sınıflarında yer alan temel kavramları ele aldık. Uygulamamızın davranışını doğrulayan yüzlerce basit testi kolayca oluşturmak için bu teknikleri kullanabiliriz.

Denetleyici ve model testlerimiz gerçek bir veritabanı gerektirmediğinden, son derece hızlı ve çalıştırılması kolaydır. Saniyeler içinde yüzlerce otomatik test gerçekleştirebilecek ve yaptığımız bir değişikliğin bir şeyi bozup bozmadığıyla ilgili hemen geri bildirim alacağız. Bu, uygulamamızı sürekli olarak geliştirmemiz, yeniden düzenlememiz ve iyileştirmemiz için bize güven sağlamaya yardımcı olur.

Testi bu bölümdeki son konu olarak ele aldık; ancak test, geliştirme sürecinin sonunda yapmanız gereken bir şey olduğundan değil! Aksine, geliştirme sürecinizde mümkün olduğunca erken otomatikleştirilmiş testler yazmanız gerekir. Bunu yaptığınızda, geliştirirken anında geri bildirim alabilir, uygulamanızın kullanım örneği senaryoları hakkında düşünceli düşünmenize yardımcı olur ve uygulamanızı temiz katmanlama ve bağlamayı göz önünde bulundurarak tasarlamanız için size yol gösterir.

Kitabın sonraki bir bölümünde Test Temelli Geliştirme (TDD) ve bunun ASP.NET MVC ile nasıl kullanılacağı ele alınacaktır. TDD, sonuçta elde edilen kodunuzun karşıladığı testleri ilk kez yazdığınız yinelemeli bir kodlama uygulamasıdır. TDD ile her özelliğe başlamak üzere olduğunuz işlevselliği doğrulayan bir test oluşturursunuz. Önce birim testini yazmak, özelliği ve nasıl çalışması gerektiğini net bir şekilde anlamanıza yardımcı olur. Yalnızca test yazıldıktan (ve başarısız olduğunu doğruladıktan) sonra testin doğruladığınız gerçek işlevselliği uygularsınız. Özelliğin nasıl çalışması gerektiğiyle ilgili kullanım örneğini düşünmek için zaten zaman harcadığınız için gereksinimleri ve bunları en iyi nasıl uygulayabileceğinizi daha iyi anlayacaksınız. Uygulamayı tamamladığınızda testi yeniden çalıştırabilir ve özelliğin düzgün çalışıp çalışmadığına ilişkin anında geri bildirim alabilirsiniz. 10. Bölümde TDD'ye daha fazla değineceğiz.

Sonraki Adım

Bazı son özet açıklamaları.