Decision Table Testing: Kural Kombinasyonları, Tabloyu Sadeleştirmek ve Kural Tablosundan Teste
Decision table testing, sonucu birden fazla koşulun kombinasyonuna bağlı kurallar için bütün kombinasyonları tabloya döker, sadeleştirir ve her sütunu bir teste çevirir. Bookstore'un kargo ücretinde cümle başına yazılan dört yeşil test, Plus üyesinin yurt dışı kargosundaki çelişkiyi ve yedi tanımsız kupon durumunu kaçırdı; tablo hepsini test yazmadan gösterdi. Limited ve extended-entry tablo, "–" ve N/A, iade onayı ve testi koddaki tablodan kopyalamamak.
Sürüm notu: Bu yazıdaki kodlar .NET 10 ve xUnit.net v3 4.0.3 ile derlenip denenmiştir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'da (bir online kitapçı) kargo ücreti yeniden düzenleniyor. Selin'in story'sinde dört cümle var: Plus üyelerinin kargosu her zaman ücretsiz. İndirimli ara toplamı 500 TL ve üstü olan siparişte kargo ücretsiz. Ücretsiz kargo kuponu kargo ücretini sıfırlar ve kupon kullanılmış sayılır. Yurt dışı gönderimlerde ücretsiz kargo uygulanmaz; yurt içi kargo 49,90 TL, yurt dışı 349,90 TL.
Emre, Refactoring serisinin "Koşul Yığınından Okunur Tasarıma" yazısındaki gibi iç içe if'ler yerine bir kural tablosu yazmış. İlk eşleşen kural kazanıyor:
namespace Bookstore.Shipping;
public enum Destination
{
Domestic,
International,
}
public sealed record ShippingRequest(
bool IsPlusMember, decimal DiscountedSubtotal, Destination Destination, bool HasFreeShippingCoupon);
public sealed record ShippingQuote(decimal Fee, bool CouponConsumed, bool CouponRejected);
public static class ShippingFeeCalculator
{
public const decimal DomesticFee = 49.90m;
public const decimal InternationalFee = 349.90m;
private sealed record Rule(Func<ShippingRequest, bool> Applies, Func<ShippingRequest, ShippingQuote> Quote);
// İlk eşleşen kural kazanır
private static readonly Rule[] Rules =
[
new(r => r.IsPlusMember, _ => new(0m, false, false)),
new(r => r.Destination == Destination.International,
r => new(InternationalFee, false, r.HasFreeShippingCoupon)),
new(r => FreeShippingPolicy.Qualifies(r.DiscountedSubtotal), _ => new(0m, false, false)),
new(r => r.HasFreeShippingCoupon, _ => new(0m, true, false)),
new(_ => true, _ => new(DomesticFee, false, false)),
];
public static ShippingQuote Quote(ShippingRequest request) =>
Rules.First(rule => rule.Applies(request)).Quote(request);
}
Testler de story'deki her cümle için bir tane:
// Story'deki her cümle için bir test
[Fact]
public void Plus_members_ship_free()
{
ShippingQuote quote = ShippingFeeCalculator.Quote(new(true, 120m, Destination.Domestic, false));
Assert.Equal(0m, quote.Fee);
}
[Fact]
public void Orders_from_500_try_ship_free()
{
ShippingQuote quote = ShippingFeeCalculator.Quote(new(false, 650m, Destination.Domestic, false));
Assert.Equal(0m, quote.Fee);
}
[Fact]
public void Free_shipping_coupon_is_consumed()
{
ShippingQuote quote = ShippingFeeCalculator.Quote(new(false, 120m, Destination.Domestic, true));
Assert.Equal(new ShippingQuote(0m, true, false), quote);
}
[Fact]
public void International_shipping_is_never_free()
{
ShippingQuote quote = ShippingFeeCalculator.Quote(new(false, 650m, Destination.International, false));
Assert.Equal(349.90m, quote.Fee);
}
Dört test de geçiyor. Almanya'ya kitap gönderen bir Plus üyesi ne kadar kargo öder? Kod ne diyor, story ne diyor?
Cevap: Story Kendisiyle Çelişiyor
Kod 0 TL diyor, çünkü Plus kuralı tablonun ilk satırında. Story ise iki cevap veriyor: ilk cümleye göre Plus üyesinin kargosu "her zaman" ücretsiz, dördüncü cümleye göre yurt dışında ücretsiz kargo yok. Emre kuralları yazarken farkında olmadan bir karar vermiş: tablonun sırası çelişkinin hangi taraftan çözüleceğini seçmiş. Kimse bu kararı onaylamadı, çünkü kimse çelişkiyi görmedi.
Testler de görmedi. Her cümleye bir test yazmak, her cümleyi diğerlerinden bağımsız denemek demek. Kurallar ise bağımsız değil, birbirine değiyor: Plus ile yurt dışı, kupon ile 500 TL eşiği, kupon ile yurt dışı. Hata tam bu kesişimlerde. Cümle cümle test yazmak, kesişimleri hiç sormuyor.
Bu tür kurallar için teknik decision table testing. Önce bütün kombinasyonları bir tabloda görür, sonra tabloyu sadeleştirip her sütunu bir teste çeviririz.
Koşullar, Aksiyonlar ve Tam Tablo
ISTQB (International Software Testing Qualifications Board; yazılım test sertifikalarını tanımlayan uluslararası kurul) syllabus'ına göre bir decision table (karar tablosu) iki parçadan oluşur: koşullar ve sistemin bunlara karşılık yaptığı aksiyonlar. Her sütun bir kuraldır, yani koşulların bir kombinasyonu ve o kombinasyonun aksiyonları. Koşullar yalnızca doğru ya da yanlış olabiliyorsa tablo limited-entry'dir (her hücrede T ya da F); koşullar aralık ya da partition gibi çok değerliyse extended-entry'dir. Gösterimde T koşulun sağlandığını, F sağlanmadığını, "–" koşulun o kural için önemsiz olduğunu, N/A da o kombinasyonun mümkün olmadığını anlatır.
Kargo kuralında dört koşul var: yurt dışı mı, Plus üyesi mi, indirimli ara toplam 500 TL ve üstü mü, ücretsiz kargo kuponu var mı. Aksiyonlar kargo ücreti ve kuponun ne olacağı. Dört boolean koşul 2⁴ = 16 kombinasyon yapar. Syllabus sütunları kural olarak çizer; burada okunması kolay olsun diye tabloyu çevirip her kuralı bir satıra koyuyoruz:
| Kural | Yurt dışı | Plus | 500 TL ve üstü | Kupon | Ücret | Kupona ne olur? |
|---|---|---|---|---|---|---|
| 1 | F | T | T | T | 0 | Tanımsız |
| 2 | F | T | T | F | 0 | – |
| 3 | F | T | F | T | 0 | Tanımsız |
| 4 | F | T | F | F | 0 | – |
| 5 | F | F | T | T | 0 | Tanımsız |
| 6 | F | F | T | F | 0 | – |
| 7 | F | F | F | T | 0 | Kullanılır |
| 8 | F | F | F | F | 49,90 | – |
| 9 | T | T | T | T | Çelişki | Tanımsız |
| 10 | T | T | T | F | Çelişki | – |
| 11 | T | T | F | T | Çelişki | Tanımsız |
| 12 | T | T | F | F | Çelişki | – |
| 13 | T | F | T | T | 349,90 | Tanımsız |
| 14 | T | F | T | F | 349,90 | – |
| 15 | T | F | F | T | 349,90 | Tanımsız |
| 16 | T | F | F | F | 349,90 | – |
Tabloyu doldurmak yarım saat sürdü ve üç şey çıktı. Dört kuralda (9–12) iki cümle farklı ücret söylüyor: çelişki. Yedi kuralda kuponun akıbeti tanımsız. Kargo zaten başka bir sebeple ücretsizken kupon kullanılmış sayılır mı (1, 3, 5)? Yurt dışında kupon ne olur, kullanılır mı, reddedilir mi, müşteriye bir şey söylenir mi (9, 11, 13, 15)? Ve Emre'nin dört testi on altı kuraldan yalnızca dördüne (4, 6, 7, 14) dokunuyor.
Syllabus decision table'ın gücünü tam burada görür: koşulların bütün kombinasyonlarını sistematik olarak önüne koyduğu için requirement'taki boşlukları ve çelişkileri de gösterir. Bu bulgular test yazmadan, tek satır kod çalıştırmadan çıktı.
Eksik ve Çelişkili Kuralları Kapatmak
Tablo Selin'e gidiyor, Selin de üç karar veriyor. Plus üyesinin yurt dışı kargosu ücretsiz değil, yarı fiyatına: 174,95 TL. Kargo zaten Plus ya da 500 TL eşiği sayesinde ücretsizse kupon kullanılmış sayılmaz, müşteride kalır. Yurt dışı gönderimde kupon geçersizdir: kullanılmaz ve müşteriye kuponun yurt dışında geçmediği söylenir.
Bu kararlar story'deki cümlelerin kendisini de değiştiriyor. "Her zaman ücretsiz" ifadesi gidiyor; yerine "yurt içinde ücretsiz, yurt dışında yarı fiyatına" geliyor. Decision table'ın requirement ile test arasındaki yeri bu: testi tasarlarken requirement'ı düzeltir. Bu yüzden tabloyu yalnızca test ekibinin dokümanı olarak değil, Requirements Analysis serisindeki example mapping oturumunun bir çıktısı olarak da düşünmek gerekir. Kırmızı kartla sorulan "kupon zaten ücretsizken ne olur?" sorusunun tablodaki karşılığı "Tanımsız" hücresi.
Tabloyu Sadeleştirmek
On altı kuralın her birini test etmek gerekmez. Syllabus iki sadeleştirme tanımlar: mümkün olmayan kombinasyonların sütunlarını silmek ve sonucu etkilemeyen bir koşulun farklı değerleriyle aynı aksiyonu veren sütunları "–" ile birleştirmek.
Bu tabloda mümkün olmayan kombinasyon yok; dört koşul birbirinden bağımsız. Birleştirme ise çok şey kazandırıyor. Yurt içinde Plus üyesi için ara toplam ve kupon önemsiz: kural 1–4 tek kurala iner. Yurt dışında 500 TL eşiği hiçbir şeyi değiştirmiyor, çünkü Selin'in kararından sonra yurt dışında eşik yok. Sonuç sekiz kural:
| Kural | Yurt dışı | Plus | 500 TL ve üstü | Kupon | Ücret | Kupon kullanılır | Kupon reddedilir |
|---|---|---|---|---|---|---|---|
R1 | F | T | – | – | 0 | ||
R2 | F | F | T | – | 0 | ||
R3 | F | F | F | T | 0 | X | |
R4 | F | F | F | F | 49,90 | ||
R5 | T | T | – | F | 174,95 | ||
R6 | T | T | – | T | 174,95 | X | |
R7 | T | F | – | F | 349,90 | ||
R8 | T | F | – | T | 349,90 | X |
Decision table testing'de coverage item'lar mümkün olan kombinasyonları taşıyan sütunlardır ve yüzde 100 coverage her sütunun en az bir testle denenmesi demektir. Sadeleştirilmiş tabloda bu sekiz test. Koşul sayısı arttıkça kural sayısı üstel büyür: on boolean koşul 1.024 kombinasyon eder. O noktada ya agresif biçimde sadeleştirilmiş bir tablo ya da riske göre seçilmiş sütunlar kullanılır. Hangi sütunların birleştirileceğini bulan sistematik algoritmalar var, ama syllabus bunları kapsam dışında bırakıyor; günlük işte yukarıdaki gibi elle birleştirmek çoğu zaman yeterli.
Sadeleştirmenin bir bedeli var: "–" ile birleştirilen her sütun artık tek bir testle temsil ediliyor. R1, Plus üyesinin kupon kullanıp kullanmadığından bağımsız olarak aynı sonucu iddia ediyor; test bu iddianın bütün kombinasyonlar için doğru olduğunu değil, seçilen temsilci için doğru olduğunu gösteriyor. Bu yüzden "–" yerine konacak değeri rastgele değil, en riskli olanı seçerek koyuyoruz.
Tablodan TheoryData'ya
Her satır bir test. "–" hücreleri için seçtiğimiz temsilciler iki şeye dikkat ediyor: kupon önemsizse bile kuponlu bir sepet seçiyoruz, çünkü kuponun kullanılmaması da bir aksiyon ve doğrulanması gerekiyor; 500 TL eşiği geçen her satırda da bir önceki yazıdaki sınır değerlerini, 500,00 ve 499,99'u kullanıyoruz.
namespace Bookstore.Shipping.Tests;
public sealed class ShippingFeeCalculatorTests
{
private const bool Plus = true;
private const bool Coupon = true;
// Sadeleştirilmiş decision table: her sütun bir test; "-" olan koşullar için temsilci değer
public static IEnumerable<TheoryDataRow<bool, decimal, Destination, bool, ShippingQuote>> DecisionTable =>
[
new(Plus, 120.00m, Destination.Domestic, Coupon, new(0m, false, false)) { Label = "R1" },
new(!Plus, 500.00m, Destination.Domestic, Coupon, new(0m, false, false)) { Label = "R2" },
new(!Plus, 499.99m, Destination.Domestic, Coupon, new(0m, true, false)) { Label = "R3" },
new(!Plus, 499.99m, Destination.Domestic, !Coupon, new(49.90m, false, false)) { Label = "R4" },
new(Plus, 650.00m, Destination.International, !Coupon, new(174.95m, false, false)) { Label = "R5" },
new(Plus, 120.00m, Destination.International, Coupon, new(174.95m, false, true)) { Label = "R6" },
new(!Plus, 650.00m, Destination.International, !Coupon, new(349.90m, false, false)) { Label = "R7" },
new(!Plus, 120.00m, Destination.International, Coupon, new(349.90m, false, true)) { Label = "R8" },
];
[Theory]
[MemberData(nameof(DecisionTable))]
public void Shipping_quote_follows_the_decision_table(
bool isPlusMember, decimal subtotal, Destination destination, bool hasCoupon, ShippingQuote expected)
{
ShippingQuote quote = ShippingFeeCalculator.Quote(new(isPlusMember, subtotal, destination, hasCoupon));
Assert.Equal(expected, quote);
}
}
Beklenen değer bir ShippingQuote record'u; record'ların değer eşitliği sayesinde tek bir Assert.Equal bütün aksiyonları birlikte kontrol ediyor: ücret, kuponun kullanılması ve reddedilmesi. Tablodaki bir satırın aksiyon sütunlarının hepsi testte de var. Plus ve Coupon sabitleri de satırları tabloya benzetmek için: !Plus bir hücredeki F'yi okumaktan daha kolay. Eski koda karşı çalıştırıyoruz; bu koşuda, Sık Düşülen Tuzaklar'da göstereceğimiz ve kodun tablosundan kopyalanan beş satırlık test de var:
failed Bookstore.Shipping.Tests.ShippingFeeCalculatorTests.Shipping_quote_follows_the_decision_table [R5] (1ms)
Assert.Equal() Failure: Values differ
Expected: ShippingQuote { Fee = 174.95, CouponConsumed = False, CouponRejected = False }
Actual: ShippingQuote { Fee = 0, CouponConsumed = False, CouponRejected = False }
failed Bookstore.Shipping.Tests.ShippingFeeCalculatorTests.Shipping_quote_follows_the_decision_table [R6] (0ms)
Assert.Equal() Failure: Values differ
Expected: ShippingQuote { Fee = 174.95, CouponConsumed = False, CouponRejected = True }
Actual: ShippingQuote { Fee = 0, CouponConsumed = False, CouponRejected = False }
Test run summary: Failed!
total: 18
failed: 2
succeeded: 16
skipped: 0
İki kırmızı da çelişkinin olduğu satırlarda. R6 ayrıca yurt dışında kuponun reddedilmediğini gösteriyor: Plus kuralı ilk sırada olduğu için yurt dışı kuralına hiç ulaşılmıyor. Düzeltme kural tablosunun sırasını değiştirmek ve Plus'ın yurt dışı kuralını eklemek:
// İlk eşleşen kural kazanır; yurt dışı kuralları ücretsiz kargo kurallarından önce
private static readonly Rule[] Rules =
[
new(r => r.Destination == Destination.International && r.IsPlusMember,
r => new(InternationalFee / 2, false, r.HasFreeShippingCoupon)),
new(r => r.Destination == Destination.International,
r => new(InternationalFee, false, r.HasFreeShippingCoupon)),
new(r => r.IsPlusMember, _ => new(0m, false, false)),
new(r => FreeShippingPolicy.Qualifies(r.DiscountedSubtotal), _ => new(0m, false, false)),
new(r => r.HasFreeShippingCoupon, _ => new(0m, true, false)),
new(_ => true, _ => new(DomesticFee, false, false)),
];
Test run summary: Passed!
total: 13
failed: 0
succeeded: 13
skipped: 0
duration: 307ms
İlk eşleşen kuralın kazandığı bir tabloda sıra, kodun bir parçası değil, iş kuralının bir parçası. Bu yüzden testin sırayla ilgili hiçbir varsayımı yok: decision table'daki her satır diğerlerinden bağımsız ve tek bir sonucu iddia ediyor.
Extended-entry Tablo: İade Onayı
Koşulların hepsi doğru ya da yanlış olmak zorunda değil. Bookstore'un back office'inde iade onayının kuralı şöyle: e-kitap indirilmişse iade reddedilir; indirilmemiş e-kitap teslimden sonraki 14 gün içinde onaylanır, sonrasında reddedilir. Basılı kitap kargoda hasar gördüyse 30 gün içinde otomatik onaylanır, sonrasında manuel incelemeye düşer. Hasarsız basılı kitap 14 gün içinde onaylanır, 15–30 gün arasında manuel incelemeye düşer, 30 günden sonra reddedilir.
Burada "teslimden bu yana geçen gün" boolean değil; partition'ları olan bir koşul: 14 ve altı, 15–30, 30'un üstü. Böyle tablolara extended-entry denir. Ayrıca iki kombinasyon mümkün değil: e-kitap kargoda hasar görmez, basılı kitap da indirilmez. Bu hücreler N/A olur ve test edilmez. Tablonun sekiz mümkün kuralı var: e-kitap için üç (E1–E3), basılı kitap için beş (P1–P5). Kod bir switch expression:
namespace Bookstore.Ordering;
public enum BookFormat
{
Printed,
EBook,
}
public enum ReturnDecision
{
Approve,
ManualReview,
Reject,
}
public sealed record ReturnRequest(BookFormat Format, int DaysSinceDelivery, bool DamagedInShipping, bool Downloaded);
public static class ReturnApprovalPolicy
{
public static ReturnDecision Decide(ReturnRequest request) => request switch
{
{ Format: BookFormat.EBook, Downloaded: true } => ReturnDecision.Reject,
{ Format: BookFormat.EBook, DaysSinceDelivery: <= 14 } => ReturnDecision.Approve,
{ Format: BookFormat.EBook } => ReturnDecision.Reject,
{ DamagedInShipping: true, DaysSinceDelivery: <= 30 } => ReturnDecision.Approve,
{ DamagedInShipping: true } => ReturnDecision.ManualReview,
{ DaysSinceDelivery: <= 14 } => ReturnDecision.Approve,
{ DaysSinceDelivery: <= 30 } => ReturnDecision.ManualReview,
_ => ReturnDecision.Reject,
};
}
Extended-entry tablo, decision table ile boundary value analysis'i doğal olarak birleştirir. Her sütunda gün koşulu bir partition; o partition'ın temsilcisini sınırdan seçersek, tek bir test hem kuralı hem sınırı kapsar. P4'ün partition'ı 15–30, iki sınırı da var; bu yüzden ona iki satır düşüyor:
namespace Bookstore.Ordering.Tests;
public sealed class ReturnApprovalPolicyTests
{
// Extended-entry tablo: gün sayısı partition'ları sınır değerleriyle temsil ediliyor
public static IEnumerable<TheoryDataRow<BookFormat, int, bool, bool, ReturnDecision>> DecisionTable =>
[
new(BookFormat.EBook, 14, false, false, ReturnDecision.Approve) { Label = "E1" },
new(BookFormat.EBook, 15, false, false, ReturnDecision.Reject) { Label = "E2" },
new(BookFormat.EBook, 3, false, true, ReturnDecision.Reject) { Label = "E3" },
new(BookFormat.Printed, 30, true, false, ReturnDecision.Approve) { Label = "P1" },
new(BookFormat.Printed, 31, true, false, ReturnDecision.ManualReview) { Label = "P2" },
new(BookFormat.Printed, 14, false, false, ReturnDecision.Approve) { Label = "P3" },
new(BookFormat.Printed, 15, false, false, ReturnDecision.ManualReview) { Label = "P4 lower" },
new(BookFormat.Printed, 30, false, false, ReturnDecision.ManualReview) { Label = "P4 upper" },
new(BookFormat.Printed, 31, false, false, ReturnDecision.Reject) { Label = "P5" },
];
[Theory]
[MemberData(nameof(DecisionTable))]
public void Return_decision_follows_the_decision_table(
BookFormat format, int daysSinceDelivery, bool damagedInShipping, bool downloaded, ReturnDecision expected)
{
ReturnDecision decision = ReturnApprovalPolicy.Decide(
new ReturnRequest(format, daysSinceDelivery, damagedInShipping, downloaded));
Assert.Equal(expected, decision);
}
}
Test run summary: Passed!
total: 59
failed: 0
succeeded: 59
skipped: 0
duration: 418ms
N/A kombinasyonları test etmiyoruz, ama ReturnRequest onları temsil edebiliyor: new ReturnRequest(BookFormat.EBook, 5, true, false) derlenir. Mümkün olmayan bir kombinasyonun tipte ifade edilebilmesi, bir gün birinin onu oluşturacağı anlamına gelir. Basılı ve dijital iade için ayrı record'lar kullanmak bu kombinasyonları derleyici seviyesinde ortadan kaldırır; bu bir tasarım kararı ve testten önce gelir.
Üretimde Dikkat
Decision table'ı story ile birlikte sakla. Tablo, requirement'ın en kesin hâli: kuralın her kombinasyon için ne yaptığını söyler. Kural değiştiğinde önce tablo değişir, sonra testler, en son kod. Tablonun test sınıfındaki TheoryDataRow satırlarıyla aynı ID'leri kullanması (R1–R8), kırmızı testten tablodaki satıra tek adımda dönmeyi sağlar.
Yeni bir koşul tabloyu ikiye katlar. Yarın "aynı gün teslimat" seçeneği gelirse koşul sayısı beşe, tam tablo 32 kombinasyona çıkar. Yeni koşulu eklerken önce tam tabloyu yeniden kur, sonra sadeleştir; mevcut sadeleştirilmiş tabloya bir sütun eklemek, yeni koşulun eski kurallarla kesişimlerini atlamak demektir.
Kural sırası üretimde de kod gibi review edilmeli. İlk eşleşen kuralın kazandığı bir tabloda iki satırın yer değiştirmesi, derleyicinin ve çoğu code review'un fark etmediği bir davranış değişikliğidir. Decision table testleri bu değişikliği yakalayan tek şey olabilir.
Aksiyonları log'la. Hangi kuralın uygulandığını (R6 gibi) sipariş kaydına ya da log'a yazmak, müşteri hizmetlerinden Gül'ün "bu müşteri neden 174,95 TL ödedi?" sorusunu koda bakmadan cevaplamasını sağlar.
Sık Düşülen Tuzaklar
Test tablosunu implementasyondan türetmek. Emre'nin kodu bir kural tablosu kullandığı için testi de aynı tablodan üretmek cazip görünür. Kodun tablosu okunup sırayla test satırlarına çevrilirse ortaya şu çıkar:
// Kodun kural tablosundan, aynı sırayla kopyalandı
[Theory]
[InlineData(true, 650.00, Destination.International, false, 0.00)]
[InlineData(false, 650.00, Destination.International, false, 349.90)]
[InlineData(false, 650.00, Destination.Domestic, false, 0.00)]
[InlineData(false, 120.00, Destination.Domestic, true, 0.00)]
[InlineData(false, 120.00, Destination.Domestic, false, 49.90)]
public void Fee_matches_the_rule_table(
bool isPlusMember, decimal subtotal, Destination destination, bool hasCoupon, decimal expectedFee)
{
Assert.Equal(expectedFee, ShippingFeeCalculator.Quote(new(isPlusMember, subtotal, destination, hasCoupon)).Fee);
}
Bu beş satır eski koda karşı yeşil geçti; ilk satır, yurt dışına gönderen Plus üyesinin 0 TL ödediğini "doğruluyor". Aynı hata iki kez yazılmış oluyor: bir kez kodda, bir kez testte. Decision table requirement'tan kurulur ve kodun tablosuyla hiç karşılaştırılmadan teste çevrilir.
Her cümleye bir test yazıp kombinasyonları atlamak. Story'nin cümleleri birbirinden bağımsız değilse, cümle başına bir test kesişimleri hiç denemez. Bu yazıdaki iki hata da kesişimdeydi.
"–" hücresine rastgele değer koymak. Önemsiz koşulun değeri, sonucu etkilemediği iddiasının test edildiği yer. Kuponu önemsiz saydığın bir satırda kuponsuz bir sepet seçersen, "kupon kullanılmaz" aksiyonunu hiç test etmemiş olursun.
Tabloyu bir kez kurup unutmak. Koşul eklendiğinde ya da bir kural değiştiğinde tablo yeniden kurulmazsa, testler eski kombinasyonları korumaya devam eder ve yeni kesişimler testsiz kalır.
Her şeyi decision table'a dökmek. İki koşullu bir kural için dört satırlık tablo kurmak gereksiz bir iş. Teknik, koşulların gerçekten birbirine değdiği ve sonucun kombinasyona bağlı olduğu yerde değer üretir.
Küçük Bir Deneme
★☆☆ Düzeltilmiş kodda r => r.IsPlusMember kuralını tablonun en başına geri taşı. Hangi satırlar kırmızıya döner? Kopyalanmış tablodan üretilen testler ne der?
★★☆ İade kuralına bir koşul ekle: kurumsal siparişlerde iade her zaman manuel incelemeye düşer. Tam tabloyu yeniden kur, N/A ve "–" hücrelerini işaretle, sadeleştir ve testleri güncelle. Kaç yeni sütun çıktı?
★★★ Kargo kuralına "aynı gün teslimat" koşulunu ekle: yalnızca yurt içinde mümkün, ek ücreti 39,90 TL ve Plus üyeleri için de ücretli. 32 kombinasyonluk tam tabloyu kur, mümkün olmayanları sil, sadeleştir ve kaç test gerektiğini hesapla. Selin'e soracağın soruları ayrıca listele.
Decision table koşulların kombinasyonuna bakıyordu, ama her kombinasyon tek bir anda değerlendiriliyordu. Bir sonraki yazıda sonucun geçmişe de bağlı olduğu kurallara geçiyoruz: state transition testing. Siparişin Created, Paid, Shipped, Delivered, Cancelled ve Returned state'lerinden test case'ler türetecek, geçersiz geçişleri bir tabloda toplayacak ve 0-switch ile 1-switch coverage'ın farkını göreceğiz.