# Birim Testleri

### 1. Amaç

Bu belgenin amacı .NET uygulamalarında geliştirilen birim testlerine ait pratikleri tanıtmaktır.

**Kaynaklar**

* <http://www.extremeprogramming.org/rules/unittests.html>

### 2. Giriş

Bir birim testi, bir kod biriminin (genellikle bir nesnenin veya modülün belli bir metodunun) davranışlarını test eden ve doğrulayan özel bir metottur. Bu kod biriminin olası tüm davranışları için testler oluşturulduğunda, birimin sistemin diğer bölümlerinden bağımsız bir biçimde doğru çıktıları sağladığı doğrulanmış olur.

Birim testleri, uygulama değiştirildiğinde, test ettikleri birimlerin işlevlerini doğru yerine getirmeye devam edip etmediğini kontrol etmeyi sağlarlar. Ayrıca test edilen metotların çalışma mantıklarını da belgelemeye yararlar.

Birim testleri olan uygulamalarda [Refactoring - Yeniden Düzenleme](https://bekoturko.gitbook.io/yazilim-gelistirme-wiki/yazilim-gelistirme-kilavuzu/refactoring) teknikleri güvenli bir şekilde uygulanabilir. İşlevlerin doğru çalıştığından emin olunarak yazılımın yapısı istendiği gibi yeniden düzenlenerek kod kalitesi ve organizasyonu iyileştirilebilir.

#### 2.1 Test Türleri

Birim testleri bir sistemin test edilme yöntemlerinden sadece birisidir.

| **Unit Testing**                | **Birim Testleri**   | *Whitebox Testing* |
| ------------------------------- | -------------------- | ------------------ |
| Integration Testing             | Entegrasyon Testleri |                    |
| System Testing                  | Sistem Testleri      |                    |
| Acceptance / Functional Testing | Kabul Testleri       | *Blackbox Testing* |

#### 2.2 Yaklaşımlar

| Name                                                       | Value                                        |
| ---------------------------------------------------------- | -------------------------------------------- |
| Birim testleri                                             | → **ne** yapılacağını belirler.              |
| Test Driven Development (Test Güdümlü Programlama)         | → testin **ne zaman** yazılacağını belirler. |
| Behavior Driven Development (Davranış Güdümlü Programlama) | → testin **nasıl** yazılacağını belirler.    |

Her biri ayrı ayrı da kullanılabilse de, en iyi sonuçlar için bu yöntemler bir arada kullanılmalıdır. Zaten beraber uygulanmaya da çok uygundurlar.

**TDD (Test Driven Development)**\
Test Güdümlü Programlama: İşlevin geliştirilmesinin önce testlerinin yazılarak sağlandığı yöntem.\
Bkz. [Test Driven Development (TDD)](https://bekoturko.gitbook.io/yazilim-gelistirme-wiki/test-gelistirme/birim-testleri/test-driven-development-t.d.d.)

**BDD (Behaviour Driven Development)**\
Sistemin davranışlarına odaklanan bir geliştirme yöntemidir. Bu yaklaşımda testler implementasyon yerine beklenen davranışı (fonksiyonalite) test etmeye yoğunlaşır. Bkz. [Behaviour Driven Development (BDD)](https://bekoturko.gitbook.io/yazilim-gelistirme-wiki/test-gelistirme/birim-testleri/behaviour-driven-development-bdd)

Kullanılan terminoloji iş beklentilerini karşılamaya yönelik oluşturulur ve iş uzmanları ile geliştiriciler arasında oluşan iletişim boşluğunu da gidermeyi hedefler.

Tasarımı, beklenen davranış üzerinden giderek düşünürsek ve organize edersek, iş ihtiyaçlarının mantıksal yerleşimi testlere yansır, test sınıfları fonksiyonaliteyi test eder hale gelir.

### 3. Bir Testin Anatomisi

#### 3.1 Örnek Birim Testi

```csharp
[Test]
public void UpdateMember_ValidMember_ReturnSuccess()
{
    //Arrange
    Member member = new Member();
    Member existingMember = null;
    bool isUpdated = true;

    accountRepository.Setup(x => x.GetMemberByEmailAddress(member.EmailAddress)).Returns(existingMember);
    accountRepository.Setup(x => x.UpdateMemberAccountInfo(member)).Returns(isUpdated);

    //Act
    var result = accountService.UpdateMemberAccountInfo(member);

    //Assert
    result.ShouldEqual(true);
}
```

#### 3.2 Bir Birim Testinin Organizasyonu Hakkında Öneri: A/A/A

Bir birim testi 3 temel bölümden oluşur.\
Bu bölümler yorum ifadeleriyle birbirinden ayrılabilir.

| **Name**    | Value                                                             |
| ----------- | ----------------------------------------------------------------- |
| **Arrange** | Test edilecek işlevin çalıştırılabilmesi için gerekli kurulumlar. |
| **Act**     | İşlevin çalıştırıldığı bölüm.                                     |
| **Assert**  | Sonucun kontrolü.                                                 |

**Arrange**\
Bu bölümde test edilecek birime, koşullara ve sonuçlara ait kurulumlar yapılır.

```csharp
//Arrange
Member member = new Member();
Member existingMember = null;
bool isUpdated = true;
```

*Setup*

Bir metodun belli parametrelerle çalıştırılması sonucunda döndürdüğü sonucu belirlemek için kullanılır.

```csharp
accountRepository.Setup(x => x.GetMemberByEmailAddress(member.EmailAddress)).Returns(existingMember);
accountRepository.Setup(x => x.UpdateMemberAccountInfo(member)).Returns(isUpdated);
```

**Act**\
Bu bölümde test edilecek birim istenen koşullarla çalıştırılır ve sonuç elde edilir.

```csharp
//Act
var result = accountService.UpdateMemberAccountInfo(member);
```

**Assert**\
Bu bölümde çalıştırılan birimin ürettiği sonuç kontrol edilerek beklentileri karşılaması sağlanır.\
Testin geçip geçmediği bu bölümde belli olur. Bu nedenle beklenen sonuçlar için kontrol ifadeleri kapsamlı bir şekilde tanımlanmalıdır.

```csharp
//Assert
result.ShouldEqual(true);
```

*ASP.NET MVC* yapısı kullanılıyorsa ve test edilecek birim Controller katmanında ise işlem sonucunda arayüzle ilgili sonuçlar kontrol edilir.

*Verify*\
Bir metodun çalışıp çalışmadığını kontrol etmek için `Verify` metodu kullanılır.

```csharp
logger.Verify(x => x.LogException(It.IsAny<string>()), Times.Once);
accountRepository.Verify(x => x.GetMemberAccountInfo(memberId), Times.Never);
```

Bir sınıfa ait özelliğe atama yapılıp yapılmadığı `SetValue` metodu ile kontrol edilir.

```csharp
session.Verify(x => x.SetValue("CheckoutInfo", It.IsAny<CheckoutInfo>()), Times.Once);
```

Mock kurulumu yapılan bir metot sonuç döndürüp akışın sürmesini sağlıyorsa, kurulum yapılan metodun çalıştığını `Verify` etmeye gerek kalmaz.

#### 3.3 SetUp / TearDown metotları

**SetUp**\
Bir test sınıfının içerdiği tüm testlerde kullanılacak kurulumlar için `SetUp` metodu kullanılır.\
`SetUp` metodu her bir test çalışmaya başlamadan önce çalışır.

```csharp
#region setup

Mock<IAccountRepository> accountRepository;
Mock<IErpService> erpService;
Mock<ILogger> logger;
Mock<ISettings> settings;

AccountService accountService;

[SetUp]
public void Initialize()
{
    accountRepository = new Mock<IAccountRepository>();
    erpService = new Mock<IErpService>();
    logger = new Mock<ILogger>();
    settings = new Mock<ISettings>();

    accountService = new AccountService(       
        accountRepository.Object,
        erpService.Object,
        logger.Object,
        settings.Object);
}

#endregion
```

Bu metotta genelde test edilecek sınıfa kullanacağı arayüzlerin Mock versiyonları enjekte edilir.\
Böylece bu arayüzlerin istenen metotlarının mock kurulumları ile istendiği gibi sonuç üretmesi sağlanabilir.

**TearDown**\
Bir test sınıfının içerdiği tüm testlerde kullanılacak sonlandırma işlemleri için `TearDown` metodu kullanılır.\
`TearDown` metodu her bir testin çalışması tamamlandıktan sonra çalışır.

```csharp
[TearDown]
public void TearDown()
{
    accountRepository.Dispose();
}
```

### 4. Birim Testlerinin İsimlendirilmesi

bkz. [Birim Testi İsimlendirme Kılavuzu](https://bekoturko.gitbook.io/yazilim-gelistirme-wiki/yazilim-gelistirme-kilavuzu/isimlendirme/birim-testi-isimlendirme-kilavuzu)

### 5. Test Geliştirme Pratikleri

#### 5.1 Prensipler

bkz. [Birim Testi Geliştirme Prensipleri](https://bekoturko.gitbook.io/yazilim-gelistirme-wiki/test-gelistirme/birim-testleri/birim-testi-gelistirme-prensipleri)

#### 5.2 Testleri Paralel Çalıştırmak

Testlerin hızlı çalışması önemlidir, çünkü testler hızlı çalışmıyorsa, çalıştırılmamaya başlanır. Testler sık çalıştırılmazsa refactoring yapma eğilimi de azalır (çünkü programcının kendine olan güveni azalır).

Birim testlerini paralel çalıştırmak zamandan kazandırır. Testleri paralel çalıştırmak için *NUnit 3.0* ve üstü versiyonlarda test sınıflarına (**TestFixture**) `[Parallelizable(ParallelScope.Fixtures)]` etiketi eklenir.

**Vaka:** Foo projesinde test sayısı 800’ü geçince testlerin çalışma süresi 10 saniyeye çıktı. Paralel çalıştırma sonucunda 2.600 civarında test ortalama 6,50 saniye gibi bir sürede çalışabiliyor.

### 6. Birim Testlerinde Sorunlar ve Çözümler

#### 6.1 Test Yazmanın Uzun Sürmesinden Yakınan Geliştiriciler İçin Açıklamalar

**1. Arrange bölümü çok uzun**

* Önce **Act** kısmını yaz, ihtiyacın kadar **Setup** ekle. (Test edilen metodu olabildiğince *Black Box* olarak düşün. Bu şekilde düşünmek metodun soyutlama seviyesini belirlemekte de yararlıdır.)
* İhtiyaç olmadığı sürece, **Mock** sınıfların **Setup** tanımlarına verilen parametre ve dönüş tipi sınıflarının özelliklerine atama yapma ya da sadece yapılması gerektiği kadar yap. (Kurulumların çalışması için bu sınıfları sadece *new* ile oluşturmak ve nesnenin referansını kullanmak yeterlidir.)
* Birden fazla testte kullanılan setup bölümleri ayrı bir metoda alınıp ortak olarak kullanılabilir. (Parametreler için factory metotlar oluşturulabilir.)
* Yine de çok fazla metodu setup yapmak gerekiyorsa bu durum, test edilecek metodun birden fazla sorumluluğu yerine getirmeye çalıştığını gösterir. Bu sorumlulukların soyutlama seviyeleri büyük olasılıkla farklıdır. Alt seviye sorumlulukları yerine getiren adımlar gruplanıp başka bir sınıfa sorumluluk olarak taşınabilir. Bu durumda taşınan metodun setup'ını yapmak yeterli olur. İşler birden fazla sınıfa dağılacak şekilde anlamsal bir bütünlüğe sahipse böyle yap.

**2. Assert satırları fazla**

* *FluentAssertions* veya benzeri, *BDD (Behaviour-Driven Development)* odaklı, zincir metotlar ile çalışabilen bir Assert kütüphanesi kullan.
  * **Assert** bölümü **testin** amacıdır, **en önemli kısmıdır**.
  * Testin neden yazıldığı, hangi sonuçların beklendiği **açık**, **anlaşılır** ve **kısa** olmalı.
  * FluentAssertions ile cümle okur gibi okunabilen, BDD odaklı, daha az satırdan oluşan Assert ifadeleri yazılabiliyor.

**3. Çok fazla test oluşturmam gerekiyor**

* Sadece belirli koşulları ya da sonuçları değişen testleri yeniden yazma. Parametrik testler oluşturarak, değişen bölümleri parametre olarak tanımla. (**TestCase** kullan.)

#### 6.2 Sık Sorulan Sorular

**1. Setup çalışmıyor**

* Setup'a sağlanan parametreyi kontrol et. Parametrenin test edilen birimde (metotta)
  * Değer tipi parametre ise aynı değerle
  * Referans tipi ise aynı referansla çalıştırıldığından emin ol.
  * Aynı değilse setup başarısız olur, test de (çoğu zaman) geçmez.
* Setup’a sağlanan referans tipi parametre, test edilen metodun içinde farklı bir referansla oluşturuluyorsa (örneğin `new` anahtar kelimesi ile) setup başarısız olur. Bu durumda yaşanan genel yanılgı şudur: “Parametre sınıfı için tüm özellikleri beklenen değerlere sahip bir nesne oluşturmak bu işi çözebilir.” Hayır, çözmez. Sınıfın özelliklerine ait değerler aynı bile olsa referansı farklı olacaktır. Bu durumun olası çözümleri:
  * Test edilen metodun oluşturduğu ve kullandığı nesne için bir factory metot oluşturulur. Metot nesneyi factory aracılığıyla oluşturur ve kullanır. Testte factory metodunun kurulumu yapılır ve kullanılacak parametre bu metodun dönüşünde sağlanır. İmplementasyon ile test aynı yöntemi kullanarak parametreyi oluşturacağından setup çalışır.
  * Setup metoduna bir nesne vermek yerine `It.IsAny<ParameterType>()` ifadesi ile parametre sağlamak. Bu kuruluma göre aynı tipte herhangi bir nesne parametre olarak kullanılırsa, setup başarılı olur. (Bu kullanım önerilmez. *Bkz. Not - It.IsAny Kullanımı*)

*Not - It.IsAny Kullanımı*\
Setup yapılan metotlarda parametreler için `It.IsAny` tipini kullanmak sakıncalıdır.

* **SORUN 1:** Metot içindeki çalışma akışında parametre için doğru nesne kullanıldığından emin olunamaz. Test fonksiyonaliteyi tam anlamıyla test ediyor olmaz.
* **SORUN 2:** `It.IsAny` kullanmak gerekmesi bir soruna işarettir. İmplementasyon içinde `new` anahtar kelimesi ile sınıf oluşturulduğunu gösterir. `new` ile sınıf ancak Factory metotlarda oluşturulmalı. Bu durumlarda Factory metot kullanılmalıdır. Yeni oluşturulan sınıfın istenen değerlerle oluşturulup oluşturulmadığı (verilen değerler ve beklenen varsayılan kurulumlarıyla) Factory metodun testinde kontrol edilir.

**2. .NET Framework veya 3. parti bir bileşene ait metot beklendiği gibi çalışmıyor**

* `Url`, `ControllerContext`, `HttpContext`, `File`, `Session`, vb. Bileşenler birim testi ortamında beklendiği gibi çalışmaz. (Özellikle bir `HttpContext` nesnesine ihtiyaç duyan Web metotları)
* Bu durumlarda gevşek-eşleştirme (loose-coupling) sağlanmalı. Wrapper sınıflar oluşturulmalı ve istemci sınıf bu framework bileşenlerine wrapper sınıfların arayüzleri aracılığıyla ulaşmalıdır.
* Wrapper metotlar istemcinin çağıracağı hedef metot ile aynı imzaya sahip vekil metotlardır.
* Wrapper → Kullanılan bileşenin metotları ile aynı imzaya sahip arayüz oluşturulur, bu arayüzü uygulayan wrapper sınıfı, metotların implementasyonlarında hedef bileşenin metotlarını çalıştırır ve sonucu döndürür.)
* Örnek: `ISession` (arayüz)
  * `SessionWrapper` (implementasyon)
  * `RedisSessionWrapper` (implementasyon)
* Sıkı sıkıya eşleştirmekten kurtulmanın faydası: Örneğin `Session` işlemleri için kullanılan `ISession` arayüzünün kullanılan implementasyonu istendiği zaman kolayca değiştirilebilir. ASP.NET `Session` nesnesini saran `SessionWrapper` yerine Redis kullanan `RedisSessionWrapper` kullanımına geçilebilir ve istemci kodu ve testleri bu durumda değişmek zorunda kalmaz.

#### 6.3 Birim Testleri ve Teknik Olmayan Proje Paydaşları

Geliştirme sürecinde birim testi yazıldığını müşteriye, proje yöneticisine bildirmeyin. Testleri geliştirmenin doğal bir parçası olarak düşünün. Yeterli bilgiye sahip olmayan kişiler, test yazmanın geliştirmeyi uzatacağı, gereksiz olduğu gibi önyargılara sahip olabilir (genelde böyle olur). Dikkate almayın. Testlerin geliştirme sürecinin doğal ve gerekli bir aşaması olduğunu içselleştirememiş organizasyonlarda, bunu açıkça belirtmeden testlerinizi yazmanız daha iyi olabilir.

**Öneri:** Test Driven Development (TDD) yöntemi ile geliştirme yaparsanız, işlevin kodlamasını bitirmiş ama testlerini henüz yazmamış bir durumda kalmazsınız. Önce testi yazarsınız ve uygulama kodu da doğal olarak oluşturulmuş olur. Geliştirme bittiğinde testler de, uygulama kodu da tamamlanmış olur.

### 7. Test Assert Örnekleri

Bu bölümde gösterilen örneklerde `FluentAssertions` kütüphanesi kullanılmıştır.

#### 7.1 Web Testleri

**Yönlendirme Testi**

```csharp
//Assert
result.Should().BeRedirectToRouteResult().WithController(ControllerNames.Friend).WithAction(FriendControllerActionNames.Index);
```

**ViewResult ve Model Testi**

```csharp
//Assert
result.Should().BeViewResult().WithViewName(ViewNames.Index).ModelAs<FriendListViewModel>).Friends.Should().BeEquivalentTo(friends.Items);
result.As<ViewResult>().Model.As<FriendListViewModel>().PagingInfo.Should().BeEquivalentTo(pagingInfo);
```

**JsonResult ve Data Testi**

```csharp
//Assert
var resultModel = result.Should().BeOfType<JsonResult>().Which.Data.As<ProfileActionResult>();
resultModel.IsSuccess.Should().Be(expectedModel.IsSuccess);
resultModel.ProfileId.Should().Be(expectedModel.ProfileId);
resultModel.Message.Should().Be(expectedModel.Message);
resultModel.UrlToRedirect.Should().Be(expectedModel.UrlToRedirect);
```

**TempData ve ViewData Testi**

```csharp
//Assert
result.Should().BeRedirectToRouteResult().WithController(ControllerNames.Friend).WithAction(FriendControllerActionNames.Index);
var resultMessage = controller.TempData[nameof(DictionaryKey.ResultMessage)].As<ResultMessage>();
resultMessage.MessageType.Should().Be(MessageType.Success);
resultMessage.Message.Should().NotBeNullOrEmpty();
controller.ViewData["PageSize"].ShouldEqual(5);
```

**ASP.NET MVC ModelState Kontrolü**\
Controller testlerinde `ModelState` nesnesinin geçersiz olduğu, hata içerdiği durumları kontrol etmek için aşağıdaki Assert ifadesi kullanılabilir.

```csharp
//Assert
accountController.ModelState.GetErrorCount("").Should().NotBe(0);
```

`GetErrorCount` metodu `ModelState` sınıfı için oluşturulan bir extension metottur:

```csharp
public static int GetErrorCount(this ModelStateDictionary dictionary, string key)
{
    if (dictionary.Keys.Count == 0)
    {
        return 0;
    }

    return dictionary[key].Errors.Count;
}
```

Model validasyonu testler çalışırken devreye girmediğinden `ModelState` nesnesini hatalı duruma getirmek ve bu durumu test etmek gerekebilir. Bu durumda `ModelState` hatasını elle oluşturmak amacıyla Act bölümünden önce, test edilecek Controller için `AddModelError` metodu kullanılabilir.

```csharp
accountController.ModelState.AddModelError("", "");
```

#### 7.2 Validasyon Testleri

**FluentValidation Testleri**\
FluentValidation testleri genelde sadece Assert bölümlerinden oluşan kısa testlerdir. Bu testlerde birden fazla durum aynı koşul için aynı metotta test edilebilir.

Validasyon testlerinin aşağıdaki gibi iki bölüme ayırılması testlerin organizasyonu açısından yararlıdır.

* Invalid conditions
* Valid conditions

```csharp
[Test]
public void Validate_ValueEmpty_HaveError()
{
    validator.ShouldHaveValidationErrorFor(model => model.CardHolderName, string.Empty);
    validator.ShouldHaveValidationErrorFor(model => model.CardHolderName, null as string);
    validator.ShouldHaveValidationErrorFor(model => model.CardNumber, string.Empty);
    validator.ShouldHaveValidationErrorFor(model => model.CardNumber, null as string);
    validator.ShouldHaveValidationErrorFor(model => model.ExpireDateMonth, 0);
    validator.ShouldHaveValidationErrorFor(model => model.ExpireDateYear, 0);
}
```

Validasyon testlerinde validasyonu ihlal eden koşullar kontrol edildiği gibi, kabul edilebilir değerlerin de bir ihlale yol açmadığı da kontrol edilmelidir.

```csharp
[Test]
public void Validate_ValueNotEmpty_DoNotHaveError()
{
    validator.ShouldNotHaveValidationErrorFor(model => model.CardHolderName, "test kişisi");
    validator.ShouldNotHaveValidationErrorFor(model => model.CardNumber, "0000111122223333");
    validator.ShouldNotHaveValidationErrorFor(model => model.Csc, "000");
    validator.ShouldNotHaveValidationErrorFor(model => model.ExpireDateMonth, 1);
    validator.ShouldNotHaveValidationErrorFor(model => model.ExpireDateYear, DateTime.Now.Year);
}
```

Diğer durumlarda kullanılabilecek test isimleri:

* `Validate_ValueNotInRange_HaveError`
* `Validate_ValueLengthNotInRange_HaveError`
* `Validate_ValueFormatInvalid_HaveError`
* `Validate_ValuesDoNotMatch_HaveError`

*Özel kontroller için*

* `Validate_CityEmpty_HaveError`
* `Validate_AddressValueMaxLength_HaveError`

#### 7.3 Hata (Exception) Testleri

Test edilecek işlev bir sonuç döndürmek yerine hata fırlatıyorsa `Assert.That` metodu ile beklenen hata tipi belirtilir ve bu hatanın fırlatılması durumunda test geçmiş olur. Metodun çağrısı ile sonucun kontrolü aynı satırda olduğu için Act ve Assert bölümleri birleşir.

```csharp
[Test]
public void GetAppSettingValueAsString_KeyDoesNotExist_ThrowException()
{
    // Arrange
    var key = ConfigKey.DefaultMailSenderName;

    bool keyExists = false;

    configManager.Setup(x => x.AppSettingsKeyExists(key)).Returns(keyExists);

    // Act - Assert
    Assert.That(() => settingsReader.GetAppSettingValueAsString(key), Throws.Exception.TypeOf<Exception>());
    configManager.Verify(x => x.GetAppSettingsValue(VerifyAny<ConfigKey>()), Times.Never);
}
```

Akışın belli bir aşamasında bir metodun hata fırlatması istenirse, bu metodun Setup işlemi Returns yerine Throws metodu ile yapılır.

```csharp
mailService.Setup(x => x.SendApplicationReceivedMail(memberApplicant)).Throws(new Exception());
```

### 8. Yardımcı Kütüphaneler

#### NUnit

.NET için açık kaynaklı bir birim testi kütüphanesi.

[https://www.nuget.org/packages/NUnit](https://www.nuget.org/packages/NUnit/)

#### Moq (Mocking library)

Mocking, birim testlerinde gerçek uygulama sınıfları yerine bu sınıfların özelliklerine sahip (aynı arayüzleri uygulayan) geçici sınıflar tanımlama ve kullanma yöntemi.

Moq, bir arayüzden mock nesnesi yaratıldığında, çalışma zamanında arayüzü uygulayan bir sınıf oluşturarak bu sınıfın kullanılmasını, bu sınıfın özelliklerinin ve metotlarının beklenen sonuçları verecek şekilde ayarlanmasını sağlar.

<https://www.nuget.org/packages/Moq/>\
<http://code.google.com/p/moq/>

#### FluentAssertions

Daha önce kullanılan basit `UnitTestHelper` sınıfındaki `ShouldEqual`, `ShouldNotEqual` metotları yerine çok daha kapsamlı olan ve BDD yönelimli `FluentAssertions` kütüphanesinin kullanımına geçildi.

[FluentAssertions ](https://fluentassertions.com/)Yararları:

1. Daha anlamlı, cümle yapısına sahip
2. Daha kısa Assert ifadeleri yazılabiliyor
3. Test satır sayısı da azalıyor
4. Assert ifadelerindeki because parametresi aslında ne beklendiğini ve gerekçesini, geçmeyen testlerin çıktılarına daha anlaşılır bir şekilde yansıtmayı sağlıyor.

#### TestDriven.Net

VisualStudio ile **NUnit**’in entegrasyonunu sağlayan, testi debug ile çalıştırmaya ve **NCover** programı ile coverage gösterebilen eklenti.

<http://www.testdriven.net/>

Visual Studio entegrasyonu

[https://www.nuget.org/packages/NUnitTDNet](https://www.nuget.org/packages/NUnitTDNet/)

#### NCrunch (Automated Concurrent Testing Tool)

<https://www.ncrunch.net/>

#### Ninject (Dependency injection library)

Ninject, sınıfların bağımlılıklarını ayarlamaya yarayan bir kütüphane. Arayüzlerle bağlanan sınıfların çalışma zamanında hangi somut sınıflarla çalışacağının belirlenmesini sağlar.

Hangi controller’ın hangi somut sınıfların nesnelerini kullanacağını belirlemek için Ninject kullanılan özel `ControllerFactory` sınıfı, uygulamanın kullandığı `ControllerFactory` olarak `Global.asax` içinde `Application_Start` olayında atanır.

Test için `FluentAssertions.Ioc.Ninject paketi`

[http://ninject.org](http://ninject.org/)
