Uygulama İzleme Yazılımı Serisi 3 – Utility, Birim Testler, Sürekli Entegrasyon

Evet dostlar, uygulama yazımıza devam ediyoruz. Önceki yazılarıma erişmek için aşağıdaki bağlantıları kullanabilirsiniz:

Uygulama İzleme Yazılımı

Uygulama İzleme Yazılımı 2 – Başlangıç

Öncelikle bu yazımızda nelere değineceğiz, kısaca onlara bir göz atalım:

  • Utility kütüphanesi,
  • Birim testler (google test) ve sürekli entegrasyon
  • CMake’e ilişkin eklediklerimiz.

Utility Kütüphanesi

Bu kütüphane, QT’ye bağımlı olmayan, fakat uygulama ve kütüphaneler içerisinde kullanılacak bir takım genel kabiliyetleri barındıryor olacak. Şimdilik aşağıdaki sınıfları içermektedir. İlerleyen dönem içerisinde bunların saysı artabilir.

  • ParameterSet: Bu sınıfı genel olarak, “özellik” anlamında tutulabilecek her türlü/metin değer eşleniği listelerini tutmak için kullanıyor olacağız. Burada daha önce sizler ile de paylaştığım std::any kabiliyetini kullanıyoruz.
  • PropertyItem: Temelde bir önceki kabiliyete benziyor fakat tekli kullanımlar için bir veri yapısı.
  • Timestamp: Bu da yine, daha önce bir yazımda işlediğim, zaman etiketleri ve benzeri kabiliyetler için kullanılacaktır.
  • UniqueIDGenerator: Basit bir eşsiz tanımlayıcı üretmek için kullanılacak bir sınıf. Burada da, std::atomic kabiliyetini kullanıyoruz. Bu kabiliyete de değinmiştik.

Bu arada kodlara baktığınızda, sizlerin de fark edeceği üzere kod yorumları için doxygen tabanlı bir yaklaşım izledim. Bunun için de atomineer eklentisini kullandım.

Bu kütüphaneyi statik bir kütüphane olarak kullanacağız. Bu bağlamda CMake içerisinde, add_library komutunu, STATIC ayarı ile tanımlıyoruz. Ayrıca benzer şekilde, bu kütüphaneye ilişkin başlık dosyalarını kullanabilmek için de target_include_directories komutunu kullanıyoruz.

Bir sonraki yazılarımızda, QT kütüphaneleri için ne yaptığımıza bakacağız.

Birim Testler

Bu uygulama içerisinde sizler ile paylaşacağım bir diğer kabiliyet de, birim testler. Birim testlerin temel amacı, ilgili kod parçalarının doğru çalışıp/çalışmadığının kontrol edilmesi olarak özetlenebilir. Bu noktada tabi, birim olarak neyi referans alınacağı aklınıza gelebilir. Bu genelde, uygulamanın test edilebilecek olan en küçük parçası olarak değerlendirilmektedir. C++ bağlamında genelde “public” sınıf fonksiyonları referans alınmaktadır. Birim testler ile bu parçaların birbirlerinden izole edilerek, tek tek doğru çalıştığının gösterilmesi amaçlanmaktadır. Burada son bir önemli noktada da, bu testlerin hızlı bir şekilde koşturulabilip, yapılan değişikliklerden yazılımın etkilenmediğini göstermektir.

Elbette birim teslerin bir çok faydası var. Aşağıda bunları kısaca sıralamaya çalıştım:

  • Kodun modülarize edilmesine, daha kolay test edilebilir parçalara ayrılmasına yardımcı olur,
  • Hatalar daha kolay ve hızlı bir şekilde bulunur
  • Her birim tarafından sunulan işlevsellik daha iyi anlaşılır
  • Yazılımın hayat döngüsünde ileri zamanlarda yapılacak güncellemelerin doğru bir şekilde yapıldığından emin olunur,
  • Yazılım test edilmesi için, bitmesi beklenmez,
  • Zaman kazandırır ve maliyeti düşürür.

Birim testler genel olarak üç faza ayrılır ve orjinal isimleri ile “Arrange, Act, Assert” olarak ifade edilir. Bu aşamalar aşağıdaki gibi gösterilebilir:

birim testlerinin açık ve spesifik, geçme/kalma koşulları vardır. Burada birim testleri yazarken dikkat etmek gereken bir kaç husustan da bahsetmek istiyorum. Bunlar:

  • İlgili sınıfın bütün public fonksiyon, yapıcı ve operatörlerini test etmelisiniz,
  • Açık olan koşullar yanında, diğer koşullar, hatalı girdiler ve uç koşulları da test etmelisiniz,
  • Her bir test bir birinden bağımsız ve diğerlerinin koşumunu etkilemiyor olmalı,
  • Testlerin koşum sırası, sonucu etkilemiyor olmalıdır.

Birim testlerin yanında ayrıca “mocking” kabiliyetinden de sık sık bahsedilir ve kullanılır. Bu da, test etmek olduğunuz kabiliyetin diğer birimler ile olan etkileşimlerini kontrol etmek için sizlere yardımcı araçlar sunar. Bu konuya, ilerleyen yazılarımda örnekler ile değineceğim.

Birim testler için kullanılabilecek bir çok kütüphane mevcut. Bu yazılımda biz, google test’i kullanacağız. Google test ile ilgili çok güzel kaynaklar var. Özellikle hoşuma gidenleri kaynaklar kısmına ekliyorum, burada temel bir takım özelliklerini sizler ile paylaşacağım, daha detaylı bilgi için kaynaklara göz atabilirsiniz.

Google Test 

Google Test ve Google Mock, google tarafından geliştirilen ve bir çok kullanıcısı bulunan test kütüphanelerinden biri. Bir çok platformu desteklemesi yanında, çeşitli durdurucu (fatal) ve durdurucu olmayan (non-fatal) önerme kontrolleri, test “fixture”‘ları, gruplamaları, bilgilendirici mesajları, xml tabanlı raporlama ve bir çok kabiliyeti barındırmaktadır.

Diğer bir takım kütüphanelerden farklı olarak, ne yazık ki, kütüphane şeklinde kullanılmakta. Bir diğer ifade ile kullanacağınız platform için, google test ve mock’u oluşturmanız gerekiyor. Neyse ki, sizlere sunduğum kodta (ve bir önceki yazımda) bunu nasıl yapabileceğinizi sizlere aktarmıştım. Şimdi, hızlıca google testin özelliklerin üzerinden geçelim.

Google test içerisinde, kod parçalarınızın doğruluğunu test etmek için kullandığınız ifadeler, önerme olarak “assertion” adlandırılmaktadır. Bunlardan durdurucu (“fatal”) olanlar, ASSERT_ ön eki ile başlarken, durdurucu olmayanlar (“non-fatal”) ise, EXPECT_ ön eki ile başlamaktadır. Bu ikisi arasından en önemli fark, durdurucu önermelerdeki koşullar sağlanamaz ise test orada biter ve sonraki önermelere bakılmaz. Diğerinde ise, diğer önermelere de bakılır.  Testleri tanımlamak için ise TEST, makrosunu kullanıyoruz. Hemen bir örnek inceleyelim:

Google test ile gelen önerme kontrollerinden bir kısmını aşağıya ekliyorum. Daha detaylı önermeler için https://github.com/google/googletest/blob/master/docs/primer.md‘a göz atabilirsiniz. Burada, durdurucu olmayan önermelere ilişkin komutlar mevcut, EXPECT yerine ASSERT kullanarak durdurucu olanları kullanabilirsiniz:

Mantıksal ÖnermelerEXPECT_TRUE(condition)
EXPECT_FALSE(condition)
Genel KarşılaştırmalarEXPECT_EQ(expected, actual) / EXPECT_NE(val1, val2)
EXPECT_LT(val1, val2) / EXPECT_LE(val1, val2)
EXPECT_GT(val1, val2) / EXPECT_GE(val1, val2)
Kayan Noktalı SayılarEXPECT_FLOAT_EQ(expected, actual)
EXPECT_DOUBLE_EQ(expected, actual)
EXPECT_NEAR(val1, val2, abs_error)
Metin KarşılaştırmaEXPECT_STREQ(expected_str, actual_str) / EXPECT_STRNE(str1, str2)
EXPECT_STRCASEEQ(expected_str, actual_str) / EXPECT_STRCASENE(str1, str2)
İstisna KontrolleriEXPECT_THROW(statement, exception_type)
EXPECT_ANY_THROW(statement)
EXPECT_NO_THROW(statement)

Bir süre test yazdıktan sonra, testlerde bir çok veriyi kullanma ve ortak işlevleri bir araya getirme ihtiyacınız olabilir. İşte “test fixture”‘ları tam olarak bu noktada imdadınıza yetişiyor. Aslında bakarsanız, yukarıda verdiğim google test sayfasında da, eğer birden fazla test için veri hazırlıyor durumda kendinizi bulursanız. Test “fixture”‘larını kullanmalısınız diye öneriyor 🙂 Hemen bir örnek ile inceleyelim:

Öncelikli olarak, ::testing::test sınıfından bir sınıf türetiyorsunuz ve fonksiyon kapsam tanımlayıcısının en kötü “protected” olduğundan emin oluyoruz. Her testin başında yapılmasını istediğiniz işlevleri SetUp() içerisinde, test sonrasında yapılmasını istediğiniz işlevleri de TearDown() içerisine ekliyorsunuz. Bunlara ihtiyacınız yok ise eklemeyebilirsiniz. Bunlar dışında da, kendi ihtiyaçlarınız doğrultusunda bir çok fonksiyon ya da üye veri yapısı ekleyebilirsiniz. Şimdi de, google testin, bir kuyruk veri yapısı için verdiği örneğe göz atalım. Önce veri yapısı sınıfına, daha sonra da ilgili fixture sınıfı aşağıdaki gibi:

Bu “fixture”‘ları kullanarak test yazmak için ise TEST_F makrosunu kullanıyoruz ve ilk parametrenin yukarıdaki sınıf olarak verildiğinden emin oluyoruz:

Burada unutmamanız gereken şey, hem IsEmptyInitially hem de DequeueWorks için ayrı QueueTest nesneleri oluşturulmaktadır. Detaylar için https://github.com/google/googletest/blob/master/docs/primer.md‘a göz atabilirsiniz

Uygulama izleme yazılımı için google test’i, ext dizini içerisinde projemize dahil ediyoruz ve CMake yardımı ile hedef platforma göre oluşturuyoruz.

Mevcut, proje dizini içerisinde, Utility kütüphanesine ilişkin google test ile yazılımış birim testleri görebilirsiniz. Bu da size, bu testleri nasıl kullanılabileceğine ilişkin birinci elden bir örnek teşkil edeceğini umuyorum. Zamanla diğer eklediğim sınıflara dair testleri de ekliyor olacağım. Hatta QT kod parçaları için QTest’e de hızlıca bakacağız.

Şimdilik google test olayını burada bırakıyoruz. Sonraki yazı serisinde ya da bağımsız yazılarımda, google test’e ilişkin ip uçlarını sizlerle paylaşacağım.

Sürekli Entegrasyon

Gelelim bir diğer önemli başlık olan sürekli entegrasyona. Aslında başlı başına bir yazı olabilecek bir konuya burada hızlıca bir giriş yapıp, önümüzdeki yazılarda bu konuya değinmeye devam edeceğim. Sürekli entegrasyon amacı ile gitlab kullanacağımı bir önceki yazımda sizlere aktarmıştım. Burada kısaca sürekli entegrasyon ve konuşlandırma konularının arkasındaki motivasyona göz atıyor olacağız.

Sürekli entegrasyon kavramı ilk olarak Kent Back tarafından 90’lı yıllarda kullanılmıştır ve günümüzde artık bir çok yazılım firması ve projesinde kullanılan önemli bir çevik yazılım geliştirme pratiklerinden biri haline gelmiştir.

Bu konuda oldukça fazla makale yazmış olan Martin Fowler’ın, sürekli entegrasyon için kullandığı tanıma bir bakalım:

Sürekli entegrasyon, yazılım takım üyelerinin işlerini sık bir şekilde  ana kod yapısna entegre ettiği, genelde günde en az bir kere, bir yazılım geliştirme pratiğidir. Her bir entegrasyon, test de içeren otomatik bir oluşturma işlemi tarafından kontrol edilerek, bu entegrasyonun sebep olduğu hatalar varsa erken ortaya konulmasına yardımcı olur.

Bu makale, bu pratiği uygulayan bir çok yazılımcı tarafından kabul görmüş, sürekli entegrasyona dair bir takım aktivitelerden bahsedilmiş. Nedir peki bu sürekli entegrasyon aktiviteleri, kısaca bunlara bir göz atalım. Daha sonraki yazılarda bunların detaylarına bakıyor olacağız:

1. Bir Tane Kod Kaynağı Olmalı
2. Derleme ve Oluşturma Süreci, Otomatize Edilmeli
3. Derleme, Kendi Kendini Test Eder Şekilde Olmalı
4. Herkes Geliştirdiği Kodu, Her Gün Entegre Eder
5. Yazılım Geliştiriciler Tarafından Gönderilen Her Kod, Entegrasyon Makinesi Üzerinde Derlenmelidir
6. Hatalı Oluturmalar/Derlemeler Hemen Düzeltilmelidir
7. Oluşturma Hızlı Olmalıdır
8. Testler, Üretim Ortamına Benzer Bir Ortamda Yapılmalıdır
9. En Son Çalıştırılabilir Kod, Herkes İçin Kolay Ulaşılabilir Olmalıdır
10. Herkes Olanları Görebiliyor Olmalı
11. Konuşlanma Otomatikleştirilmelidir

Yukarıdaki kalemler, sizlere sürekli entegrasyonun temelde neyi başarmayı hedeflediğini ve ne yapılırsa bu pratikten daha fazla faydalanılabileceğini çok güzel bir şekilde özetliyor. Bunların detaylarına https://www.martinfowler.com/articles/continuousIntegration.html adresinden ulaşabilirsiniz, fakat bu yazı dizisi boyunca, bunlara da zaman zaman değineceğiz.

Peki, bu aşamada biz ne yaptık? Hemen aktarayım.Updated CI/CD Pipeline

Gitlab’ın bizlere sunduğu sürekli entegrasyon kabiliyetlerini kullanıyoruz. Bunun için de yapmanız gereken tek şey. Repository’niz içerisinde, tepe dizinde, “.gitlab-ci.yml” dosyası oluşturmak ya da “CI/CD Configuration” düğmesine tıklamak:

Buna tıkladığınızda karşınıza aşağıdaki gibi bir betik geliyor olacak:

Şimdi buradaki anahtar kelimelere bir göz atalım.

Bu yazıda yine,çok detaya girmeden sadece kullandıklarıma bakacağız. İleride bir yazıyı sadece bu konuya ayırırz. Ama şimdiden daha derine inmek istiyorsanız https://docs.gitlab.com/ee/ci/quick_start/ adresine bir göz atabilirsiniz 😉

Uygulama izleme yazılımı için şimdilik tek aşamadan oluşan bir akış (“pipeline”) kullanıyor olacağız. Akış aslında, bizim sürekli entegrasyon kapsamında otomatikleştirmeye çalıştığımız aktiviteleri, aşamaları ve bu aşamalar kapsamında koşturulan işleri (“Jobs”) kapsar. Her bir iş için, betikler, ön koşullar ve bağımlılıklar tanımlanabilir.  Yukarıda verilen betik ile aslında basit bir akışı tanımlamış olduk. Ne içeriyor bu akış? Şimdilik sadece bir aşama (“stages”) içeriyor ve bu aşama oluşturma (“build”) aşamasıdır. Bu aşama içerisinde yine tek bir iş tanımlanmış durumda, o da “buildJob” işidir.

Tahmin edebileceğiniz üzere, bu aşamaların sayısı, uygulamanıza, ihtiyaçlarınıza göre değişiyor olacaktır ama en azından, oluşturma/test/konuşlandırma aşamalarını içeriyor olabilir. Bizim akışımız da inşallah yakında bu aşamalara sahip olacak 🙂 Bu aşamalar paralel olabileceği gibi genelde sıralı olarak da tasarlanabilmektedir. Aşağıda, daha karmaşık bir akış görebilirsiniz:

Bizim örneğimizden devam edelim. “image” anahtar kelimesine bakalım. Bu aslında, ilgili akış için hangi imaj’ın referans alınacağını ifade ediyor. Burada seçtiğiniz aslında Docker imajının ismi oluyor ve gitlab sizler için otomatik olarak bunu Docker Hub‘tan çekiyor. Bizim örneğimizde, uygulamamızda, QT’yi kullanacağımız için, Qt kurulu bir imaj seçtim. Siz de ihtiyaçlarınıza göre Docker Hub’tan bir imaj seçebilirsiniz.

Daha sonra, build aşaması tanımlanıyor ve bunun için de “buildJob” işi tanımlanıyor. Bunun altındaki “stage:” anahtar kelimesi ile, bu işin hangi aşamaya ait olduğunu belirtiyorsunuz.

Daha sonra verilen “script:” anahtar kelimesi ile de ilgili işe ilişkin büyünün yapıldığı, betiklerin tanımlandığı satırlar geliyor. Bu satırlar ile, ilgili işin ne yapacağını belirleyebiliyorsunuz. Burada basitçe üç/dört adımı içeriyor:

  • build dizini oluştur
  • dizin içerisine gir ve CMake ile gerekli oluşturma dosyalarını üret. Bizim durumumuzda, linux üzerinde makefile
  • daha sonra bu makefile dosyasını ve make komutunu kullanarak, projeyi oluştur
  • son olarak test’leri CMake alt yapısı ile koştur.

Şimdilik bu adımların hepsi tek bir iş içerisinde ama kodumuz büyüdükçe bunları birlikte farklı işlere ve aşamalara dağıtıyor olacağız 😉

Son olarak “artifacts:” anahtar kelimesi ile de, aşamalarda üretilen dosyalardan hangilerinin, akış tamamlandıktan sonra silinmemesini istediğinizi belirtiyorsunuz. Bizim örneğimizde tek bir aşama olduğu için bu pek anlamlı gelmeyebilir fakat çoklu aşamaları içeren akışlarda, aşamalar arası bazı verilerin aktarılmasını istiyor olabiliriz. O noktada “artifact” i oldukça sık kullanıyor olacağız.

Evet dostlar, bu yazımın diğer önemli bir konusu olan sürekli entegrasyona da dalmış bulunduk. Yazımı tamamlamadan önce değineceğim bir kaç konu daha var.

CMake’e Dair Notlar

Yazımı tamamlamadan önce CMake’e ilişkin yaptığım bir takım eklemeleri sizler ile paylaşmak istiyorum. Bu yazılar boyunca, CMake ile ilgili bir çok kod parçasını sizler ile paylaşıyor olacağım:

  • İlk eklediğimiz komut, debug derlemeler sonucu elde edilen çıktıların sonuna bir son ek eklenmesi. Bunun için aşağıdaki komutu kullanabilirsiniz. Bunu en tepedeki CMakeLists.txt içerisinde yaptığımız için alt seviyedeki bütün projeler bundan faydalanıyor olacaklar:
    • set(CMAKE_DEBUG_POSTFIX D)"
  • Diyelim ki, kütüphanelerinizin ve çalıştırılabilir dosyalarınızın belirli bir dizinde toplamak istiyorsunuz ki ben de genelde bu yöntemi tercih ediyorum. Bunu peki nasıl sağlayabiliriz? Hemen bakıyoruz:

    • Şimdi sizin kafanızda iki tane soru işareti var. Bir; dayak nedir? İki; neden atılır? Evet hatlar karıştı ama kafalarda bir takım soru işaretleri oluşmuş olabilir. Hemen açıklayalım. Öncelikle, “CMAKE_[ARCHIVE|LIBRARY|RUNTIME]_OUTPUT_DIRECTORY” değişkenlerinin az çok ne için kullanıldığını anlamışsınızdır. Peki “foreach” i ne için kullanıyoruz. Şunu için: özellik Visual Studio ve benzeri araçlar içerisinde sadece Debug ve Release değil, başka bir takım konfigürasyon tipleri de tanımlanmakta. Buradaki döngü ile bu dizinleri, her biri için aynı olacak şekilde ayarlayabiliyoruz. Elbette, bunların ayrı olmasını istiyorsanız ya da özel bir dizin, şu şekilde tanımlamalar yapabilirsiniz: CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG, CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE.

Sonuç

Evet dostlar, bu yazımızı da burada sonlandırıyoruz. Bir sonraki yazımda, CMake ile QT’yi nasıl kullanabiliriz. Statik kütüphaneler ve QT uygulamaları için nasıl konfigüre edebiliriz ona bakacağız.

Kaynaklar

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.