Bölüm 11: Test ve Hata Ayıklama
Giriş
Öğrenme Çıktıları
Birim, modül, alt sistem, sistem ve kabul testini ayırt etmek; her birinin neyi doğruladığını açıklamak.
@Test, @BeforeEach ve assertEquals, assertThrows gibi doğrulama metotlarıyla otomatik testler yazmak, başarısız testin mesajını okumak.
Sınır değerlerini seçmek, parametreli test yazmak; testleri belirleyici ve tekrarlanabilir kurmak; regresyon testinin değerini açıklamak.
Kesme noktası koymak, programı adım adım (Step Into / Over / Out) yürütmek, değişkenleri izleyerek hatanın kaynağını bulmak.
Profil oluşturucu, kod kapsamı, statik analiz (SpotBugs), özellik tabanlı test, Selenium, Memory Analyzer ve VisualVM'in hangi soruyu yanıtladığını bilmek.
Bir sorun için test mi, hata ayıklayıcı mı, profil oluşturucu mu gerektiğine karar vermek.
Giriş
Test ve Hata Ayıklama: Neden?
“Test, hata bulmakla ilgili değildir; sistemin beklendiği gibi çalıştığından emin olmakla ilgilidir.”
Michael Bolton (yazılım test uzmanı)
“Geleceği tahmin etmenin en iyi yolu onu yaratmaktır.”
Sıklıkla Abraham Lincoln'e atfedilir; Peter Drucker ve Alan Kay'e de dayandırılır
“Hata (bug), henüz keşfedilmemiş bir özelliktir.”
Anonim yazılımcı esprisi
“Kalite bir eylem değil, bir alışkanlıktır.”
Aristoteles'e atfedilir; aslında Will Durant'ın Aristoteles yorumudur
“Test etmemek, yazılımla Rus ruleti oynamaya benzer.”
Anonim
“Test, hataların varlığını gösterebilir; yokluğunu asla gösteremez.”
Edsger W. Dijkstra
Giriş
Hata, Kusur, Arıza: Test ile Hata Ayıklama Farkı
| Test (testing) | Hata ayıklama (debugging) | |
|---|---|---|
| Amaç | Programın beklendiği gibi çalışıp çalışmadığını sınamak | Bilinen bir arızanın nedenini bulup düzeltmek |
| Soru | “Çalışıyor mu?” | “Neden çalışmıyor?” |
| Sonuç | Geçti / kaldı (✔ / ✘) | Hatalı satır ve düzeltme |
| Araçlar | JUnit, Selenium, kod kapsamı | Hata ayıklayıcı (debugger), izleme listesi, println |
| Ne zaman | Sürekli, her değişiklikten sonra | Bir test kaldığında ya da kullanıcı hata bildirdiğinde |
Test Türleri
Yazılım Geliştirmede Test Türleri
Birim Testi (Unit Testing)
- Bireysel, bağımsız bileşenlerin (bir sınıf, bir metot) test edilmesine odaklanır.
- Her bileşenin tek başına doğru çalıştığından emin olunur.
- Örnek:
BankaHesabi.cek()yetersiz bakiyede istisna fırlatıyor mu?
Birim testi: en küçük parça, en çok sayıda test, en hızlı çalışan.
Modül Testi (Module Testing)
- Birbiriyle ilgili bileşenlerden oluşan bir modülün test edilmesidir.
- Modül içindeki bileşenlerin entegrasyonu ve işlevselliği doğrulanır.
- Örnek:
Hesap,IslemveDekontsınıfları birlikte para transferini doğru yapıyor mu?
Modül testi: ilgili bileşenler bir arada.
Alt Sistem Testi (Sub-System Testing)
- Alt sistemler arasındaki arayüz uyumsuzluklarını test eder.
- Bileşenler arasındaki iletişim ve veri akışının doğruluğunu kontrol eder.
- Örnek: hesap alt sistemi ile veri tabanı alt sistemi aynı tarih biçimini mi kullanıyor?
Alt sistem testi: alt sistemlerin birleştiği arayüzler.
Sistem Testi (System Testing)
- Alt sistemler arasındaki etkileşimlerin bütün olarak test edilmesidir.
- İşlevsel (ne yapıyor) ve işlevsel olmayan (hız, güvenlik, kullanılabilirlik) gereksinimlerin karşılandığı doğrulanır.
- Örnek: 1000 eş zamanlı kullanıcıyla havale 2 saniyenin altında tamamlanıyor mu?
Sistem testi: bütün sistem, işlevsel ve işlevsel olmayan gereksinimler.
Kabul Testi (Acceptance Testing)
- Simüle veriler yerine gerçek verilerle sistemin test edilmesidir.
- Sistemin gerçek dünyadaki kullanılabilirliğini ve performansını doğrular.
- Kullanıcılar ile beraber yapılır: alfa testi geliştiricinin ortamında, beta testi kullanıcının kendi ortamında.
Kabul testi: müşteri “bu sistemi kabul ediyorum” diyebiliyor mu?
Test Türleri
Test Seviyeleri Bir Bakışta
| Seviye | Neyi test eder? | Kim yapar? | Veri |
|---|---|---|---|
| Birim | Tek sınıf / metot | Geliştirici | Küçük, elle seçilmiş |
| Modül | Birlikte çalışan birkaç sınıf | Geliştirici | Senaryo verisi |
| Alt sistem | Alt sistemler arası arayüzler | Geliştirici, test ekibi | Senaryo verisi |
| Sistem | Bütün sistem; hız, güvenlik | Test ekibi | Gerçeğe yakın |
| Kabul (alfa / beta) | Müşteri gereksinimleri | Kullanıcı ile birlikte | Gerçek veri |
Kodun içine bakmadan, yalnızca girdi → beklenen çıktı ile sınanır. Kabul ve sistem testleri çoğunlukla böyledir.
Kodun yapısı bilinerek her dal ve döngü sınanır. Birim testleri ve kod kapsamı bu bakış açısını kullanır.
Aşağı seviyedeki hata ucuz ve hızlı bulunur; aynı hata kabul testinde ya da kullanıcıda ortaya çıkarsa düzeltmesi çok daha pahalıdır.
Birim Testi ve JUnit
Önce: Elle Test Etmek
Test edilecek sınıf
public class HesapMakinesi { public int topla(int a, int b) { return a + b; } public int carp(int a, int b) { return a * b; } }
Elle deneme programı
public class ElleTest { public static void main(String[] args) { HesapMakinesi hm = new HesapMakinesi(); System.out.println("topla(2, 3) = " + hm.topla(2, 3)); System.out.println("carp(2, -3) = " + hm.carp(2, -3)); // Doğru mu? Ekrana bakıp kendimiz karar veriyoruz. } }
topla(2, 3) = 5 carp(2, -3) = -6
Bu yöntemin sorunları
- Sonucun doğru olduğuna insan gözü karar verir; yorulur, yanılır.
- Kod değişince her şeyi yeniden elle denemek gerekir.
- Test kodu
mainiçinde kalır, sonra silinir: emek kaybolur. - 100 metot için 100 çıktı satırını kim okuyacak?
Birim Testi ve JUnit
JUnit
- JUnit, Java için en yaygın birim testi çerçevesidir (framework). Testler sıradan Java metotları olarak yazılır, JUnit onları bulur, çalıştırır ve raporlar.
- Bir birim, Java'da genellikle bir sınıf anlamına gelir; testler o sınıfın metotlarını tek tek sınar.
- JUnit temel olarak birim testi için tasarlanmıştır ama oldukça esnektir; şu testlerde de kullanılabilir:
- Modül testi: birden fazla sınıfı içeren senaryoların test edilmesi.
- Sistem testi: sistem seviyesindeki etkileşimlerin test edilmesi.
- IntelliJ IDEA, Eclipse, NetBeans, VS Code ve Maven / Gradle JUnit testlerini doğrudan çalıştırır.
| Sürüm | Test nasıl yazılır? |
|---|---|
| JUnit 3 | TestCase sınıfından kalıtım, adı test ile başlayan metotlar |
| JUnit 4 | @Test anotasyonu, org.junit |
| JUnit 5 | @Test, org.junit.jupiter.api; lambda, parametreli test |
Bu bölümde JUnit 5 (Jupiter) kullanılır. Gradle ile projeye ekleme:
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")Test sınıfları src/test/java klasörüne, test edilen sınıflar src/main/java klasörüne konur.
Birim Testi ve JUnit
İlk JUnit 5 Testi
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class HesapMakinesiTest { @Test void ikiSayiyiToplar() { HesapMakinesi hm = new HesapMakinesi(); assertEquals(5, hm.topla(2, 3)); } @Test void negatifSayiylaCarpar() { HesapMakinesi hm = new HesapMakinesi(); assertEquals(-6, hm.carp(2, -3)); } }
- ✔
ikiSayiyiToplar() - ✔
negatifSayiylaCarpar()
@TestBu metodun bir test olduğunu JUnit'e söyler. Metot void ve parametresizdir; main gerekmez; public olması da gerekmez.
assertEquals(beklenen, gerçek)Önce beklenen değer, sonra gerçek sonuç. Eşit değilse test kalır (✘). import static sayesinde sınıf adı yazılmaz.
ikiSayiyiToplar gibi adlar, test kaldığında neyin bozulduğunu söyler.
Birim Testi ve JUnit
Doğrulama (Assertion) Metotları
| Metot | Ne zaman geçer? | Örnek (hepsi geçer) |
|---|---|---|
assertEquals(b, g) | b ve g eşit (equals) | assertEquals(5, 2 + 3) |
assertEquals(b, g, delta) | Ondalık sayılarda fark ≤ delta | assertEquals(0.3, 0.1 + 0.2, 1e-9) |
assertNotEquals(a, g) | Eşit değil | assertNotEquals(4, 2 + 3) |
assertTrue / assertFalse | Koşul doğru / yanlış | assertFalse("abc".isEmpty()) |
assertNull / assertNotNull | Değer null / null değil | assertNotNull("x") |
assertSame(a, b) | Aynı nesne (==) | assertSame(s, s) |
assertArrayEquals | Diziler eleman eleman eşit | assertArrayEquals(new int[] {1, 2}, new int[] {1, 2}) |
assertThrows(T.class, kod) | Kod o türde istisna fırlatır | assertThrows(ArithmeticException.class, () -> …) |
assertAll(…) | Tüm doğrulamalar (hepsi denenir) | assertAll(() -> …, () -> …) |
fail("mesaj") | Asla; testi bilerek düşürür | fail("henüz yazılmadı") |
Ondalıkta delta unutulursa assertEquals(0.3, 0.1 + 0.2) kalır: expected: <0.3> but was: <0.30000000000000004>. Son parametre isteğe bağlı bir mesajdır: assertEquals(4, 2 + 3, "toplam yanlış") → toplam yanlış ==> expected: <4> but was: <5>
Birim Testi ve JUnit
Test Edilecek Sınıf: BankaHesabi
public class BankaHesabi { private double bakiye; public BankaHesabi(double bakiye) { this.bakiye = bakiye; } public void yatir(double miktar) { if (miktar <= 0) { throw new IllegalArgumentException("Geçersiz miktar"); } bakiye += miktar; } public void cek(double miktar) { if (miktar > bakiye) { throw new IllegalStateException("Yetersiz bakiye"); } bakiye -= miktar; } public double getBakiye() { return bakiye; } }
Hangi davranışlar test edilmeli?
| Çağrı (bakiye 100) | Beklenen |
|---|---|
yatir(50) | bakiye 150 |
cek(30) | bakiye 70 |
cek(500) | IllegalStateException; bakiye 100 kalır |
yatir(-10) | IllegalArgumentException |
yatir(0) (sınır) | IllegalArgumentException |
cek(100) (sınır) | bakiye 0, istisna yok |
Her satır ayrı bir test metodu olur. Hem normal durumlar hem hatalı girdiler hem de sınırlar düşünülür.
Birim Testi ve JUnit
Bir Testin Yapısı: Hazırla, Çalıştır, Doğrula
class AaaTest { @Test void cekmeBakiyeyiAzaltir() { // Hazırla (Arrange) BankaHesabi hesap = new BankaHesabi(100); // Çalıştır (Act) hesap.cek(30); // Doğrula (Assert) assertEquals(70.0, hesap.getBakiye(), 0.001); } }
- ✔
cekmeBakiyeyiAzaltir()
Arrange: nesneyi ve verileri kur.
Act: test edilen metodu bir kez çağır.
Assert: sonucu beklenenle karşılaştır.
Her test tek bir davranışı sınar. Bakiye double olduğu için delta = 0.001 verilir.
Birim Testi ve JUnit
Ortak Hazırlık: @BeforeEach
BankaHesabiTest
class BankaHesabiTest { private BankaHesabi hesap; @BeforeEach void hazirla() { hesap = new BankaHesabi(100); } @Test void yatirmaBakiyeyiArtirir() { hesap.yatir(50); assertEquals(150.0, hesap.getBakiye(), 0.001); } @Test void fazlaCekmeIstisnaFirlatir() { assertThrows(IllegalStateException.class, () -> hesap.cek(500)); assertEquals(100.0, hesap.getBakiye(), 0.001); } }
@BeforeEachmetodu her testten önce çalışır: her test yeni ve temiz bir hesapla (bakiye 100) başlar.- JUnit her test metodu için test sınıfından yeni bir nesne oluşturur; bir testin değiştirdiği
hesapdiğerini etkilemez. assertThrows, ikinci parametredeki kodu çalıştırır ve o türde bir istisna fırlatılmasını bekler.() -> hesap.cek(500)bir lambda ifadesidir: “çalıştırılacak kod parçası”.- İstisnadan sonra bakiyenin değişmediği de doğrulanır.
- ✔
fazlaCekmeIstisnaFirlatir() - ✔
yatirmaBakiyeyiArtirir()
Birim Testi ve JUnit · Adım adım
JUnit Testleri Nasıl Çalıştırır?
JUnit sınıftaki @Test metotlarını bulur ve ilk test için yeni bir test nesnesi oluşturur. Alan hesap henüz null.
Testten önce @BeforeEach çalışır: yeni hesap, bakiye 100.
Sıra JUnit'tedir: önce fazlaCekmeIstisnaFirlatir çalıştı. assertThrows lambdayı çağırır: cek(500) istisna fırlatır.
İstisna beklenen türde: assertThrows geçer. Bakiye hâlâ 100: ikinci doğrulama da geçer.
İkinci test için yepyeni bir test nesnesi (#2). Önceki nesne ve hesabı artık kullanılmaz.
@BeforeEach yine çalışır: bu test de bakiye 100 ile başlar.
Çalıştır: yatir(50), bakiye 150 olur.
Doğrula: 150.0 = 150.0 (fark ≤ 0.001). İki test de geçti; ikisi de birbirinden bağımsız çalıştı.
class BankaHesabiTest { private BankaHesabi hesap; @BeforeEach void hazirla() { hesap = new BankaHesabi(100); } @Test void yatirmaBakiyeyiArtirir() { hesap.yatir(50); assertEquals(150.0, hesap.getBakiye(), 0.001); } @Test void fazlaCekmeIstisnaFirlatir() { assertThrows(IllegalStateException.class, () -> hesap.cek(500)); assertEquals(100.0, hesap.getBakiye(), 0.001); } }
Birim Testi ve JUnit
İstisnaları Test Etmek: assertThrows
class BankaIstisnaTest { @Test void fazlaCekmeMesajiDogru() { BankaHesabi hesap = new BankaHesabi(100); IllegalStateException e = assertThrows( IllegalStateException.class, () -> hesap.cek(500)); assertEquals("Yetersiz bakiye", e.getMessage()); } @Test void negatifYatirmaReddedilir() { BankaHesabi hesap = new BankaHesabi(100); assertThrows(IllegalArgumentException.class, () -> hesap.yatir(-10)); } @Test void yanlisBeklenti() { BankaHesabi hesap = new BankaHesabi(100); assertThrows(IllegalStateException.class, () -> hesap.cek(50)); } }
assertThrowsfırlatılan istisna nesnesini döndürür; mesajı da doğrulanabilir.- Hatalı girdinin reddedildiğini sınamak, doğru girdiyi sınamak kadar önemlidir.
- Son test bilerek yanlış: 50 TL çekmek geçerlidir, istisna yok.
- ✔
fazlaCekmeMesajiDogru() - ✔
negatifYatirmaReddedilir() - ✘
yanlisBeklenti()Expected java.lang.IllegalStateException to be thrown, but nothing was thrown.
JUnit 4'te: @Test(expected = …class).
Birim Testi ve JUnit
Test Yaşam Döngüsü Anotasyonları
class YasamDongusuTest { @BeforeAll static void basla() { System.out.println("BeforeAll"); } @BeforeEach void hazirla() { System.out.println(" BeforeEach"); } @Test void testA() { System.out.println(" testA"); } @Test void testB() { System.out.println(" testB"); } @AfterEach void temizle() { System.out.println(" AfterEach"); } @AfterAll static void bitir() { System.out.println("AfterAll"); } }
BeforeAll
BeforeEach
testA
AfterEach
BeforeEach
testB
AfterEach
AfterAll| Anotasyon | Ne zaman çalışır? |
|---|---|
@BeforeAll | Tüm testlerden önce bir kez (static) |
@BeforeEach | Her testten önce |
@AfterEach | Her testten sonra (test kalsa bile) |
@AfterAll | Tüm testlerden sonra bir kez (static) |
Testlerin kendi aralarındaki sırası belirli ama kod sırası değildir: testler birbirine bağımlı yazılmamalıdır.
Birim Testi ve JUnit
Başarısız Test: Kırmızıdan Yeşile
DiziIslem (ilk sürüm)
public class DiziIslem { public static int enBuyuk(int[] dizi) { int enb = 0; for (int i = 0; i < dizi.length; i++) { if (dizi[i] > enb) { enb = dizi[i]; } } return enb; } }
DiziIslemTest
class DiziIslemTest { @Test void pozitifDizi() { int[] dizi = {3, 9, 4}; assertEquals(9, DiziIslem.enBuyuk(dizi)); } @Test void tumuNegatifDizi() { int[] dizi = {-5, -2, -8}; assertEquals(-2, DiziIslem.enBuyuk(dizi)); } }
Kod derleniyor, ilk bakışta doğru görünüyor. İki test yazalım: biri pozitif, biri tamamen negatif bir dizi.
DiziIslem (ilk sürüm)
public class DiziIslem { public static int enBuyuk(int[] dizi) { int enb = 0; for (int i = 0; i < dizi.length; i++) { if (dizi[i] > enb) { enb = dizi[i]; } } return enb; } }
- ✔
pozitifDizi() - ✘
tumuNegatifDizi()org.opentest4j.AssertionFailedError: expected: <-2> but was: <0>
expected = beklenen (-2), but was = gerçekte dönen (0). 0 dizide yok bile!
Kırmızı: enb = 0 başlangıcı, tüm elemanlar negatifken hiç aşılmıyor.
DiziIslem (düzeltilmiş)
public class DiziIslem { public static int enBuyuk(int[] dizi) { int enb = dizi[0]; // ilk eleman for (int i = 1; i < dizi.length; i++) { if (dizi[i] > enb) { enb = dizi[i]; } } return enb; } }
- ✔
pozitifDizi() - ✔
tumuNegatifDizi()
En büyük değer dizinin ilk elemanıyla başlatılır. Bu test artık kalıcıdır: hata geri gelirse hemen yakalanır.
Yeşil: düzeltmeden sonra aynı testler yeniden çalıştırıldı, ikisi de geçti.
Birim Testi ve JUnit
Hangi Değerleri Test Etmeli? Sınır Değerleri
public class Not { public static String harfNotu(int puan) { if (puan < 0 || puan > 100) { throw new IllegalArgumentException("Geçersiz puan: " + puan); } if (puan >= 90) return "AA"; if (puan >= 80) return "BA"; if (puan >= 70) return "BB"; if (puan >= 60) return "CB"; if (puan >= 50) return "CC"; return "FF"; } }
- Hatalar en çok sınırlarda olur:
>yerine>=,< 100yerine<= 100… - Sınır değer analizi: her aralığın iki ucu (49 / 50, 89 / 90) ve geçersiz bölgenin ilk değeri (−1, 101) seçilir.
- Denklik sınıfları: aynı aralıktaki 61, 65, 68 aynı sonucu verir; bir tanesi yeterlidir.
Birim Testi ve JUnit
Parametreli Testler JUnit 5
class NotTest { @ParameterizedTest @CsvSource({"0, FF", "49, FF", "50, CC", "59, CC", "60, CB", "89, BA", "90, AA", "100, AA"}) void sinirDegerleri(int puan, String beklenen) { assertEquals(beklenen, Not.harfNotu(puan)); } @Test void aralikDisiPuanReddedilir() { assertThrows(IllegalArgumentException.class, () -> Not.harfNotu(-1)); assertThrows(IllegalArgumentException.class, () -> Not.harfNotu(101)); } }
@ParameterizedTest: aynı test metodu farklı verilerle tekrar tekrar çalışır.@CsvSource: her dizgi bir satır; virgülle ayrılan değerler sırayla parametrelere (puan,beklenen) dönüştürülür.- Bağımlılık:
junit-jupiter-params(junit-jupiterpaketine dahildir).
- ✔
[1] 0, FF - ✔
[2] 49, FF - ✔
[3] 50, CC - ✔
[4] 59, CC - ✔
[5] 60, CB - ✔
[6] 89, BA - ✔
[7] 90, AA - ✔
[8] 100, AA - ✔
aralikDisiPuanReddedilir()
Birim Testi ve JUnit
İyi Bir Birim Testinin Özellikleri: FIRST
Milisaniyeler içinde biter; binlercesi her değişiklikten sonra çalıştırılabilir. Dosya, ağ, veri tabanı yok.
Başka bir teste, test sırasına veya önceki testin bıraktığı duruma bağlı değildir.
Her makinede, her zaman aynı sonucu verir: rastgelelik ve saat kontrol altında.
Sonuç ✔ ya da ✘; çıktıyı bir insanın okumasına gerek yok.
Kodla birlikte (hatta koddan önce) yazılır; aylar sonraya bırakılmaz.
Açıklayıcı ad, AAA düzeni, test başına tek davranış. Test, kodun nasıl kullanılacağını gösteren bir belgedir.
Regresyon ve Otomasyon
Regresyon Testi (Regression Testing)
- Yapılan değişikliklerden sonra önceden çalışan her şeyin hâlâ çalıştığını doğrulamak anlamına gelir. Regresyon = “geriye gidiş”: düzelen bir şeyin yeniden bozulması.
- Yöntem: daha önce yazılmış tüm testler her değişiklikten sonra yeniden çalıştırılır.
Testler, aynı koşullarda her seferinde aynı sonucu vermelidir. Bazen geçip bazen kalan (“kararsız”, flaky) test güveni yok eder.
Testler farklı zamanlarda, farklı makinelerde ve durumlarda tekrar edilebilir şekilde tasarlanmalıdır.
Regresyon ve Otomasyon
Rastgelelik İçeren Kodu Belirleyici Test Etmek
import java.util.Random; public class Zar { private final Random rastgele; public Zar(Random rastgele) { // dışarıdan verilir this.rastgele = rastgele; } public int at() { return rastgele.nextInt(6) + 1; } }
Aynı tohumla (42) beş atış
3 4 1 3 1
class ZarTest { @Test void ayniTohumAyniSonuclar() { Zar z1 = new Zar(new Random(42)); Zar z2 = new Zar(new Random(42)); for (int i = 0; i < 100; i++) { assertEquals(z1.at(), z2.at()); } } }
Random nesnesi dışarıdan verilir (bağımlılık enjeksiyonu). Test, sabit tohum (seed) vererek her çalıştırmada aynı sayıları alır; zar sınıfı içinde new Random() yazılsaydı sonuçlar her seferinde değişirdi.
Regresyon ve Otomasyon
Otomatik Testler
- Kodu manuel olarak test etmek yerine, otomatik testler yazılmalıdır.
- Harcanan çaba aşağı yukarı aynıdır, ancak sonunda elinizde otomatik bir test kalır.
- Bu test, istenildiği zaman (her derlemede, her değişiklikte) tekrar çalıştırılabilir.
- Yazılım bir kez doğru çalıştı diye bir daha test etmenin gereksiz olduğunu düşünmek yanlıştır: kod değişir, kütüphaneler değişir, ortam değişir.
- Sürekli entegrasyon (CI, ör. GitHub Actions, Jenkins): her
git pushsonrası tüm testleri sunucuda otomatik çalıştırır; kırmızı test birleştirmeyi durdurur.
| Elle | Otomatik | |
|---|---|---|
| İlk maliyet | Düşük | Biraz yüksek |
| Tekrar maliyeti | Her seferinde aynı | ≈ 0 |
| Hız | Dakikalar | Saniyeler |
| Güvenilirlik | İnsan hatası | Her seferinde aynı |
| Regresyon | Pratikte yapılmaz | Her değişiklikte |
| Belge değeri | Yok | Kullanım örneği |
Regresyon ve Otomasyon
Test Güdümlü Geliştirme (TDD)
- Test-Driven Development: testi koddan önce yazmak.
- Kırmızı: henüz olmayan davranış için bir test yaz; kalır (ya da derlenmez).
- Yeşil: testi geçirecek en basit kodu yaz.
- Yeniden düzenle (refactor): kodu temizle; testler yeşil kaldıkça davranış korunur.
- Örnek: önce
assertEquals("CC", Not.harfNotu(50))testini yaz, sonraharfNotumetodunu. Her yeni not aralığı yeni bir döngüdür.
Hata Ayıklama
Hata Ayıklayıcı (Debugger)
Hata ayıklayıcı, çalışan bir programın durumunu incelemeye olanak sağlar. Hata ayıklayıcı ile yapılabilecekler:
- Programı komut komut (satır satır) çalıştırma
- Çalışan programın kaynak kodunu o anki satırla birlikte inceleme
- Değişkenlerin değerlerini görüntüleme ve gerektiğinde değiştirebilme
- Programa kesme noktaları (breakpoint) ekleyebilme
- Programı belirtilen bir kesme noktasına kadar çalıştırma
- Çökmüş ya da durdurulmuş programın durumunu bir döküm dosyası üzerinden inceleme (C'de core file; Java'da yığın dökümü
.hprofve iş parçacığı dökümü)
IntelliJ IDEA, Eclipse, NetBeans, VS Code: satır numarasının yanına tıklayarak kesme noktası, Debug düğmesiyle başlatma.
Hızlıdır ama kodu kirletir, yeniden derleme ister ve sonradan silinmesi unutulur. Hata ayıklayıcı kodu değiştirmez.
Hata Ayıklama
Kesme Noktaları ve Adım Komutları
| Komut | Ne yapar? | IntelliJ | Eclipse |
|---|---|---|---|
| Kesme noktası (breakpoint) | Program bu satıra gelince, satır çalışmadan önce durur | Ctrl+F8 | Ctrl+Shift+B |
| Step Over (üzerinden geç) | Satırı çalıştır; metot çağrısı varsa içine girmeden tamamla | F8 | F6 |
| Step Into (içine gir) | Satırdaki metot çağrısının içine gir | F7 | F5 |
| Step Out (dışarı çık) | Bulunulan metodu bitir, çağıran metoda dön | Shift+F8 | F7 |
| Resume (devam) | Bir sonraki kesme noktasına kadar çalış | F9 | F8 |
| Evaluate / Watch | Bir ifadenin (ör. dizi[i] > enb) değerini anlık hesapla, sürekli izle | Alt+F8 | Ctrl+Shift+I |
Yalnızca koşul doğruysa durur, ör. i == 999: binlerce turluk döngüde tam aranan anı yakalar.
Belirli bir istisna (ör. NullPointerException) fırlatıldığı anda durur.
Kısayollar varsayılan tuş eşlemesine göredir; menüden de (Run / Debug) aynı komutlara ulaşılır.
Hata Ayıklama · Adım adım
Hata Ayıklayıcı ile Kusuru Bulmak
Kesme noktası 5. satırda (if). Program Debug ile başlatıldı.
Step Into: enBuyuk çağrısının içine giriyoruz.
Yeni çerçeve: dizi aynı diziyi gösterir. enb henüz atanmadı.
Kesme noktası: enb = 0, dizi[0] = -5. İzlenen ifade false.
Resume: i = 1, -2 > 0 yine false; enb değişmedi.
i = 2, -8 > 0 false. Şüphe: enb hiç güncellenmiyor!
Döngü bitti, 0 döndürülüyor. Kusur bulundu: başlangıç değeri 0, tüm elemanlar negatifse yanlış. Doğrusu enb = dizi[0].
Arıza ekranda: En yüksek: 0. Düzeltmeden sonra bu durumu bir birim testi olarak ekleyin.
public class HataAyikla { public static int enBuyuk(int[] dizi) { int enb = 0; for (int i = 0; i < dizi.length; i++) { if (dizi[i] > enb) { enb = dizi[i]; } } return enb; } public static void main(String[] args) { int[] sicaklik = {-5, -2, -8}; int sonuc = enBuyuk(sicaklik); System.out.println("En yüksek: " + sonuc); } }
Hata Ayıklama · Adım adım
Step Into, Step Over, Step Out
Kesme noktasında durdu Debug
Kesme noktası 13. satırda: satır henüz çalışmadı.
Step Into F7
Step Into: kareToplam'ın içine girildi; yığına yeni çerçeve eklendi.
Step Over F8
Step Over: kare(3) çalıştı ama içine girilmedi; k1 = 9.
Step Into F7
Step Into: bu kez kare(4)'ün içindeyiz; yalnız x görünür.
Step Out Shift+F8
Step Out: kare bitti, 16 döndü; çağıran satıra dönüldü, atama tamamlanmak üzere.
Step Over F8
Step Over: k2 = 16.
Step Out Shift+F8
Step Out: kareToplam 25 döndürdü; main'e dönüldü.
Step Over F8
Step Over: s = 25.
Step Over F8
Step Over: çıktı yazıldı. Resume ile program sonlanır.
public class AdimOrnek { static int kare(int x) { return x * x; } static int kareToplam(int a, int b) { int k1 = kare(a); int k2 = kare(b); return k1 + k2; } public static void main(String[] args) { int s = kareToplam(3, 4); System.out.println("s = " + s); } }
Hata Ayıklama
Komut Satırında Hata Ayıklama: jdb
1public class Jdb { 2 static int kare(int x) { 3 int sonuc = x * x; 4 return sonuc; 5 } 6 7 public static void main(String[] args) { 8 int toplam = 0; 9 for (int i = 1; i <= 3; i++) { 10 toplam += kare(i); 11 } 12 System.out.println("Toplam: " + toplam); 13 } 14}
Derle ve hata ayıklayıcıyla başlat
javac -g Jdb.java jdb Jdb
-g: yerel değişken adlarını da sınıf dosyasına yazar; locals komutu bunlara ihtiyaç duyar.
jdb (Java Debugger), JDK ile gelen komut satırı hata ayıklayıcısıdır; IDE'lerin yaptığını komutlarla yapar.
| Komut | Anlamı |
|---|---|
stop at Sınıf:satır | kesme noktası koy |
clear Sınıf:satır | kesme noktasını kaldır |
run / cont | başlat / devam et |
step / next / step up | Step Into / Over / Out |
where | çağrı yığını |
locals, print x | yerel değişkenler, bir değer |
Çalışan bir uygulamaya bağlanmak için uygulama java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 Uygulama ile başlatılır; IDE ya da jdb -attach 5005 bağlanır.
Hata Ayıklama
Bir jdb Oturumu
> stop at Jdb:4 Deferring breakpoint Jdb:4. It will be set after the class is loaded. > run VM Started: Set deferred breakpoint Jdb:4 Breakpoint hit: "thread=main", Jdb.kare(), line=4 bci=4 main[1] where [1] Jdb.kare (Jdb.java:4) [2] Jdb.main (Jdb.java:10) main[1] locals Method arguments: x = 1 Local variables: sonuc = 1
main[1] cont Breakpoint hit: "thread=main", Jdb.kare(), line=4 bci=4 main[1] print sonuc sonuc = 4 main[1] clear Jdb:4 Removed: breakpoint Jdb:4 main[1] cont > Toplam: 14
- Kesme noktası 4. satırda (
return sonuc;): program bu satırı çalıştırmadan durur. where:main(10. satır) →kare.locals:x = 1,sonuc = 1.contile döngünün ikinci turunda yeniden durur:sonuc = 4.- Kesme noktası kaldırılınca program biter: 1 + 4 + 9 = 14.
Hata Ayıklama
assert ile Varsayımları Denetlemek
public class AssertOrnek { static double ortalama(int[] notlar) { assert notlar.length > 0 : "Dizi boş olamaz"; int toplam = 0; for (int n : notlar) { toplam += n; } return (double) toplam / notlar.length; } public static void main(String[] args) { System.out.println(ortalama(new int[] {70, 80})); System.out.println(ortalama(new int[0])); } }
75.0 NaN
75.0
Exception in thread "main" java.lang.AssertionError: Dizi boş olamaz
at AssertOrnek.ortalama(AssertOrnek.java:3)
at AssertOrnek.main(AssertOrnek.java:13)assert koşul : mesaj;koşul yanlışsaAssertionErrorfırlatır.- Varsayılan olarak kapalıdır;
-ea(enable assertions) ile açılır. Kapalıyken boş dizi sessizceNaNüretti. - Geliştirme sırasında “bu asla olmamalı” varsayımları içindir; kullanıcı girdisini doğrulamak için istisna kullanın.
Hata Ayıklama
Sistematik Hata Ayıklama
{-5, -2, -8}).Üretilemeyen hata ayıklanamaz.
Test, düzeltmenin işe yaradığını da gösterecek.
Değerin ilk kez yanlış olduğu satırı arayın.
enb 0 ile başladığı için güncellenmiyor” gibi bir açıklama önerin; hata ayıklayıcıda doğrulayın.Tahminle kod değiştirmek yeni hatalar doğurur.
Test kalıcı olarak test kümesinde kalır.
Analiz Araçları
Profil Oluşturucu (Profiler)
- Bir programın zamanını nerede harcadığını gösterir: hangi metot kaç kez çağrıldı, ne kadar sürdü, ne kadar bellek ayırdı.
- Profil oluşturucu çağrı grafiğini (call graph: kim kimi çağırdı) çeşitli biçimlerde gösterebilir: düz liste, ağaç, alev grafiği (flame graph).
- Teknik ayrıntılar, kullanılan derleyiciye ve JVM sürümüne göre farklılık gösterir.
- Kural: önce ölç, sonra iyileştir. Programın çoğu zaman küçük bir “sıcak nokta”da (hot spot) harcanır; tahminle optimizasyon çoğu kez yanlış yere yapılır.
java -XprofEski JVM'lerin yerleşik profil oluşturucusuydu; Java 10'da kaldırıldı. Java 21'de:
OpenJDK 64-Bit Server VM warning: Ignoring option -Xprof; support was removed in 10.0
JDK Flight Recorder (JFR) + JDK Mission Control, VisualVM, IDE profil oluşturucuları. Aşağıda: JFR kaydıyla çalıştır, sonra kaydın özetini al.
java -XX:StartFlightRecording=filename=kayit.jfr Profil jfr summary kayit.jfr
Analiz Araçları
Sıcak Noktayı Ölçmek
public class Profil { static String birlestirArti(int n) { String s = ""; for (int i = 0; i < n; i++) { s += i; // her turda yeni String } return s; } static String birlestirBuilder(int n) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { sb.append(i); } return sb.toString(); } // main: yandaki kutuda }
s += iher turda yeni bir String oluşturup eskisini kopyalar: toplam iş n² ile büyür.StringBuildertek bir tampona ekler: iş n ile büyür. Fark yüzlerce kat.
public static void main(String[] args) { long t0 = System.nanoTime(); String a = birlestirArti(50_000); long t1 = System.nanoTime(); String b = birlestirBuilder(50_000); long t2 = System.nanoTime(); long ms1 = (t1 - t0) / 1_000_000; long ms2 = (t2 - t1) / 1_000_000; System.out.println("+= : " + ms1 + " ms"); System.out.println("SB : " + ms2 + " ms"); System.out.println(a.equals(b)); }
+= : 873 ms SB : 2 ms true
Analiz Araçları
Kod Kapsamı Aracı (Coverage Tool)
- Test çalıştırması sırasında kodun hangi bölümlerinin çalıştırıldığını gösterir.
- Çalıştırılmayan veya kullanılmayan kod parçaları belirlenebilir.
- Test edilmemiş kod bölgelerini bulmaya yardımcı olur.
- Kod kapsamı, yazılımın güvenilirliğini artırmak için kritik bir araçtır.
- Araçlar: JaCoCo (Maven / Gradle), IntelliJ “Run with Coverage”, Eclipse EclEmma. Sonuç, kod satırları renklendirilerek gösterilir.
| Ölçüt | Ne sayılır? |
|---|---|
| Satır (line) | En az bir kez çalışan satırlar |
| Dal (branch) | Her if / döngü koşulunun hem doğru hem yanlış sonucu |
| Metot | En az bir kez çağrılan metotlar |
assert içermeyen bir test de kapsamı artırır.Analiz Araçları
Kod Kapsamı: Bir Örnek
Isaret: iki test sonrası kapsam
public class Isaret { public static String isaret(int x) { if (x > 0) { return "pozitif"; } else if (x < 0) { return "negatif"; } return "sıfır"; }}
çalıştı hiç çalışmadı
| Ölçüt | Kapsam |
|---|---|
| Satır (çalıştırılabilir 5 satır) | 4 / 5 = %80 |
| Dal (2 koşul × doğru / yanlış) | 3 / 4 = %75 |
IsaretTest
class IsaretTest { @Test void pozitif() { assertEquals("pozitif", Isaret.isaret(5)); } @Test void negatif() { assertEquals("negatif", Isaret.isaret(-3)); } }
İki test de yeşil (✔), ama x < 0 koşulunun yanlış dalı ve return "sıfır" hiç denenmedi. Kapsam raporu eksik testi gösterir: isaret(0).
Analiz Araçları
Statik Analiz: FindBugs ve SpotBugs
- FindBugs, Java kodunun statik analizi için kullanılan bir araçtır: programı çalıştırmadan derlenmiş kodu inceler.
- Kod hatalarını hızlı bir şekilde bulmaya yardımcı olur:
nullerişimi, yanlış karşılaştırma, kapatılmayan kaynak… - FindBugs artık geliştirilmiyor; devamı SpotBugs'tır (Maven, Gradle, IDE eklentileri). Benzerleri: PMD, Checkstyle, SonarQube.
import java.util.Scanner; public class SpotOrnek { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String cevap = sc.nextLine(); // sonuç kullanılmıyor: cevap.trim(); // String == ile karşılaştırılıyor: if (cevap == "evet") { System.out.println("Onaylandı"); } else { System.out.println("Reddedildi"); } } }
Girdi evet olduğu hâlde çıktı: Reddedildi. Derleyici sessiz kalır; SpotBugs iki satırı da işaretler.
| SpotBugs kalıbı | Uyarı |
|---|---|
ES_COMPARING_STRINGS_WITH_EQ | String nesneleri == ile karşılaştırılıyor; equals kullanın |
RV_RETURN_VALUE_IGNORED | trim() yeni bir String döndürür; sonuç atılıyor |
Analiz Araçları
İlk Statik Analizci: Derleyici (-Xlint)
1public class Lint { 2 public static void main(String[] args) { 3 int gun = 2; 4 switch (gun) { 5 case 1: 6 System.out.println("Pazartesi"); 7 case 2: 8 System.out.println("Salı"); 9 case 3: 10 System.out.println("Çarşamba"); 11 break; 12 } 13 int x = 10 / 0; 14 } 15}
Salı
Çarşamba
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Lint.main(Lint.java:13)Lint.java:13: warning: [divzero] division by zero
int x = 10 / 0;
^
Lint.java:7: warning: [fallthrough] possible fall-through into case
case 2:
^
Lint.java:9: warning: [fallthrough] possible fall-through into case
case 3:
^
3 warnings- Düz
javac Lint.javahiç uyarı vermeden derler. -Xlint:allşüpheli yapıları bildirir:breakunutulmuşcase'ler ve sabit sıfıra bölme.- Uyarıları okumak, hatayı çalıştırmadan önce bulmanın en ucuz yoludur.
Analiz Araçları
QuickCheck: Özellik Tabanlı Test
- QuickCheck testleri otomatik olarak üretir: girdileri programcı değil, araç rastgele seçer.
- Programcı tek tek örnek yerine bir özellik (property) yazar: “her girdi için doğru olmalı”.
- Özelliği bozan bir girdi bulunursa onu karşı örnek olarak raporlar (ve mümkünse küçültür).
- İlki Haskell içindir; Java dahil birçok dil için sürümleri vardır: jqwik, junit-quickcheck.
Yandaki test fikri düz JUnit ile uygular: 1000 rastgele metin.
- ✔
ikiKezTers…()
class OzellikTest { static String rastgeleMetin(Random r) { int uzunluk = r.nextInt(20); // 0..19 StringBuilder sb = new StringBuilder(); for (int i = 0; i < uzunluk; i++) { sb.append((char) ('a' + r.nextInt(26))); } return sb.toString(); } @Test void ikiKezTersCevirmekAyniMetniVerir() { Random r = new Random(42); // sabit tohum for (int k = 0; k < 1000; k++) { String s = rastgeleMetin(r); String t = MetinIslem.ters(s); assertEquals(s, MetinIslem.ters(t)); } } }
Analiz Araçları
Özellik Testi İlk Sürümdeki Hatayı Buluyor
class OzellikTest2 { @Test void enBuyukDizininEnBuyukElemanidir() { Random r = new Random(7); for (int k = 0; k < 1000; k++) { int[] dizi = new int[1 + r.nextInt(5)]; for (int i = 0; i < dizi.length; i++) { dizi[i] = r.nextInt(201) - 100; // -100..100 } int enb = DiziIslem.enBuyuk(dizi); boolean dizideVar = false; for (int x : dizi) { assertTrue(enb >= x); // özellik 1 if (x == enb) dizideVar = true; } assertTrue(dizideVar, // özellik 2 "Karşı örnek: " + Arrays.toString(dizi)); } } }
İlk (hatalı) enBuyuk sürümüyle çalıştırıldığında:
- ✘
enBuyukDizininEnBuyukElemanidir()Karşı örnek: [-60, -69, -39, -65, -13] ==> expected: <true> but was: <false>
- Özellik 1 (sonuç hiçbir elemandan küçük değil) hatalı sürümde de geçer: 0, tüm negatiflerden büyüktür.
- Özellik 2 (sonuç dizinin bir elemanıdır) hatayı yakalar.
- Düzeltilmiş sürümle 1000 girdinin tamamı geçer. Ders: özellikleri eksiksiz yazın; zayıf bir özellik hatalı kodu da onaylar.
Analiz Araçları
Selenium
- Selenium, web uygulamalarını test etmek için bir çerçevedir (framework).
- Tarayıcı tabanlı testleri kolaylaştırır: gerçek bir tarayıcıyı programla açar, bağlantıya tıklar, forma yazar, butona basar ve sayfada beklenen metnin çıktığını doğrular.
- Java, Python, C#, JavaScript gibi dillerle kullanılır; Java'da genellikle JUnit testinin içinden çağrılır.
Giriş sayfasını aç → kullanıcı adı ve parolayı yaz → “Giriş” → başlıkta “Hoş geldiniz” var mı?
Kullanıcının gördüğü bütünü sınar: sistem ve kabul testleri.
Birim testlerinden çok daha yavaş ve kırılgandır; sayısı az tutulur (piramidin tepesi).
Analiz Araçları
Bellek Sızıntısı Örneği
import java.util.ArrayList; import java.util.List; public class Sizinti { static List<byte[]> onbellek = new ArrayList<>(); static void istekIsle(int no) { byte[] veri = new byte[1024 * 1024]; // 1 MB onbellek.add(veri); // hiç silinmiyor! } public static void main(String[] args) { for (int i = 1; ; i++) { istekIsle(i); if (i % 20 == 0) { System.out.println(i + " istek işlendi"); } } } }
20 istek işlendi
…
120 istek işlendi
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Sizinti.istekIsle(Sizinti.java:8)
at Sizinti.main(Sizinti.java:14)Çöp toplayıcı (GC) yalnız erişilemeyen nesneleri siler; sızıntı, gereksiz nesnelere hâlâ bir referans kalmasıdır. -Xmx256m: yığın en çok 256 MB.
Analiz Araçları
Eclipse Memory Analyzer (MAT)
- Java yığınını (heap) analiz ederek bellek sızıntılarını tespit eder.
- Bellek optimizasyonu için kullanılır: hangi nesne ne kadar yer tutuyor, onları kim tutuyor?
- Girdi: bir yığın dökümü (heap dump,
.hprof): belirli bir andaki tüm nesnelerin fotoğrafı. - Önemli raporlar: Leak Suspects (sızıntı şüphelileri), Dominator Tree (en çok belleği kim tutuyor), Histogram (sınıf başına nesne sayısı).
Yığın dökümü almak
// bellek dolunca otomatik java -XX:+HeapDumpOnOutOfMemoryError Sizinti // çalışan programdan jps jcmd <pid> GC.heap_dump yigin.hprof
Leak Suspects raporu, yığının neredeyse tamamını Sizinti.onbellek listesinin ve içindeki byte[] dizilerinin tuttuğunu gösterir. Çözüm: işi biten veriyi listeden çıkarmak ya da boyutu sınırlı bir önbellek kullanmak.
Analiz Araçları
VisualVM
- Bellek tüketimini, iş parçacığı kullanımını, CPU yükünü ve daha fazlasını canlı olarak görselleştirir.
- Java uygulamalarının performansını izlemek ve analiz etmek için kullanılır: çalışan JVM'lere bağlanır, profil çıkarır, yığın ve iş parçacığı dökümü alır.
- Ücretsiz ve açık kaynaktır (visualvm.github.io); eskiden JDK ile birlikte
jvisualvmadıyla gelirdi. - Komut satırı karşılıkları:
jps(JVM'leri listele),jstat,jcmd,jconsole.
Yığın grafiği bir sızıntıyı ele verir: çöp toplamadan sonraki dipler sürekli yükseliyorsa nesneler serbest bırakılmıyordur. Sonra yığın dökümü alınıp MAT ile incelenir.
Özet
Özet: Hangi Soruya Hangi Araç?
| Soru | Teknik / araç |
|---|---|
| Bu metot doğru sonuç veriyor mu? | Birim testi: JUnit, @Test, assertEquals, assertThrows |
| Parçalar birlikte, bütün sistem ve kullanıcı gözünden çalışıyor mu? | Modül, alt sistem, sistem, kabul (alfa / beta) testi; web için Selenium |
| Değişiklik eski bir şeyi bozdu mu? | Regresyon testi: tüm otomatik testleri her değişiklikte (CI) çalıştır |
| Hangi girdileri seçmeliyim? | Sınır değerleri, denklik sınıfları, @ParameterizedTest |
| Seçmediğim girdilerde de doğru mu? | Özellik tabanlı test (QuickCheck, jqwik) |
| Testlerim kodun ne kadarını çalıştırıyor? | Kod kapsamı: JaCoCo, EclEmma |
| Kodda çalıştırmadan görülebilen hata var mı? | Statik analiz: javac -Xlint, SpotBugs (FindBugs) |
| Neden yanlış çalışıyor? | Hata ayıklayıcı: kesme noktası, Step Into / Over / Out, izleme; jdb |
| Zaman nerede harcanıyor? | Profil oluşturucu: JFR, VisualVM |
| Bellek neden doluyor? | VisualVM ile izle, yığın dökümünü Eclipse Memory Analyzer ile incele |
Özet
İlgili Sayfalar
JUnit 5 ile birim testi: doğrulama metotları, yaşam döngüsü, istisna testleri, parametreli testler, iyi test alışkanlıkları (İngilizce).
Singleton, Factory, Observer, MVC ve Strategy kalıpları; ne zaman kullanılır, ne zaman kaçınılır (İngilizce).
Zar örneğindeki Random gibi) hem testi hem de tasarımı kolaylaştırır.Özet
Sık Yapılan Hatalar
assertEquals(hm.topla(2, 3), 5) da çalışır ama kalınca mesaj ters okunur: expected gerçek sonucu gösterir.
assertEquals(0.3, 0.1 + 0.2) kalır: expected: <0.3> but was: <0.30000000000000004>.
import static eksikassertEquals(5, x) → derleme hatası: cannot find symbol: method assertEquals(int,int).
org.junit.Test ile org.junit.jupiter.api.Test farklıdır; yanlış import testlerin hiç çalışmamasına yol açabilir.
@BeforeAll metodu static değilJUnit 5 testleri çalıştırmaz: @BeforeAll method '…' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).
Bir testin bıraktığı nesneye ötekinde güvenmek: sıra değişince testler rastgele kalır. Her test kendi verisini kursun.
Yalnızca metodu çağırıp hiçbir assert yazmamak: test her zaman ✔ olur, hiçbir şey kanıtlamaz.
Sınırlar (0, boş dizi, negatif), geçersiz girdiler ve istisnalar unutulur; hatalar tam oradadır.
Özet
Örnek Problemler (1)
Problem 1: Hangi test geçer, hangisi kalır?
class SoruTest { @Test void a() { assertEquals(7, 3 + 4); } @Test void b() { assertEquals(0.3, 0.1 + 0.2); } @Test void c() { assertTrue("Java".length() == 4); } @Test void d() { assertThrows(ArithmeticException.class, () -> System.out.println(10 / 2)); } }
Problem 2
harfNotu içinde puan >= 50 yanlışlıkla puan > 50 yazılsaydı parametreli testin hangi satırı kalırdı? Mesaj ne olurdu?
Problem 3
Aşağıdaki fonksiyon için en az kaç test gerekir ki dal kapsamı %100 olsun? Girdileri seçin.
public class Mutlak { static int mutlak(int x) { if (x < 0) { return -x; } return x; } }
Cevapları göster
- a ✔; b ✘
expected: <0.3> but was: <0.30000000000000004>(delta yok); c ✔; d ✘Expected java.lang.ArithmeticException to be thrown, but nothing was thrown.(10 / 2 = 5 yazar). [3] 50, CCkalır:expected: <CC> but was: <FF>. 49 ve 59 yine geçer; hatayı yalnız tam sınır yakalar.- 2 test: biri negatif (ör. −3 → 3), biri sıfır ya da pozitif (ör. 5 → 5). Sınır olarak 0 da eklenmesi iyi olur.
Özet
Örnek Problemler (2)
Problem 4: Bu test sınıfının sorunu nedir?
class SayacTest { static int sayac = 0; @Test void artir() { sayac++; assertEquals(1, sayac); } @Test void ikiKezArtir() { sayac += 2; assertEquals(2, sayac); } }
Problem 5
AdimOrnek programında hata ayıklayıcı int s = kareToplam(3, 4); satırında durmuşken art arda iki kez Step Over yapılırsa hangi satıra gelinir, ekranda ne vardır ve s kaçtır?
Cevapları göster
staticalan iki test arasında paylaşılır; sonuç test sırasına bağlıdır. Hangisi ikinci çalışırsa kalır (önceartirçalışırsa:expected: <2> but was: <3>). Alanıstaticolmaktan çıkarın (JUnit her test için yeni nesne oluşturur) ya da@BeforeEach'te sıfırlayın.- Birinci Step Over satırı tamamlar (
s = 25) veprintlnsatırına gelir; ikincisis = 25yazdırır vemain'in kapanış satırına gelir.s= 25.
Özet
Örnek Problemler (3): Hangi Araç?
Problem 6: Her durum için hangi teknik ya da araç kullanılmalı?
- Program 10 dakika sonra
OutOfMemoryErrorile çöküyor. - Rapor oluşturma 30 saniye sürüyor; hangi metot yavaş?
- Yeni eklenen indirim kodu eski fatura hesabını bozdu mu?
- Web sitesindeki kayıt formu tüm tarayıcılarda çalışıyor mu?
switch'te unutulmuşbreak'ler var mı?- Bir döngü 10 000. turda yanlış değer üretiyor; o anda değişkenler ne?
Cevapları göster
- VisualVM ile yığın grafiğini izle; yığın dökümünü (
-XX:+HeapDumpOnOutOfMemoryError) Eclipse Memory Analyzer ile incele. - Profil oluşturucu: JDK Flight Recorder ya da VisualVM.
- Regresyon testi: tüm otomatik testleri yeniden çalıştır (tercihen CI ile her değişiklikte).
- Selenium ile tarayıcı tabanlı testler, farklı tarayıcı sürücüleriyle.
- Statik analiz:
javac -Xlint:all([fallthrough]), SpotBugs. - Hata ayıklayıcıda koşullu kesme noktası:
i == 9999.
Özet
Alıştırmalar (1)
Dikdortgensınıfı (en,boy,alan(),cevre()) için JUnit 5 test sınıfı yazın. Negatif kenar uzunluğuIllegalArgumentExceptionfırlatmalı.static boolean asalMi(int n)için@ParameterizedTestile sınır değerlerini (0, 1, 2, 3, 4, 97, 100) test edin.BankaHesabi'nahavale(BankaHesabi hedef, double miktar)ekleyin; önce testini yazın (TDD). Hangi testler gerekir?
Cevapları göster
- Testler:
alanDogru(3 × 4 → 12),cevreDogru(→ 14), sıfır kenar,assertThrows(IllegalArgumentException.class, () -> new Dikdortgen(-1, 2)). Ondalık kenarlarda delta kullanın. @CsvSource({"0, false", "1, false", "2, true", "3, true", "4, false", "97, true", "100, false"})veassertEquals(beklenen, asalMi(n)). 0, 1 ve 2 en çok hata yapılan sınırlardır.- Başarılı havale (iki bakiye de değişir), yetersiz bakiye (istisna, iki bakiye de değişmez), negatif miktar, kendine havale. Her test önce kırmızı, sonra yeşil.
Özet
Alıştırmalar (2)
- Bir dizinin ortalamasını hesaplayan metot bazı dizilerde yanlış sonuç veriyor (ör.
{1, 2}için 1.0). Hata ayıklayıcıyla kusuru bulma adımlarınızı yazın. - “Sıralanmış dizide her eleman bir sonrakinden büyük değildir” özelliğini 1000 rastgele dizi ile sınayan bir test yazın. Bu özellik tek başına yeterli mi?
Isaretsınıfının dal kapsamını %100 yapmak için hangi test eklenmeli?
Cevapları göster
- Kalan bir test yaz (
{1, 2}→ 1.5) → kesme noktasınıreturnsatırına koy →toplamve bölme ifadesini izle →toplam / dizi.lengthtam sayı bölmesi (3 / 2 = 1) olduğunu gör →(double) toplam / dizi.length→ tüm testleri çalıştır. - Yeterli değil: girdiyi yok sayıp hep boş ya da hep sıfırlardan oluşan dizi döndüren hatalı bir “sıralama” da bu özelliği sağlar. Sonucun girdinin bir permütasyonu olduğu (aynı elemanlar, aynı sayıda) da doğrulanmalıdır.
assertEquals("sıfır", Isaret.isaret(0)): hemx < 0koşulunun yanlış dalını hem de sonreturn'ü çalıştırır.
Özet
Kaynaklar
- JUnit Team, JUnit 5 User Guide, junit.org/junit5/docs/current/user-guide (Writing Tests, Assertions, Parameterized Tests).
- Oracle, The Java Tutorials ve JDK 21 Tool Specifications:
javac(-Xlint),jdb,jcmd,jfr. - Oracle, Programming With Assertions (Java SE belgeleri).
- J. Gosling ve diğ., The Java Language Specification, Java SE 21 Edition (14.10 The assert Statement).
- P. Deitel ve H. Deitel, Java: How to Program, 11. baskı, Pearson.
- C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, Pearson (Bölüm 7: Exceptions, Assertions, and Logging; hata ayıklama ipuçları).
- K. Beck, Test-Driven Development: By Example, Addison-Wesley.
- Araç belgeleri: SpotBugs (spotbugs.github.io), JaCoCo (jacoco.org), Selenium (selenium.dev), Eclipse Memory Analyzer (eclipse.dev/mat), VisualVM (visualvm.github.io), jqwik (jqwik.net).