Gömülü Sistemlerde Test Güdümlü Geliştirme -2

Serbay Özkan
4 min readApr 30, 2020

--

Gömülü sistemlerde yazılım geliştirme süreçlerinde bir veya birden fazla nedenden dolayı aksamalar oluşabilmektedir. Zaman kaybı olarak karşımıza çıkan bazı başlıkları ele alırsak aşağıdaki gibi sıralayabiliriz.

  • Donanımın hazır olmaması
  • Donanımın istenilen şekilde çalışmaması
  • Donanımın çok pahalı ve oldukça az sayıda olması
  • Kullanılan toolchain’den kaynaklı maksimum kullanıcı lisans sınırlamasının olması

Gömülü yazılım geliştirme süreçlerinin doğası gereği çoğunlukla özel bir donanımla çalışıldığı için yukarıda belirtilen sorunlarla karşılaşmak mümkün olabilmektedir. Bu gibi sorunları çözmek için TDD bize Dual-Targeting dediğimiz çift yönlü yazılım geliştirme tekniğini sunmaktadır.

Dual-Targeting en baştan itibaren kodun hem hedef donanımda hem de host işletim sisteminde çalışacak şekilde tasarlanmasıdır. Buradaki amaç mümkün olabildiğince donanım bağımsız bir kod geliştirme ortamı oluşturmaktır. Bu yöntem ile donanım hazır olmasa bile yazılımın aslında büyük kısmı test edilebilmektedir. Özel bir donanım üzerinde çalışırken bir kod parçası yazıldıktan sonra derlenip önce işlemciye yüklenir daha sonra debug moduna geçilir ve kodun işleyişi donanım üzerinden takip edilir. Bu döngü gerçekten tam bir zaman kaybı oluşturmaktadır. Kullanılan JTAG emülatörüne göre bu süreç daha da uzayabilmektedir.

Dual-targeting bize donanım bağımsız kod yazmayı mümkün kıldığı için geliştirilen yazılımın farklı donanımlara entegre edilmesi doğal olarak daha kolay olmaktadır bu da tekrar kullanılabilirliği arttırmaktadır.

Şimdiye kadar bahsettiğim avantajların yanında tabii ki bazı riskler de mevcut ancak bunlar bu yöntemi kullanmayı engelleyecek bir neden olmamalıdır. Bu riskleri aşağıdaki gibi sıralayabiliriz.

  • Host işletim sisteminde kullanılan derleyici ile hedef donanım için kullanılan derleyici arasında farklılıkların olabilmesi
  • Host ve hedef donanım derleyicisinin kendine özgü ayrı ayrı buglarının olabilmesi
  • Runtime kütüphanelerde farklılıkların olabilmesi

Dual-targeting ile donanım bağımsız kod geliştirme süreci için TDD bize Test Double adı verilen bir yapıyı sunmaktadır. Test Double ile host işletim sisteminde yazdığımız modüllerde donanım bağımlı objeleri gerçek obje gibi tanıtıp birim testlere entegre edilebilmektedir. Test double Dummy, Fake, Stub ve Mock denilen dört farklı bileşenden oluşmaktadır. Bunlar ile ilgili yakın zamanda gördüğüm güzel bir yazı var buradan ayrıntılı bilgi edinebilirsiniz. Test double konusu yazımın referansı olan Test Driven Development for Embedded C (Pragmatic Programmers) kitabında da ayrıntılı olarak anlatılmıştır. Bu konuyu tek seferde sindirmek belki mümkün olmayabilir. Ben kitabı bitirdikten sonra test double bölümlerini birkaç defa daha okumak ve çalışmak zorunda kaldım.

Gömülü yazılım geliştirme sürecinde TDD geliştirme döngüsü aşağıdaki adımları içermektedir.

  • TDD Mikro Döngüsü
  • Derleyici Uyumluluk Kontrolü
  • Hedef Donanım Birim (Unit) Testi
  • Hedef Donanım Kabul (Acceptance) Testi

Şu ana kadar bahsedilen yapıları bir uygulama ile açıklamak daha anlaşılır olacaktır. Üzerinde 4 tane dijital çıkışı olan (röleye bağlı) bir cihaz için aşağıdaki isterleri sağlayan bir yazılım modülü geliştirilecektir.

  • Bütün çıkışlar aynı anda açılabilmelidir.
  • Bütün çıkışlar aynı anda kapatılabilmelidir.
  • Çıkışlar bağımsız olarak kontrol edilebilmelidir.
  • Dijital çıkışların durumları sorgulanabilmelidir.

Yukarıdaki gibi isterleri bulunan bir uygulamada ana referans noktası donanım üzerinde bulunan dijital çıkış birimi olsa da donanımdan bağımsız olarak yukarıdaki isterleri sağlayan ve host işletim sisteminde test edilebilen bir yazılım geliştirilebilir.

Uygulamayı Eclipse ile Cpputest birim test çerçeve yazılımını kullanarak gerçekleştireceğim. TDD ile uygulama geliştirileceği için TDD mikro döngüsü adımları uygulanacaktır.

  1. Test yazılır. Test yazarken en özelden en genele doğru gitmekte fayda vardır. Rölelerin açma/kapama operasyonlarında röle durumunun kontrolü için set/get/clear fonksiyonları kullanılacağından ilk önce bu fonksiyonların testi yazılır.

Testlerin hepsi moduleNameTest.cpp formatında yazılan kaynak dosyası içerisinde yer almaktadır. Test kaynak dosyasında relay_controller adlı bir test grubu ve getRelayStatus adında bir test yer almaktadır. Bu test içerisine testin amacına uygun olarak fonksiyon çağrımı gerçekleştirilir. Röle id bilgisine göre o id’ye sahip rölenin durumunu dönen ilgili fonksiyon getRelayStatus testinin varoluş sebebidir. Test yazıldıktan sonra kod derlenir. Derleme sonrasında relay_get_status henüz yazılımda var olmadığı için “relay_get_status was not declared in this scope” benzeri bir hata alınacaktır.

2. Testi yazılan fonksiyonun prototipi başlık (header) dosyasında tanımlanır ve kod tekrardan derlenir. Fonksiyon prototipi başlık dosyasında mevcut ama henüz kaynak dosyasında tanımlaması yapılmadığı için derleyeci tarafından “undefined reference to relay_get_status” hatası alınacaktır.

3. Kaynak (source) dosyasına relay_get_status fonksiyonun tanımlaması yapılır. Burada röle durum bilgisi alınacağı için dönüş değeri olarak boolean, fonksiyon argümanı olarak da röle id bilgisi sağlanmaktadır. Fonksiyonun içi asıl amacını yerine getirmek için düzenlenmeden önce hard code fake değerler ile testten kalınması sağlanır. Böylelikle fonksiyondan önce testin testi gerçekleştirilir.

4. Test fail ettikten sonra fonksiyonun içi asıl amacını gerçekleştirecek şekilde düzenlenir ve test geçene kadar bu işleme devam edilir.

5. Yazılan testlerde anlasılması zor olan ifadeler varsa temizlenir.

Bu tüm adımlar sırasıyla tekrar ve tekrar gerçekleştirilir. Her bir döngü tekrarında sisteme yeni bir özellik eklenir. Bir sonraki döngüde sırasıyla setRelayStatus, clearRelayStatus testleri yazılır ve bu testleri geçirecek kadar üretim kodu yazılır yani relay_set_status ve relay_clear_status fonksiyonları tanımlanır.

TDD mikro döngüsü tekrarlandıkça hem test hem de üretim kodu artmaya başlayacaktır. Röle durum kontrollerini yapan fonksiyonlardan sonra röleleri toplu olarak açıp/kapatan veya sadece spesifik bir röleyi açıp kapatacak testler yazılır. Daha sonra yine bu testleri geçirecek kadar üretim kodu yazılır.

Buradaki relay_gpio_on ve relay_gpio_off fonksiyonları aslında birer fonksiyon göstericisini (function pointer) referans almaktadır. Böylelikle donanıma bağlı ifadeleri üretim kodunda tamamen bağımsız bir hale getirmek mümkün olmaktadır. Donanım bağımlı GPIO kontrol fonksiyonlarını daha sonradan aşağıdaki gibi tanımlayarak daha temiz ve platform bağımsız bir kod yazabilmek mümkün olabilmektedir.

Platform bağımlı tüm kodlar port edilmesi amacıyla relayControllerPort.c içerisinde bulunmaktadır ve buradan fonksiyon göstericisi yardımıyla relay_gpio_on ve relay_gpio_off fonksiyonlarına referans verilmektedir. Bu kullanım Runtime-Bound Test Doubles olarak adlandırılmaktadır. Bu uygulamada yazılan tüm kodlara github linkinden erişerek daha detaylı incelemeniz mümkün olacaktır.

Burada runtime-bound test double kullanarak röle kontrolcü yazılım modülünü özel donanım bağımsız olarak Windows/Linux üzerinde Eclipse ile testlerini gerçekleştirme imkanı elde ettik. Bundan sonra entegrasyon ve kabul testleri için bu kaynak ve header dosyaları mikrokontrolcü yazılımını geliştirmek için kullanılan IDE’ye veya IDE kullanılmıyorsa ilgili text editor/derleyeci tool chain’e entegre edilir. Sadece relayControllerPort.c içerisinde yer alan donanım bağımlı ifadeler projede kullanılan işlemci BSP’leri kullanılarak doldurulur ve testler tekrardan yapılır.

Diğer yazılarımda görüşmek dileğiyle…

--

--

No responses yet