Daha önceki yazımızda Unit Test Nedir Nasıl Yazılır konusuna değinmiştik ve basit bir console uygulaması ve onun unit test metotlarının bulunduğu test projemizi yazmıştık. O örneğimizde herhangi bir database veya kendi oluşturduğumuz data modelleri vs yoktu 4 işlem yapan bir projeydi. Bu yazımızda Db operasyonları olan bir projede unit test yazmak istesek ne yapardık bu soruya cevap arıyor olacağız.
Bir UserRepository class'ımız olsun ve bu repository için unit test metotları yazıyor olalım. Peki ama test metotlarını yazarken nasıl bir yol izleyeceğiz ? Her bir test case'i için gidip database saçma sapan fake kayıtlar atıp CRUD işlemleri yapmamamız gerekir. Bu gibi durumlar için mocking dediğimiz "alaycı" veya "sahte" kayıtlar oluşturmamızı sağlayan library'ler bulunmakta. Bu library'lerden Moq'u kullanarak sahte nesneler üreterek UserRepository için basit bir test projesi yazacağız.
Moq
Moq .Net tarafında unit test yazmada kullanabildiğimiz bir mocking kütüphanesidir. Testlerimiz için sahte nesneler üreterek normal projemizde ki case'leri test etmemizi sağlar.
Örnek projemiz için öncelikle bir tane UserSample adımda Console Application oluşturalım ve içerisine User.cs ve IUserRepository.cs class'larını aşağıdaki gibi tanımlayalım.
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public interface IUserRepository
{
IList<User> GetAll();
User GetById(int userId);
void Insert(User user);
void Update(User user);
void Delete(int Id);
}
Şimdi ise UserSample.Test adında test projemizi oluşturalım ve UserSample projemizi referans olarak test projemize ekleyelim. Sonrasında test projemize Nuget üzerinden Moq kütüphanesini kuralım.
Tools > Nuget Package Manager > Package Manager Console > PM> Install-Package Moq
Kurulum işlemi tamamlandıktan sonra UserRepositoryTest adında bir class oluşturalım.
İlk olarak User Repository için gerekli olan setup işlemlerini yapalım. Setup işlemi kısaca repository'nin içerisindeki metotların sahte objelerle işlemleri yapmasını sağlayacak kodları yazmak diyebiliriz. Mocking setup ile ilgili kodlarımız aşağıdaki gibi olacaktır.
[TestClass]
public class UserRepositoryTest
{
public readonly IUserRepository MockUserRepository;
public UserRepositoryTest()
{
// Test metotları genelinde kullanacağımız User listesi
var userList = new List<User>
{
new User {Id=1,FirstName="User1",LastName="User1LastName" },
new User {Id=2,FirstName="User2",LastName="User2LastName" },
new User {Id=3,FirstName="User3",LastName="User3LastName" }
};
// Mock the Products Repository using Moq
var mockUserRepository = new Mock<IUserRepository>();
// GetAll metodu için setup işlemi
mockUserRepository.Setup(mr => mr.GetAll()).Returns(userList);
// GetById metodu için setup işlemi
mockUserRepository.Setup(mr => mr.GetById(It.IsAny<int>())).Returns((int i) => userList.Single(x => x.Id == i));
// Insert için setup işlemi
mockUserRepository.Setup(mr => mr.Insert(It.IsAny<User>())).Callback(
(User target) =>
{
userList.Add(target);
});
// Update için setup işlemi
mockUserRepository.Setup(mr => mr.Update(It.IsAny<User>())).Callback(
(User target) =>
{
var original = userList.Where(q => q.Id == target.Id).Single();
if (original == null)
{
throw new InvalidOperationException();
}
original.FirstName = target.FirstName;
original.LastName = target.LastName;
});
// Test metotlarından erişilebilmesi için global olarak tanımladığımız MockUserRepository'e yukarıdaki setup işlemlerini atıyoruz
this.MockUserRepository = mockUserRepository.Object;
}
}
Yukarıda bulunan kodlar kısaca şunları söylemekte;
Arkadaş senin IUserRepository diye CRUD işlemlerinin yapıldığı bir class'ın var ve bu class içerisinde bulunan GetAll, GetById, Insert, Update, Delete metotları için tanımlanan mocking veya kandırmaca işlemleri yukarıdaki gibidir. Sen Database üzerinden bu işlemleri yapmak yerine rahatça userList array'i üzerinden bu işlemleri yapabilirsin.
Buraya kadar her şey OK ise aşağıdaki gibi sırasıyla test metotlarımızı yazalım.
GetAll metodunu çağırarak bize veri döndüğünü gösteren test metodu.
[TestMethod]
public void GetAll_Than_Check_Count_Test()
{
var expected = this.MockUserRepository.GetAll().Count;
Assert.IsNotNull(expected);// Test not null
Assert.IsTrue(expected > 0);// Test GetAll returns user objects
}
GetById metodu için doğru objeyi return edip etmediği durumu için test metodu.
[TestMethod]
public void GetById_Than_Check_Correct_Object_Test()
{
var actual = new User { Id = 2, FirstName = "User2", LastName = "User2LastName" };
var expected = this.MockUserRepository.GetById(2);
Assert.IsNotNull(expected); // Test is not null
Assert.IsInstanceOfType(expected, typeof(User)); // Test type
Assert.AreEqual(actual.Id, expected.Id); // test correct object found
}
Insert işleminden sonra GetAll metodundan dönen object sayısı doğrumu testi
[TestMethod]
public void Insert_User_Than_Check_GetAll_Count_Test()
{
var actual = this.MockUserRepository.GetAll().Count + 1;
var user = new User { Id = 4, FirstName = "User4", LastName = "User4LastName" };
this.MockUserRepository.Insert(user);
var expected = this.MockUserRepository.GetAll().Count;
Assert.AreEqual(actual, expected);
}
GetById metoduna hatalı bir Id ile çağrım yapıldığında Exception döneceği durumu için test metodu.
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]//Eğer beklediğimiz bir exception var ise bu şekilde tanımlayabiliriz
public void GetyId_With_Undefined_Id_Than_Exception_Occurred_Test()
{
var expected = this.MockUserRepository.GetById(It.IsAny<int>());
}
Update işlemi sonrasında GetById yapılarak dönen nesnede bulunan değerler doğrumu test metodu.
[TestMethod]
public void Ipdate_User_Than_Check_It_Is_Updated_Test()
{
var actual = new User { Id = 2, FirstName = "User2_Updated", LastName = "User2LastName_Updated" };
this.MockUserRepository.Update(actual);
var expected = this.MockUserRepository.GetById(actual.Id);
Assert.IsNotNull(expected);
Assert.AreEqual(actual.FirstName, expected.FirstName);
Assert.AreEqual(actual.LastName, expected.LastName);
}
Test metotlarını yazdıktan sonra Run All Tests diyerek testlerimizi çalıştırıp success fail durumlarını görebiliriz.
Bu yazımızda bir Mocking kütüphanesi olan Moq kullanarak basitçe bir Unit Test projesi hazırladık ve halen daha çok fazla önemsenmese de unit Test dev-ops süreçlerinin olgunlaşmasıyla artık bir çok firma için "IsMust" zorunlu hale gelmiş bir kuraldır ve daha önceki yazılarda da bahsettiğim üzre Unit Test yazıyor olmak artık interview'larda beklenen bir durum haline gelmiştir.