Repository Pattern CRUD İşlemleri Dışında Bulunan Specific Metotlar İçin Mocking

Bir önceki yazımızda Repository Pattern için Mocking Infrastructure Oluşturma konusuna değinmiştik ve CRUD işlemleri için ortak bir setup yapısı oluşturmuştuk. Peki ya aşağıdaki sorulduğu gibi bir case ile karşılaşırsak;

Soru : Crud metotları dışında sadece o repository'e özel bir metot tanımlamak istersek setup işlemi için nasıl bir yol izlemeliyiz ? 

Örneğin UserRepository için bir önceki örnekte tanımladığımız tanımladığımız All, Get, Insert, Update, Delete metotlarının dışında bir de GetByEmail() adında bir metot gerekli. Bu metot için gidip IRepository içerisine yeni bir metot eklemek ve sonrasında RepositoryBaseTest içerisine setup tanımlaması yapmak doğru olmaz çünkü orası adından da anlaşıldığı üzre Base anlayışına uyan işlemler için sınırlandırılmış bir yer. 

Bu gibi durumlarda IUseRepository adında bir interface tanımlayıp ve UserRepository'yi aşağıdaki gibi modify etmemiz yeterli olacaktır.

    public interface IUserRepository
    {
        User GetByEmail(string email);
    }

public class UserRepository : BaseRepository<int, User>, IUserRepository
    {
        public User GetByEmail(string email)
        {
            throw new NotImplementedException();
        }
    }

Test tarafındaki mocking işlemi için ise IUserRepository interface'ini mock yaparak setup işlemini tamamlayabiliriz.

UserRepositoryTest class'ının son hali aşağıdaki gibidir.

    [TestClass]
    public class UserRepositoryTest : RepositoryBaseTest
    {
        private List<User> _userList;
        private Mock<IRepository<int, User>> _mockRepo;
        private Mock<IUserRepository> _mockUserRepo;

        [TestInitialize]
        public void Setup()
        {
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);

            _mockRepo = new Mock<IRepository<int, User>>();

            // mock common methods
            SetupRepositoryMock<int, User>(_mockRepo, _userList);

            _mockUserRepo = new Mock<IUserRepository>();

            // mock specific method
            _mockUserRepo.Setup(x => x.GetByEmail(It.IsAny<string>()))
                .Returns(new Func<string, User>(
                    email => _userList.Single(x => x.Email == email))
                );
        }

        [TestMethod]
        public void Get_By_Email_Then_Result_OK()
        {
            var userFirst = _mockRepo.Object.All().FirstOrDefault();

            var userByEmail = _mockUserRepo.Object.GetByEmail(userFirst.Email);

            Assert.IsNotNull(userByEmail);
            Assert.AreEqual(userFirst.Email, userByEmail.Email);
            Assert.AreEqual(userFirst.Id, userByEmail.Id);
        }
    }

Yukarıda olduğu gibi ihtiyacımız olan metodu interface aracılığıyla soyutlaştırarak common olan ortak metotlar dışında ayrı olarak mocking işlemi yapabiliriz.

Repository Katmanı için Mocking Infrastructure Oluşturma (Moq Library)

Daha önceki Unit Test yazılarımızda Unit Test Nedir Nasıl Yazılır ve Moq Library Kullanarak Unit Test Yazma konularına değinmiştik. Bu yazımızda ise çokça kullandığımız Repository Pattern CRUD işlemlerinin yapıldığı metotlar için reusable bir mocking yapısı oluşturacağız. 

Öncelikle VS'da RepositoryMocking adında bir proje oluşturalım ve sonrasında projemize Generic Repository ile ilgili tanımlamalarımızı yapalım. İlk olarak IRepository adında bir interface ve database de bulunan tablolardaki unique Id-primary key alanına karşılık gelen generic IUniqueIdentifier interface'ini oluşturalaım. Bu interface'i oluşturmamızdaki amaç her tabloda Id alanı farklı tiplerde olabilir bu nedenle objelerimizi oluştururken IUniqueIdentifier interface'inden implement ederek Id alanı için veri tipini belirteceğiz. Bu bize test metotlarımızı tanımlarken ilgili linq sorgularını oluşturmada yarar sağlayacak.

public interface IUniqueIdentifier<Tkey>
{
    TKey Id { get; set; }
}
 
public interface IRepository<Tkey,TEntity> where TEntity : IUniqueIdentifier<Tkey>
{
    IQueryable<TEntity> All();
    TEntity Get(TKey Id);
    TEntity Add(TEntity entity);
    void Update(TEntity entity);
    void Delete(TEntity entity);
}

Şimdi ise abstract olan ve IRepository den inherit olan BaseRepository class'ını oluşturalım.

    public abstract class BaseRepository<TKey, TEntity> : IRepository<TKey, TEntity> where TEntity : IUniqueIdentifier<TKey>
    {
        public IQueryable<TEntity> All()
        {
            throw new NotImplementedException();
        }

        public TEntity Get(TKey id)
        {
            throw new NotImplementedException();
        }

        public TEntity Add(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Update(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Delete(TEntity entity)
        {
            throw new NotImplementedException();
        }
    }

Mocking işlemi yapacağımızdan metot içlerini doldurmadım ancak tabikide ilgili linq sorgularının yazılmasını gerekir.

BaseRepository tanımlamasını da yaptıktan sonra database de bulunan User tablosu için bir object ve bu tabloya ait UserRepository class'ını oluşturalım. 

    public class User : IUniqueIdentifier<int>
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
    public class UserRepository : BaseRepository<int, User>
    {

    }

Buraya kadar olan kısımda Repository katmanı için gerekli olan her şey hazır. Artık Test projemizi oluşturabiliriz. Solution'a RepositoryMocking.UnitTest adında yeni bir test projesi oluşturalım ve içerisine RepositoryBaseTest adında bir class ekleyelim. Bu class reusable mocking setup işlemlerini yapacağımız class olacak.

 public abstract class RepositoryBaseTest
    {
        public void SetupRepositoryMock<TK, TE>(Mock mockRepo, List<TE> data) where TE : class, IUniqueIdentifier<TK>
        {
            var mock = mockRepo.As<IRepository<TK, TE>>();

            // setup All method
            mock.Setup(x => x.All()).Returns(data.AsQueryable());

            // setup Add method
            mock.Setup(x => x.Add(It.IsAny<TE>()))
                .Returns(new Func<TE, TE>(x =>
                {
                    dynamic lastId = data.Last().Id;
                    dynamic nextId = lastId + 1;
                    x.Id = nextId;
                    data.Add(x);
                    return data.Last();
                }));

            // setup Update method
            mock.Setup(x => x.Update(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data[i] = x;
                }));

            // setup Get method
            mock.Setup(x => x.Get(It.IsAny<TK>()))
                .Returns(new Func<TK, TE>(
                    x => data.Find(q => q.Id.Equals(x))
                ));

            // setup Delete
            mock.Setup(x => x.Delete(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data.RemoveAt(i);
                }));
        }
    }

Üstte bulunan kodlarda BaseRepository de bulunan db için All, Get, Insert, Update ve Delete işlemlerini yapacak olan metotlar için ortak bir setup yapısı oluşturduk ve UserRepository gibi diğer oluşturacağınız repository ler içinde RepositoryBaseTest class'ını kullanabileceğiz. Buda bizi her bir repository için ayrı ayrı setup işlemleri yapmaktan kurtarıyor. IRepository interface'ine yeni bir metot eklemek istediğinizde tekrardan yukarıda yazdığımız SetupRepositoryMock içerisine bu metot için gerekli setup işlemini tanımlayabiliriz. 

Şimdi ise UserRepository için UserRepositoryTest adında bir sınıf oluşturalım ve RepositoryBaseTest class'ını kullanarak mock işlemleri yapalım.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        //db de bulunan tablo yerine geçecek fake tablomuz
        private List<User> _userList;

        //mock user repository
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            //tablomuzun içerisini dolduralım
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);
             
            //mock respository değerini initialize edelim
            _mockRepo = new Mock<IRepository<int, User>>();

            //repositorybasetest class'ını kullarak crud metotlarını için setup işlemlerini yapalım
            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }
    }

UserRepository için setup işlemlerimizi tamamladık. Yukarıdaki işlemler sonrasında elimizde db de bulunan User tablosu yerine geçen bir _userList array'imiz ve bu array üzerinden repository metotlarını setup ettik. Şimdi bir kaç test metodu yazıp kodlarımızı test edelim. UserRepositoryTest class'ının son hali aşağıdaki gibidir.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        private List<User> _userList;
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);

            _mockRepo = new Mock<IRepository<int, User>>();

            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }

        [TestMethod]
        public void Get_All_Count()
        {
            Assert.AreEqual(_userList.Count, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Get_By_Id_Then_Check_Name()
        {
            var item = _mockRepo.Object.FindBy(4);
            Assert.AreEqual("Yeneer", item.FirstName);
        }

        [TestMethod]
        public void Remove_User_Then_Check_Count()
        {
            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Delete(user4);
            Assert.AreEqual(3, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Add_New_User_Then_Check_Count()
        {
            var tempCount = _userList.Count;

            var user5 = new User
            {
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Add(user5);

            Assert.AreEqual(tempCount + 1, _mockRepo.Object.All().Count());
        }
    }

RepositoryPattern için reusable mocking işlemi için hepsi bu kadar. Projenizde UserRepository dışında bulunan diğer repository'ler içinde aynı UserRepository de olduğu gibi generic oluşturduğumuz RepositoryBaseTest'i kullanarak setup işlemini yapıp testlerinizi yazabilirsiniz.

Moq Library Kullanarak Unit Test Yazma

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.

Unit Test Nedir

Unit Test günümüz yazılım dünyasında artık olmazsa olmaz bir kural olarak bütün projelerde yazılması zorunlu hale gelmekte ve mülakatlarda Unit Test yazabiliyorum diyebilmek bile biz developer lar için zamanla ciddi bir bonus oluyor. DevOps açısından düşündüğümüzde Continuous Integration ve Continuous Deployment tool'ları yazmış olduğumuz kodları build edip deploy etmeden önce ilk olarak test metotlarını çalıştırır ve metotlardan herhangi biri fail verdiğinde deploy'u durdurup developer'ı bilgilendirir. Bu akış . Kısaca Unit Test artık Nice to Have olmaktan çıkıp Is Must hale gelmektedir. Bu yazımızda Unit Test nedir, nasıl yazılır gibi sorulara cevap arıyor olacağız.

Unit Test Nedir ?

Unit test geliştirmiş olduğunuz kodu test etmek için yazdığınız kod dur ve esas olarak kodun veya fonksiyonun belirli bir biriminin davranışını kontrol eder ve geliştirmeyi yapan developer tarafından yazılır.

Unit test aynı zamanda kodlarınızı kullanacak diğer developer'lar tarafından okunduğunda ise onların kodları nasıl kullanması gerektiğini anlatması açısından da önemli bir göreve sahiptir. Nasıl çalıştığını öğrenmek için testleri çalıştırıp okuyabilirler.

Bazı Unit Test Frameworkleri

  1. MSTest
  2. NUnit
  3. xUnit
  4. MBunit

 

Örnek Proje

Şimdi küçük bir unit test örneği yapalım. Eft işlemi yapan bir modül için bir Console Application ve bu modülü basitçe test etmek için bir unit test projesi oluşturalım.

İlk olarak VS da EftSample adında bir console projesi oluşturalım ve sonrasında solution'a sağ tıklayıp yeni bir proje ekle diyerek ismi EftSample.Test adında bir UnitTest projesi oluşturalım.

Eft işleminde kullanılacak olan TransferRequest ve TransferResponse adında class'larını console uygulamamıza aşağıdaki gibi oluşturalım.

       public class TransferRequest
       {
           public decimal Amount { get; set; }
           public string SenderIBAN { get; set; }
           public string ReceiverIBAN { get; set; }
       }
 
       public class TransferResponse
       {
           public bool IsSuccess { get; set; }
           public string Message { get; set; }
       }

Daha sonra transfer işlemini yapacak olan metodu Transfer adında bir class içerisine TransferMoney adında bir metot aşağıdaki gibi tanımlayalım.

Transfer.cs

    public class Transfer
    {
        public TransferResponse MoneyTransfer(TransferRequest request)
        {
            var response = new TransferResponse();
            
            //Aşağıdaki validasyonlar için farklı yöntemler, frameworkler kullanılması daha doğrudur ancak açık görünmesi açısından bu şekilde yapalım
            if (request.Amount < 1)
            {
                response.IsSuccess = false;
                response.Message = "Amount must be greater Then 1";
                return response;
            }
            if (string.IsNullOrEmpty(request.SenderIBAN))
            {
                response.IsSuccess = false;
                response.Message = "Sender Account IBAN must not be null or empty.";
                return response;
            }
            if (string.IsNullOrEmpty(request.ReceiverIBAN))
            {
                response.IsSuccess = false;
                response.Message = "Receiver Account IBAN must not be null or empty.";
                return response;
            }

            //TODO do process
            response.IsSuccess = true;
            response.Message = "Transfer operation is successful.";

            return response;
        }
    }

Şimdi sıra test metotlarını yazmaya geldi. EftSample.Test adında ki test projemize gidip EftSample projemizi referans olarak ekleyelim. Sonrasında TransferMoneyTest.cs adında test metotlarımızı yazacağımız class'ı oluşturalım.

Not - 1: Unit test yazarken naming oldukça önemli bir konudur. Ortak kabul görmüş bir standart olan MethodName_StateUnderTest_ExpectedBehavior şeklinde bir isimlendirme önerilir ancak her yiğidin yoğurt yiyişi farklıdır tezinden yola çıkarak developer anlaşılır yazdığı sürece isimlendirmeyi istediği gibide yapabilmektedir. 

Not - 2: Test metotlarını yazdığımız class'ların başında [TestClass] adında bir attribute tanımlaması yapılması zorunludur. Bu attribute ile sistem o class'ın bir test class'ı olduğunu anlamakta.

Not - 3: Yazılmış olan test metotlarının başında [TestMethod] attribute'ü yazılması gerekir. Bu attribute o metodun bir test metodu olduğunu işaret etmekte.

1- Amount Less Then One Test

İlk test metodumuz Amount = 0 olarak eft işlemi yapmaya çalıştığımızda ki case'i test ediyor olsun.

        [TestMethod]
        public void TransferMoney_With_Zero_Amount_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Amount must be greater Then 1"
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 0,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

2- Sender IBAN Empty Test

İkinci test metodu ise sender iban alanı boş bırakıldığında ki case'i test ediyor olsun.

        [TestMethod]
        public void TransferMoney_With_Empty_Sender_IBAN_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Sender Account IBAN must not be null or empty."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                SenderIBAN = string.Empty,
                ReceiverIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

3- All Pramters Are Ok Test

Son metodumuz da doğru bilgilerle transfer işlemi yapılmaya çalışıldığında ki case'i test ediyor olsun.

        [TestMethod]
        public void TransferMoney_With_Correct_Request_Parameter_Then_Transfer_OK_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer operation is successful."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

Test metotlarımızı yazdıktan sonra aşağıdaki görselde olduğu gibi VS'da bulunan Test > Run > All Tests dedikten sonra Test Explorer açılır ve solution da bulunan test metotları çalıştırılıp success ve fail durumlarını görebiliriz.

TransferMoneyTest.cs içerisi son olarak aşağıdaki gibidir.

    [TestClass]
    public class TransferMoneyTest
    {
        [TestMethod]
        public void TransferMoney_With_Zero_Amount_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Amount must be greater Then 1"
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 0,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

        [TestMethod]
        public void TransferMoney_With_Empty_Sender_IBAN_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Sender Account IBAN must not be null or empty."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                SenderIBAN = string.Empty,
                ReceiverIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

        [TestMethod]
        public void TransferMoney_With_Correct_Request_Parameter_Then_Transfer_OK_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer operation is successful."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }
    }

Çok farklı frameworkler kullanarak çok farklı test yapıları oluşturabilirsiniz. Yaptığımız örnekte basitçe unit test nasıl yazılır göstermeye çalıştık. Unit test ile ilgili daha fazla bilgiyi burada bulunan linkten bulabilirsiniz. 

Redis Nedir

Daha önceki yazıda Windows üzerinde Redis kurulumunu anlatmıştık ve o yazımızda bahsettiğimiz gibi bu yazımızda StackExchange.Redis kullanarak bir .Net Client projesi geliştireceğiz. Öncelikle pc nizde Redis'in çalıştığından emin olun. Bunun için aşağıdaki gibi bir deneme yapabilirsiniz. 

Redis çalışıp çalışmadığına dair kontrol için redis-cli.exe'yi çalıştıralım ve aşağıdaki resimde olduğu gibi bir key-value tanımlayalım ve sonrasında get set işlemi yapalım

Eğer Redis Server sorunsuz bir şekilde çalışıyorsa projemizi oluşturmaya başlayalım. Öncelikle VS'da bir adet RedisDotNetClientSample isminde bir ConsoleApplication oluşturalım.

StackExchange.Redis Kurulum

Daha sonra projemize Nuget Package Manager Console üzerinden StackExchange.Redis package'ı indirip kuralım.

PM > Install-Package StackExchange.Redis

Connection

StackExchange.Redis ile uğraşıyosanız herhalde en çok dikkat etmeniz gereken class ConnectionMultiplexer. ConnectionMultiplexer Redis Server'a bağlanmanızı sağlayacak olan sınıftır ve instance yönetimi Singleton olarak yapılması önerilir. Çünkü ConnectionMultiplexer fully thread-safe dir ve her bir işlem için tekrar tekrar instance oluşturmamız gerekir. Kurulum işlemi sorunsuz tamamlandıktan sonra aşağıdaki gibi RedisConnectionFactory adında bir class oluşturalım.

    public class RedisConnectionFactory
    {
        static RedisConnectionFactory()
        {
            lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
            {
                return ConnectionMultiplexer.Connect("localhost:6379");//redis server conn string bilgisi, web config'den almak daha doğru ancak şimdilik buraya yazdık
            });
        }

        private static Lazy<ConnectionMultiplexer> lazyConnection;

        public static ConnectionMultiplexer Connection => lazyConnection.Value;

        public static void DisposeConnection()
        {
            if (lazyConnection.Value.IsConnected)
                lazyConnection.Value.Dispose();
        }
    }

Generic ICache Interface

Connection kısmını hallettik şimdi ise Cache tarafını yazalım. Bir tane aşağıdaki gibi ICache interface'i tanımlayalım.

    public interface ICache : IDisposable
    {
        T Get<T>(string key);

        void Set<T>(string key, T obj, DateTime expireDate);

        void Delete(string key);

        bool Exists(string key);
    }

RedisCache Class'ı

Şimdi ise ICache interface'ini implement etmiş RedisCache class'ımızı oluşturalım. Bu class içerisinde tanımlı fonksiyonları kullanarak string objelerimizi json formatında string olarak Redis'e atıyor olacağız.

    public class RedisCache : ICache
    {
        private readonly IDatabase _redisDb;

        //Connection bilgisi initialize anında alınıyor
        public RedisCache()
        {
            _redisDb = RedisConnectionFactory.Connection.GetDatabase();
        }

        //Redis'e json formatında set işlemi yapılan metot
        public void Set<T>(string key, T objectToCache, DateTime expireDate)
        {
            var expireTimeSpan = expireDate.Subtract(DateTime.Now);

            _redisDb.StringSet(key, SerializerHelper.Serialize(objectToCache), expireTimeSpan);
        }

        //Redis te var olan key'e karşılık gelen value'yu alıp deserialize ettikten sonra return eden metot
        public T Get<T>(string key)
        {
            var redisObject = _redisDb.StringGet(key);

            return redisObject.HasValue ? SerializerHelper.Deserialize<T>(redisObject) : Activator.CreateInstance<T>();
        }

        //Redis te var olan key-value değerlerini silen metot
        public void Delete(string key)
        {
            _redisDb.KeyDelete(key);
        }

        //Gönderilen key parametresine göre redis'te bu key var mı yok mu bilgisini return eden metot
        public bool Exists(string key)
        {
            return _redisDb.KeyExists(key);
        }

        //Redis bağlantısını Dispose eden metot
        public void Dispose()
        {
            RedisConnectionFactory.Connection.Dispose();
        }
    }

Projemizin Redis bağlantı adımları ve set ve get işlemlerini yapan kısımları hazır. Şimdi ise yazdığımız kodları test etme adımı var. 

Örnek bir obje tanımlayıp bu objeyi redis'e atıyor olalım. Bunun için aşağıdaki gibi User adında bir class'ımız olsun.

    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }

Program.cs içerisinde aşağıdaki gibi Redis içerisine bir adet User objesi oluşturup get&set işlemi yapalım.

    class Program
    {
        static void Main(string[] args)
        {
            ICache redisCache = new RedisCache();

            var userToBeCached = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            var key1 = "caner_key1";

            redisCache.Set(key1, userToBeCached, DateTime.Now.AddMinutes(30));//30 dakikalığına objemizi redis'e atıyoruz

            if (redisCache.Exists(key1))
            {
                var userRedisResponse = redisCache.Get<User>(key1);
            }
        }
    }

Projenizi çalıştırdıktan sonra aşağıdaki görselde olduğu gibi Redis'e atmış olduğumuz objeyi tekrar get işlemini yaptığımızda userRedisResponse değişkenine atanan değerleri görüyoruz.

 

Bu yazı C# ile Redis Cache ve StackExchange.Redis nasıl entegre edilip kullanılır basitçe özetlemekte. Tabiki Redis ile ilgili konuşulacak çok daha fazla konu var ancak elimden geldiğince basit bir şekilde redis client uygulaması geliştirdik.Bazı önemli linkleri aşağıda paylaşıyorum. Redis ile ilgili önemli gelişmeleri bu siteler üzerinden takip edebilirsiniz. 

redis.io 

MS OpenTech – Redis 

StackExchange.Redis 

Redis Desktop 

 

Redis Server Windows Üzerinde Kurulumu ve Kullanımı

Bu yazıda Distributed Caching sistemlerinden biri olan Redis'i inceliyor olacağız.

Redis Nedir ?

Redis için kısaca open source bir NOSQL Memcached veritabanı sistemidir diyebiliriz. Her ne kadar ilk olarak Linux için tasarlanmış olsada ihtiyaç doğrultusunda Windows işletim sistemlerinde de kullanılabilir hale getirildi. Çalışma şekli olarak Key-Value şeklinde gönderilen bilgileri store etmektedir. 

 

Veri Tipleri

Redis verileri String, Hashe, List, Set ve Sorted List olarak saklayabilir.

 Veri tipleri ile ilgili daha ayrıntılı bilgiyi bu linkte bulabilirsiniz. 

Kurulum ve Kullanımı

Öncelikle Redis'i indirip service olarak bilgisayarımıza kuruyoruz. Bunun için bu linkten sizin için uygun olan .rar uzantılı sürümü bulup bilgisayarımıza indiriyoruz. Sonrasında indirmiş olduğunuz dosyalardan redis-server.exe adlı exe'yi çalıştırıp kurulumu yapıyoruz. Default olarak 6379 port'unu hizmete sokar ancak istersek bunu değiştirebiliriz de. Exe çalıştıktan sonra aşağıdaki gibi bir ekran gördüyseniz kurulum OK dir.

Redis çalışıp çalışmadığına dair kontrol için redis-cli.exe'yi çalıştıralım ve aşağıdaki resimde olduğu gibi test amaçlı bir key-value tanımlayıp sonrasında get set işlemi yapalım

Bu yazımızda Windows üzerinde Redis Server nasıl kurulur ve kullanılır bunu gördük. Bir sonraki Redis yazımızda StackExchange.Redis redis client kullanarak NET dilleri için (C# etc) örnek proje yapıyor olacağız.

Server Side Bir Projede Olmazsa Olmazlar || Olursa Güzel Olurlar

Server-side geliştirme yaparken projemizde olursa olmazsa olmaz OR güzel olur diyebileceğimiz bazı modüller&özellikler vardır ve bu özellikler bir çok kitap yazarı, ünlü blogger veya eğitmenler tarafından kabul görmüş özelliklerdir. Yazının başlığına bakacak olursak "Olmazsa Olmazlar" şeklinde ancak yazılım denen şey tabi ki "it depends on the business" duruma göre,ihtiyaca göre geliştirilen bir şey ve "Olmazsa Olmaz" dan kasıt bu zamana kadar hem kendi okuduğum çeşitli makalelerde vurgulanan hemde geliştirmiş olduğum çeşitli projelerde deneyimlediklerimden yola çıkarak 5 farklı önemli özellik var ki bu özellikler gerçekten kurumsal veya büyük çaplı bir projede farkında olarak veya olmadan size oldukça fazla fayda sağlamaktadır.

Bu 5 başlığı sırasıyla yazacak olursak;

  1. Security,
  2. Exception Handling,
  3. Logging,
  4. Response Consistency,
  5. Development Environment

 

1- Security

Bazı bilgiler vardır uygulamanız için veya uygulamayı kullanan kişiler için çok ama çok önemlidir. Örneğin; tckn bilgisi, banka hesap numarası, userId bilgisi, username-password bilgisi vs. bu gibi bilgilere hiç kimsenin ulaşmasını istemeyiz. Günümüzde fiddler, wireshark vb. tool'ları kullanarak client-server arasındaki gidip gelen http paketleri rahatlıkla dinlenmekte ve kötü niyetli bazı kişiler araya girerek giden data'da bulunan tckn,accounNumber,amount vs gibi alanları değiştirip bir çeşit dolandırıcılık yapabilmekteler. Genel de çözüm olarak "ya https yaparız abi hiç bişey olmaz.." şeklinde cümleler kurulup service'i https olarak dışarıya açarlar ve bu tehlikeyi önlediklerini sanarlar ancak artık https bile çeşitli yollar denenerek decrypte edilebilmekte.

Security'den kasıt aslında token based authentication vs. değilde request&response gelip giderken data için uygulanabilecek security. Tabiki de service'e gelecek olan client'ları belli authentication ve authorization kontrollerinden geçirdikten sonra içeriye almak gibi bir çok yöntem mevcut ancak data-trasnfer sırasında belli bazı önlemler alarak projenizin güvenliğini biraz daha atırabilirsiniz. Bunun için çeşitli yöntemler bulunmakta. Property bazında istenilen değeri şifleme veya maskeleme. Örneğin; UserLoginRequest adında bir modeliniz var ve içerisinde bulunan UserName, Password string alanlarını şifreleyebilirsiniz. Diğer bir yöntem ise endpoint'e gelen request ve endpoint'ten çıkan response modellerinizin tamamını şifreleyerek data transferini sağlayabilirsiniz ki bu daha base-oriented bir çözüm gibi duruyor. Örnek bir response örneği aşağıdaki gibi olabilir.

public class BaseResponse
{
	public object Data {get;set;} // şifrelenmiş bir şekilde endpoint'in return ettiği response Data içerisinde gönderilebilir
	public bool IsCrypted {get;set;} her endpoint şifrelenmiş şekilde response dönmeyebilir, client'ı bilgilendirmek adına bir bool alan tutabilirsiniz
}

Not: Checksum'da unutulmaması gereken diğer bir yöntemdir. Checksum kullanarak gelip giden data uzunluğu belli bir algoritma ile şifrelenip çift taraflı kontrol uygulanarak da güvenlik sağlamak işinizi baya bi kolaylaştırabilir.

 

2- Exception Handling

"Hatasız kod olmaz.."

Exception handling her bir proje için oldukça önemli bir konudur. Hatasız kul olmayacağı gibi hatasız kod da olmaz. Exception fırlatabileceğiniz düşündüğümüz yerleri zaten önceden tedbirini alıp handle ediyoruzdur ama asıl önemli olan beklenmedik hataları nasıl handle edeceğimizdir. En yaygın hatalardan biri olan null reference exception hemen hemen bütün projelerde başımıza gelmiş bir exception türüdür ve projeniz bu hatayı fırlattığında client'a http500 internal server error dönecektir. Peki bu tür hataları handle etmek için ne yapmak gerekir ?? Bir çok projede olduğu gibi her yere try catch koyarak kodunuzu spaghetti code haline getirip yönetilmesi zor bir proje haline getirmek heralde yapılabilecek en kolay çözüm. Öyle projeler var ki denk geldiğim adam try catch e bile güvenmeyip iç içe 2 try catch yazmış.

Bu ve benzeri durumlardan sakınmak gerekir. Open-source dünya ile birlikte exception handling için bir çok kütüphaneler-yöntemler mevcut. Projeniz için her yerde kullanabileceğiniz küçük küçük interceptor'lar yazarak exception handling'inizi tek bir yerden yönetebilir ve projenize reuseable fonksiyonaliteler kazandırabilirsiniz. Aspect-Oriented Programming anlayışı aslında en güzel örnek Nuget'te bulunan çeşitli kütüphaneler ile bir kaç dakika içerisinde bu modülleri projenize ekleyebilirsiniz.

Bu gibi yapılar ile çok kolay şekilde projenizi yönetebilir ve tek bir yerden kontrol edilebilir kodlar geliştirebilirsiniz. Böylelikle yarın bir gün exception handling ile ilgili değişiklikler yapmak istediğinizde bu işlem saatlerinizi almak yerine bir kaç dakikada yapabileceğiniz bir geliştirme olacaktır.

 

3- Logging

"Log, uğrunda ölen olmadıktan sonra log değildir.."

Log bir projede her şeydir. Küçük çaplı projeleri baz almazsak log yapısı büyük çapta olan projeler için oldukça hayati önem taşır. Öyle bir an gelir ki log'a attığınız küçücük bir byte size dünyaları kazandırır. Tabi log işlemi yaparken kayda değer şeyler logluyor olmak oldukça önemlidir. Özellikle dışa bağımlı çalıştığınız projelerde yani external bir service call işlemi olan yapılarda gelip giden request & response'ları context halinde logluyor olmak oldukça kolaylık sağlar. Gerek db ye gerekse file log yaparken bütün bu işemleri uçtan uca logluyor olmak ve aralarında ilişkileri bir unique identifier kullanarak loglamak gerekir. Örnek verecek olursak "getAccountList" adında geliştirdiğiniz bir endpoint var ve client'lar bu endpoint'e request atarak hesap listesini alıyor sizde client bu endpoint'e geldiğinde arka tarafta başka bir endpoint'e gidip hesap listesi ile ilgili farklı service call işlemleri yapıyorsunuz. Geliştirmiş olduğunuz log yapısından beklenen, belli bir context olarak uçtan uca hem client'ın request'ini hemde sizin server-side da yapmış olduğunuz diğer servis çağrımlarını logluyor olması gerekir. Bu size şunu kazandırır production'da olan bir müşteri şöyle bir şikayetle müşteri hizmetlerine şikayet bırakabilir "ben saat 11:38'den hesaplarım sayfasını görüntüleyemiyorum, sayfa boş geliyor..." bu gibi durumlarda hemen müşteri numarasından yola çıkarak log'ları kontrol ettiğinizde sorunun sizin geliştirdiğiniz service katmanından değilde dışa bağlandığınız service'den kaynaklandığını kolaylıkla görebilirsiniz. Bu logları internal ve external olarak ayırmakta fayda olabilir zira proje müdürü sizden ilerleyen zamanlarda yüzdelik olarak success ve fail oranlarını isteyebilir yani aslında tutmuş olduğunuz log'lardan yola çıkılarak proje yöneticileri analiz dahi yapmak isteyebilirler. Loglama ile ilgili örnek yazıları buradan göz atabilirsiniz.

Not: Log için çok çeşitli open source kaynaklar bulunmakta. Geliştirmelerinizi loosely coupled olacak şekilde yapıyor olursanız ilerde log yapınızla ilgili kolay değişiklik yapabilir, farklı log yapılarına kolay geçişler sağlayabilirsiniz.

 

4- Response Consistency

Response tutarsızlığı herhalde Türkiye de geliştirilen server-side projelerde ki en büyük sorunlardan biri olabilir. Bir çok defa dış servislere bağlı proje geliştirmiş biri olarak aynı namespace altında olup farklı farklı response'lar dönen durumlarla epeyce bir karşılaştım. Peki projenizde bulunan end-point'lerin döndükleri response'ların tutarlı olması neden önemlidir ? Bu sorunun cevabı hem server-side'da bulunan proje açısından hemde client açısından büyük öneme sahiptir.

Server-side açısından; öncelikle uygulamada exception-handling, logging, caching vs. gibi özellikleri geliştirirken endpoint'lerinizin return ettikleri response'ların bir BaseResponse.cs'den türüyor olması tercih edildiğinde bu gibi operasyonlar için yapılması gereken geliştirmelere harcanan eforu hemen hemen yarı yarıya azaltabilirsiniz. Yapmış olduğunuz BaseResponse.cs içerisinde Error case'leri için de bir property'iniz olması gerekir çünkü client'a farklı akışları olan error-exception mesajları dönebilirsiniz. 

Örnek BaseResponse.cs

public class BaseResponse
{
	public object Data {get;set;} // endpoint'in return ettiği response
	public ErrorModel Error {get;set;} // endpoint'in return ettiği error model
}

Client açısından; yukarıda ki BaseResponse örneğinden yola çıkacak olursak client tarafta geliştirilen network-layer da gelen response açılarak endpoint'in gönderdiği Data içerisinde bulunan gerçek response alınabilir ve sonrasında alt katmanlara gönderilip işlenebilir ve bunu yapabiliyor olmak client yazan arkadaşlar bilirler ki büyük bir nimettir. Servisten hata geldiğinde ise ErrorModel tipinde olan Error property'imiz içerisinde client'a gelen hata mesajı yine network katmanında alınıp base'den hata yönetimi yapılabilen bir layer'a gönderilip gerek pop-up ile hata mesajını gösterme gerekse kullanıcının o hata türünü aldığında uygulamada nasıl bir akışa yönlendirilmesi gibi işlemler yapılmasına kolaylık sağlanabilir.

 

5- Farklı Development Environment

"Dev de çalışıyor ama Test ortamında çalışmıyor..."

Development environment oldukça önemli bir diğer konudur. Farklı ortamlarda geliştirme yapıyor olmak biz developer'lar için eksik konfigurasyon vs gibi durumlarda başımızı ağrıtan bir durum olsada aslında çok önemli bir konudur. Bilindiği üzre Production seviyesinde geliştirme yapmak mümkün değil ama geliştirmekte olduğumuz ürünü prod'a aldığımızda sorunsuz çalışması istenir. Projenizi geliştirirken o an geliştirdiğiniz ortam, test işlemlerinin yapıldığı ortam ve production ortamlarının her birinde farklı konfigurasyonların olduğu server'lar sql'ler vs. bulunur. Geliştirme yaparken bu ortamların yönetimi ve konfigürasyonu oldukça basit ve ortamlar arası geçiş sorunsuz 1-2 hareketle geçilebiliyor olması beklenir. Peki bu development ortamları nelerdir ?

Development
Development ortamı ismindende anlaşıldığı üzre developer'ların geliştirme yaptığı ortam. Bu ortam developer istediğini yapmakta özgürdür diyebiliriz. Developer kritik kararlar alıp anlık değişiklikleri projede deneyebilir çünkü bu ortam onun kendi bireysel ortamıdır diyebiliriz.

Integration
Development ortamında bireysel olarak yapılan geliştirmelerin ortak bir branch'e commit'lenip bütün geliştirmelerin ortak bir alanda gözlemlendiği alan diyebiliriz. Kısaca bu ortamın amacı yapılan işlerin stage'ing den önce combine edilip developer'ların saptayabildiği bir hata varsa hemen fix edilip ürünü görücüye çıkartma şeklinde tanımlayabiliriz.

Staging
Staging Production'dan önceki ortamdır ve sahip olduğu özellikler itibari ile production'ın ile bire bir aynı olması beklenir. Örneğin benzer özelliklere sahip sunucular, sql server'lar, canlıda olan database'in bir gün öncesinin verilerine sahip bir database vs. gibi. Staging ortamına bir çok yerde Pre-Production da denilmekte.

Production
Son adım olarak da Production ortamı artık geliştirmiş olduğunuz projeyi canlıya aldığınız ve son kullanıcının erişimine açtığınız yerdir. Prod'a gelene kadar ki ortamlarda yapılan geliştirmeler sorunsuz-planlı bir şekilde yapıldığı taktirde testler sonucunda ürün prod'a alınır.

 

Yazının başında da belirtiğim gibi "it depends on the business" yani işe bağlı projeye bağlı. Olursa güzel olur diyebileceğimiz bu modülleri projelerinizde farklı şekillerde düşünebilir entegre edebilirsiniz.

Fluent NHibernate Nedir ve Kullanımı

ORM Nedir ?

ORM (object relational mappers) uygulamalarınızda CRUD (Create, Read, Update, and Delete) işlemleri için basit bir şekilde data'ya erişimlerimizi sağlayan yapılardır. Çeşitli ORM framework'leri uzun zamandan beri data-model arasındaki veri alış verişini sağlamak için kullanılmakta ve ORM ler sql scriptleri execute etmeden CRUD işlemlerini doğrudan yapabilmemiz için kod yazmamıza olanak sağlarlar. Özetle, ORM kullanarak uygulamalarımızdaki data-model ilişkisini(database modellemesini) object-model ilişkisine dönüştürebiliriz.

Neden Fluent NHibernate ?

Klasık NHinernate, database de bulunan her bir tablo için class mapping bilgilerini .hbm uzantılı  XML formatındaki dosyalarda saklıyordu ve aslına bakılırsa biraz zahmetli bir işlemdi. Fluent NHibernate'de ise artık .hbm uzantılı mapping bilgilerini içeren XML formatındaki dosyalar kaldırıldı ve bu işlem code-behind'a taşındı ve diğer ORM'lere göre daha hızlı olduğu kabul edilir.

Kısaca özelliklerini sıralamak gerekirse;

  • Easy data-access and data-mapping,
  • Compile-time name- and type-safety,
  • IntelliSense to show you which fluent methods are available at any point,
  • Automapper impl.

Fluent NHibernate ile kolayca Linq sorguları yapabilir ve böylelikle Fluent bir Api oluşturmada kullanabiliriz. Şimdi ise projemize nasıl Fluent NHibernate entegre edip geliştirme yapabiliriz bunu incelicez.

Fluent NHibernate Kurulum

İlk olarak Visual Studio üzerinde yeni bir Console Application projesi açalım. Daha sonra Nuget üzerinden Fluent NHibernate referanslarını projemize indirip kuralım. Install-Package FluentNHibernate

Database Tablo Oluşturulması

Fluent NHibernate kullanabilmek için öncelikle bir database'e ihtiyacımız var. SQL üzerinde FNH_Db adında adında bir database'imiz olsun ve bu db'ye ait aşağıda bulunduğu gibi User adında bir tablo oluşturalım.

CREATE TABLE dbo.Users  
(
    Id int PRIMARY KEY NOT NULL,  
    Name varchar(25) NOT NULL,  
    SurName varchar(25) NOT NULL
)  

User Tablosuna Karşılık Gelen User.cs Oluşturulması

Yukarıda tanımlamış olduğumuz User tablosuna karşılık gelen User.cs aşağıdaki gibi oluşturuyoruz.

public class User
   {
       public virtual int Id { get; set; }
       public virtual string Name { get; set; }
       public virtual string SurName { get; set; }
   }

Mapping Class'ının Oluşturulması

Database tablomuz ve buna karşılık gelen class'ımız hazır ancak halen User objesinde bulunan hangi alan tabloda bulunan hangi alana karşılık geliyor bunu belirtmedik. Bunun için Fluent NHibernate'e ait olan ClassMap adında bir class bulunmakta ve gerekli mapping işlemlerini yapacağımız class ClassMap<T>'den inherit olmuş şekilde oluşturulacak. Burda ki T bizim User class'ımız oluyor.

UserMap.cs adındaki mapping class'ı aşağıdaki gibi olacak şekilde tanımlıyoruz.

   public class UserMap : ClassMap<User>
   {
       public UserMap()
       {
           Id(x => x.Id);
           Map(x => x.Name);
           Map(x => x.SurName);
           Table("User");
       }
   }

DB Connection Sağlanması

Şimdiki işlem ise Fluent Nhibernate kullanarak yukarıda tanımlamış olduğumuz FNH_Db isimli database'e bağlanmak. Bunun için OpenSession adında bir metot oluşturalım.

       public static ISession OpenSession()
       {
           string connectionString = "FNH_Db conn string";
           var sessionFactory = Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2012
                .ConnectionString(connectionString).ShowSql()
               )
               .Mappings(m =>m.FluentMappings.AddFromAssemblyOf<User>())
               .ExposeConfiguration(cfg => new SchemaExport(cfg)
               .Create(false, false))
               .BuildSessionFactory();
           return sessionFactory.OpenSession();
       }

Tablo'da Bulunan Verileri Alma

Aşağıda yer alan kod bloğu ise db de bulunan tablodaki verileri query etmemizi sağlar.

using (var session = OpenSession())
     {
          var users = session.Query<User>().ToList();
     }

Yukarıda bulunan geliştirmeleri yaptığınızda db bulunan bir proje geliştirmede basitçe connection sağlayarak data-model ilişkisini kurabilirsiniz.  

Not: Yazıyı basit tutmak adına herhangi bir design pattern den bahsetmedim ancak Db olan yerde Crud işlemleri vardır, Crud olan yerde Ado.Net veya ORM vardır, ORM varsa Fluent NHibernate vardır ve Fluent NHibernate de Repository Design Pattern ile birlikte fıstıklı baklava gibi gider :) 

Fluent NHibernate ile ilgili daha detaylı bilgi almak ve geliştirmeleri takip edebilmek için bu linke göz atabilirsiniz.

IComparable Interface'ini Kullanarak Sıralama İşlemi

Primitive type'lar için sıralama işlemini bir şekilde list.Sort() vs gibi metotlar kullanarak yapabiliyoruz. Peki kendi yazdığımız objeler için bunu nasıl yaparız ? .Net tarafında iki objeyi belli field&property'lere göre sıralamamızı veya compare etmemizi sağlayan IComparable<T> interface'i bulunmakta. Bu interface ile List<T> tipidne tanımladığımız değişkenimize bizim belirttiğimiz kritere göre sıralama özelliği kazandırmış oluyoruz. Örnek bir case üzerinden gidelim; Rectangle adında bir objemiz olsun içerisinde Rectangle objeleri bulunan bir array'imiz olsun bu arrayde bulunan objeleri alanı en küçük olandan büyük olana doğru sıralayalım. İlk olarak Rectangle objemizi aşağıdaki gibi tanımlayalım.

    public class Rectangle 
    {
        public double Length { get; set; }
        public double Breadth { get; set; }

        public double Area
        {
            get
            {
                return Length * Breadth;
            }
        }
    }

Şimdi ise üsttede belirtiğimiz gibi dikdörtgen objelerini küçük olandan büyük olana doğru array'de sıralayalım. Bu işlem yapabilmenin bir yolu IComparable interface'ini kullanmak. Rectangle objemizi bu interface'den implement etmek. Implementasyon sonucunda Rectangle class'ı CompareTo adında bir metota sahip olur. Bu metot aracılığıyla compare işlemi yaparak array'imizi sıralayabiliriz. Rectangle class'ımızın implementasyon sonrasındaki görünümü aşağıdaki gibi olacaktır.

    public class Rectangle : IComparable<Rectangle>
    {
        public double Length { get; set; }
        public double Breadth { get; set; }

        public double Area
        {
            get
            {
                return Length * Breadth;
            }
        }

        public int CompareTo(Rectangle other)
        {
            if (this.Area == other.Area)
            {
                return this.Area.CompareTo(other.Area);
            }
            return other.Area.CompareTo(this.Area);
        }
        public override string ToString()
        {
            return this.Area.ToString();
        }
    }

 Şimdi ise sırada yazdığımız bu kodu test etme işlemi var. 

        public static void Main()
        {
            var list = new List<Rectangle>
            {
                new Rectangle() {Length = 7, Breadth = 3},
                new Rectangle() {Length = 5, Breadth = 1},
                new Rectangle() {Length = 9, Breadth = 4},
                new Rectangle() {Length = 2, Breadth = 7},
                new Rectangle() {Length = 3, Breadth = 5}
            };

            list.Sort();//Sort metodu ile array'i belirtmiş olduğumuz kritere göre sıralıyoruz

            foreach (var element in list)
            {
                Console.WriteLine(element);
            }
        }

Projeyi çalıştırdığımızda aşağıdaki gibi bir ekran elde etmiş olacağız.

36
21
15
14
5

 

Servisten Gelmeli || Client Yapmalı

Servisten mi gelmeli yoksa client mı yapmalı ?.. 

Hem server tarafta hem de client tarafta geliştirme yapan biri olarak kolayca söyleyebilirim ki bu iki soru yazılımla uğraşan kişilerin sıkça dile getirdiği sorulardan dır. Özellikle 10-12 kişilik development ekiplerinin bulunduğu projelerde client ve server tarafını geliştiren kişilerin genelde scrum'larda birbirlerine karşı dile getirirler bu cümleleri. Jira'da yeni bir bug açılır analist arkadaş genelde platform belirtilmişse direkt olarak bug'ı o platformu geliştiren kişiye assign eder. Sonrasında o arkadaş ilgili bug'ın altına server-side geliştirmesini yapan arkadaşlardan birini mention'layarak şu sihirli yorumu yazar "servisten gelmeli veya servis yapmalı" veya bunun tam tersi bug server-side geliştirme yapan kişiye açılır o yorum yazarak "client yapmalı" vs şeklinde bir cümle yazar. İşte taht kavgaları tamda o anda başlar.

Aslında bu gereksiz kavganın yaşanmasındaki sebeplerin başında gerekli geliştirme hem client hem de server tarafta da yapılabiliyordur ancak developer geliştirmeyi üzerine almak istemediğinden diğer tarafa atmak ister. Bir kaç ördenk case den yola çıkarak bu soruna çözüm bulabiliriz aslında. 

SOA gerçeklerinden yola çıkacak olursak yönetimin service tarafından yapılabiliyor olması bir projede oldukça önemlidir. Örneğin bir mobil proje geliştiriyorsunuz ve username password yanlış olduğunda kullanıcıya gösterdiğiniz bir uyarı metni var "Girmiş olduğunuz bilgiler hatalıdır." şeklinde. Bu geliştirmeyi client tarafta yaptığınızı düşünelim ve uygulama marketteyken müşteri dedi ki "ya biz şu metni Girmiş olduğunuz bilgiler hatalıdır. Lütfen tekrar deneyiniz." olarak değiştirelim ve bunu hemen 1-2 saat içerisinde yapalım. Proje müdür OK verdi ve geliştirmeyi yapmak için hem ios hem android projelerinde metni değiştirdiniz tekrardan markete submit ettiniz. Uygulamanın market onay sürecinden geçmesi özellikle ios tarafında bazen 1 hafta dahi sürebiliyor. Ne oldu peki müşteri 1-2 saatte halledelim dedi ancak siz 1 haftada halledebildiniz. Eğer client'cı arkadaşların dediği gibi "geliştirmeyi servis yapsın" cümlesini dinleyerek yola çıksaydık ve geliştme service tarafında yapılmış olsaydı hemen ilgili metin değişikliğini db'de bulunan tablodan güncelleyip dakikalar içerisinde isteği yerine getirmiş olacaktık. 

Tabi client'ın her dediği yapıldığında da bir süre sonra servisci arkadaşlarda şu cümleyi ister sitemez duyabiliyoruz "e uygulamayı biz yazıyoz zaten client napıyo ki.." Bu gibi durumlar içinde aslında çok arada kalınan bir case ise ve client'ın yapması daha kolay bir durum ise client'ta yapmak daha doğru olacaktır. Örnek olarak karşılaştığım bir case olmuştu. Uygulamada ürün detay sayfasının bir bölümünün arka planında blur şekilde ürün görselinni gösterilmesi isteniyordu ve ürün resmi dış servis sağlayıcıdan dikdörtgen şeklinde geliyordu. Bunun için client tarafı bu geliştirmeyi servis yapacak demiş ve servis tarafını geliştiren ekibe yani bize şöyle bir geliştirme ticket'ı açıldı "Gelen dikdörtgen resim alınacak resmin en ortada bulunan kısmından 200x200 boyutlarında bulunan kısmı alnıp client'a o şekilde gönderilecek..." ve 2 gün sonra sürüm çıkmayı planlıyoruz.. WTF oluyosun ilk okuduğunda ama geliştirmede bu. Muhakak yapılır ancak ne gerek var o kadar uğraşmaya diyor insan. Daha önceleri client geliştirmesi yapıtığımdan biliyorum ki o resmi client tarafta alıp arka plana yerleştirip ortalamak max 15 dkkalık bir iş olsa gerek. Hemen takım liderine durumu anlattım ve hakiketen client tarafı 15-20 dkka içerisinde istenilen geliştirmeyi yaptı. Burda şunu sorabilirsiniz, e yarın 300x300 olacak şekilde isterlerse ne olacak ? Olabilir tabikide ancak 15 dkka nere 1-2 gün nere. Bu gibi durumlarda ekip olarak çoğunluğa göre karar alınıp en hızlı çözüm hangisi ise o uygulanmaya çalışılır.

Sonuç olarak; serviste mi gelmeli yoksa client mı yapmalı diye tekrar soracak olursak "it depends on the business" diyorum ben yani işe göre değişir ve her ne kadar herşeyi servisten yönetmemiz gereksede gerçek olan o ana göre mantıklı olan çözüm ney ise o taraf yapıyor olmalıdır. Gerçekten iki tarafıda en iyi şekilde bilen bir kişiye danışarak veya iki taraf bir araya gelip istişare yaptıktan sonra en efektif çözüm bulunacaktır.