İçeriğe geç
academia.sh

Ders 03 / 11

Keşifsel Test

Betiği önceden yazılmamış sınamanın bir bütçe kalemine dönüştürülmesi: oturum kapsamının genişlik ile derinlik arasında bölünmesi, betikli kümenin göremediği kusur sınıfının yalnız derinlikle görünmesi, bulunan kusurun betiğe dönüşme oranı ve yirmi sürümlük ufukta kaçan hata ile net bedelin ölçülmesi.

İçindekiler

Önceki dersin beş sırası da aynı on iki testin içinden seçim yaptı ve hiçbiri 93 puanlık tabana dokunamadı. O tabanı üreten iki sınıftan biri senaryo-disi: yazılmış hiçbir senaryonun geçmediği yollarda ortaya çıkan kusurlar. Bir sınıf, onu görecek bir test yazılmadığı sürece sıralamayla bulunamaz.

Keşifsel test (exploratory testing) betiği önceden yazılmamış, yönü sınama sırasında görülene göre belirlenen bir sınama biçimidir. Serbest olması yapılandırılmamış olduğu anlamına gelmez: sınayan bir oturum kapsamı yazar — hangi bölgeye, ne kadar süreyle, ne arayarak bakılacağı. Bu ders o kapsamı bir bütçe dağıtımı olarak kurar; dağıtılan kaynak insan dikkatidir ve iki yöne birden harcanamaz.

Genişlik ile Derinlik Aynı Bütçedendir

Sistemin sınanabilir yüzeyi iki boyutludur: kaç yol gezildiği (genişlik) ve her yolda ne kadar ileri gidildiği (derinlik). Bir oturumun bedeli bu ikisinin çarpımıdır, dolayısıyla sabit bütçede biri ötekinin karşılığıdır.

TS11 (varsayım) — sürüm başına keşifsel oturum bütçesi 120 dakikadır; bu, birinci dersin otomatik test bütçesiyle (TS3) aynı büyüklüktür ve seçimi duyarlılık taramasıyla sınanır. TS12 (varsayım) — bir yolu bir derinlik seviyesi kadar sınamak 6 dakika sürer. Bütçe böylece 20 yol-derinlik birimidir. TS13 (varsayım) — her kusur bir yolda ve bir derinlikte durur; senaryo dışı kalan iki sınıf tanımı gereği derindir (3–5), ötekiler 1–5 arasına dağılır. Bu, sınıfın tanımından gelen bir kısıttır: yazılı senaryoların geçtiği sığ yollarda duran bir kusur zaten betikli kümede görünürdü. TS14 (varsayım) — bulunan bir sınıf bir sonraki sürümden başlayarak betikli kümeye çevrilir; testsiz bir sınıf için 45 dakika yazım artı sürüm başına 5 dakika koşum, envanterde testi olan bir sınıf için yazım yoktur, bedel o testin sürüm başına dakikasıdır.

// kutuphane/envanter.mjs — birinci dersin envanterinden bu derste gereken alanlar: her testin
// kusur sinifi ve bedel nicelikleri (surec, cagri, elle adim; M21/K03 ve K04 kapanis
// tablolarindan). Dakikaya cevrim birinci dersteki TS1 ve TS2 ile, aynen.
const HAM = [["T01", "kural-siniri", 0, 266, 0], ["T02", "sema-uyusmazligi", 1, 772, 0],
  ["T03", "goc-veri-kaybi", 1, 21, 0], ["T04", "sozlesme-alani", 2, 100, 0],
  ["T05", "arayuz-durumu", 2, 16477, 0], ["T06", "gorsel-sapma", 1, 784, 1],
  ["T07", "veri-buyumesi", 9, 55000, 0], ["T08", "sql-birlestirme", 0, 75, 7],
  ["T09", "bilinen-acik", 0, 10, 5], ["T10", "erisilebilirlik", 0, 14, 6],
  ["T11", "bozulmus-yanit", 1, 13, 5], ["T12", "kurtarma-eksigi", 2, 7, 10]];
export const TESTLER = HAM.map(([kod, sinif, surec, cagri, elle]) =>
  ({ kod, sinif, dk: 0.5 * surec + 0.0002 * cagri + 6 * elle }));
const AGIRLIK = [["kural-siniri", 9], ["sema-uyusmazligi", 6], ["goc-veri-kaybi", 4],
  ["sozlesme-alani", 5], ["arayuz-durumu", 8], ["gorsel-sapma", 3], ["veri-buyumesi", 4],
  ["sql-birlestirme", 3], ["bilinen-acik", 3], ["erisilebilirlik", 4], ["bozulmus-yanit", 3],
  ["kurtarma-eksigi", 2], ["senaryo-disi", 5], ["anlam-sapmasi", 4]];
export function kusurlar(tohum, n) {
  let x = tohum;
  const rast = () => { x = (1103515245 * x + 12345) % 2147483648; return x / 2147483648; };
  const toplam = AGIRLIK.reduce((a, [, w]) => a + w, 0);
  const liste = [];
  for (let i = 1; i <= n; i += 1) {
    let p = rast() * toplam, sinif = AGIRLIK[AGIRLIK.length - 1][0];
    for (const [s, w] of AGIRLIK) { p -= w; if (p < 0) { sinif = s; break; } }
    liste.push({ no: i, sinif, olasilik: 1 + Math.floor(rast() * 5),
      etki: 1 + Math.floor(rast() * 5), duzeltme: 15 + 15 * Math.floor(rast() * 8) });
  }
  return liste;
}

Dört Kapsam, Yirmi Sürüm

Dört dağıtım aynı 120 dakikayı böler: yirmi yolu bir derinliğe kadar (E1), on yolu iki (E2), beş yolu dört (E3), dört yolu beş (E4). Kapsamın hangi yolları kapsayacağı bir bilgi sorunudur — plan kusurun nerede olduğunu bilmez, bu yüzden yol seçimi tohumlu ve rastgeledir ve iki yüz çizimin ortalaması alınır. Ölçüm yirmi sürümlük ufuk boyunca yürür: her sürümde üç kusur ortaya çıkar, bir oturum koşar, bulunan sınıf sonraki sürümden başlayarak betikli kümeye girer.

// kutuphane/kesif.mjs — kesifsel oturumu bir butce dagitimi olarak olcer.
// Girdi: kutuphane/envanter.mjs (birinci dersten devralinan test kumesi ve kusur kumesi).
import { TESTLER, kusurlar } from "./envanter.mjs";

const TOHUM = 20260731, N = 60, YOL = 20, BIRIM = 6, OTURUM = 120, CIZIM = 200;
const KAT = 6, ADAY = 20, PAY = N / ADAY;                        // TS5, TS6: surum basina 3 kusur
// Ikinci dersin yogunluk sirasinin butceye sigan dokuz kalemi; kalan siniflar betiksizdir.
const BETIKLI = new Set(["T01", "T02", "T03", "T04", "T05", "T06", "T07", "T08", "T09"]
  .map((k) => TESTLER.find((t) => t.kod === k).sinif));
// Betiksiz siniflar ikiye ayrilir: envanterde testi olan ama butceye girmeyenler (VARTEST)
// ve hicbir testin gormedigi ikisi (DERIN).
const VARTEST = new Map(TESTLER.filter((t) => BETIKLI.has(t.sinif) === false).map((t) => [t.sinif, t]));
const DERIN = new Set(["senaryo-disi", "anlam-sapmasi"]);
const v = (x, n = 1) => x.toFixed(n).replace(".", ",");
const g = (x, n) => String(x).padStart(n);
const uret = (t) => { let x = t;
  return () => { x = (1103515245 * x + 12345) % 2147483648; return x / 2147483648; }; };

// TS13: her kusur bir yolda ve bir derinlikte durur. Senaryo disi kalan iki sinif taniminca
// derindir (3-5); otekiler 1-5. Yol ve derinlik tohum 31417'den gelir.
const r0 = uret(31417);
const KUSUR = kusurlar(TOHUM, N).map((k) => ({ ...k, yol: 1 + Math.floor(r0() * YOL),
  derinlik: DERIN.has(k.sinif) ? 3 + Math.floor(r0() * 3) : 1 + Math.floor(r0() * 5) }));
const BETIKSIZ = KUSUR.filter((k) => BETIKLI.has(k.sinif) === false);

// Bir oturum p yolu d derinligine kadar gezer; bedeli p*d*BIRIM dakikadir. Kusur, yolu
// gezildiyse ve derinligi d'yi asmiyorsa bulunur; bulma ani o yolun sirasindan gelir.
const gez = (r, p) => { const y = Array.from({ length: YOL }, (_, j) => j + 1);
  for (let j = YOL - 1; j > 0; j -= 1) { const q = Math.floor(r() * (j + 1));
    [y[j], y[q]] = [y[q], y[j]]; }
  return y.slice(0, p); };

// Ufuk: ADAY surum, surum basina PAY kusur ve bir oturum. Bulunan sinif bir sonraki surumden
// baslayarak betige cevrilir (TS14) ve o siniftan sonra kusur kacmaz.
function ufuk(p, d, tohum, butce = OTURUM) {
  const r = uret(tohum), yolSayisi = Math.min(p, Math.floor(butce / (BIRIM * d))), o = [];
  for (let c = 0; c < CIZIM; c += 1) {
    const cevrik = new Map(); let bulunan = 0, an = 0, kacan = [];
    for (let s = 1; s <= ADAY; s += 1) {
      const yer = new Map(gez(r, yolSayisi).map((y, i) => [y, (i + 1) * d * BIRIM]));
      const yeni = [];
      for (const k of KUSUR.slice((s - 1) * PAY, s * PAY)) {
        if (BETIKLI.has(k.sinif) || cevrik.has(k.sinif)) continue;
        if (yer.has(k.yol) && k.derinlik <= d) { bulunan += 1; an += yer.get(k.yol); yeni.push(k.sinif); }
        else kacan.push(k);
      }
      for (const x of yeni) if (cevrik.has(x) === false) cevrik.set(x, s);
    }
    const bedel = [...cevrik].reduce((a, [x, s]) => a + (ADAY - s) *
      (VARTEST.has(x) ? VARTEST.get(x).dk : 5) + (VARTEST.has(x) ? 0 : 45), 0);
    o.push({ bulunan, an: an / (bulunan || 1), cevrik, bedel,
      testsiz: [...cevrik.keys()].filter((x) => DERIN.has(x)).length,
      disi: [...cevrik.keys()].filter((x) => VARTEST.has(x)).length,
      kacan: kacan.length, risk: kacan.reduce((a, k) => a + k.olasilik * k.etki, 0),
      duz: kacan.reduce((a, k) => a + k.duzeltme, 0) });
  }
  const ort = (f) => o.reduce((a, x) => a + f(x), 0) / CIZIM;
  return { p: yolSayisi, d, butce, bulunan: ort((x) => x.bulunan), an: ort((x) => x.an),
    testsiz: ort((x) => x.testsiz), disi: ort((x) => x.disi), kacan: ort((x) => x.kacan),
    risk: ort((x) => x.risk), bedel: ort((x) => x.bedel), duz: ort((x) => x.duz) };
}
const TABANDUZ = BETIKSIZ.reduce((a, k) => a + k.duzeltme, 0);
const net = (o) => KAT * (TABANDUZ - o.duz) - ADAY * o.butce - o.bedel;

console.log(`kusur kumesi tohum ${TOHUM}, ${N} kusur; yol ve derinlik tohum 31417, ${YOL} yol`);
console.log(`betikli kume ikinci dersin dokuz kalemi -> ${BETIKLI.size} sinif kapali; ` +
  `ufukta ${BETIKSIZ.length} betiksiz kusur, ${BETIKSIZ.filter((k) => DERIN.has(k.sinif)).length}'i testsiz iki sinifta`);
console.log("derinlik dagilimi (kusur sayisi): " + [1, 2, 3, 4, 5]
  .map((d) => d + ":" + KUSUR.filter((k) => k.derinlik === d).length).join("  "));

console.log(`\noturum butcesi TS11 = ${OTURUM} dk = ${OTURUM / BIRIM} yol-derinlik birimi; ` +
  `${ADAY} surum, ${CIZIM} cizim ortalamasi`);
console.log("kapsam                  yol  derinlik   bulunan   butce disi   testsiz   donusum   bulma ani");
const D = [[20, 1], [10, 2], [5, 4], [4, 5]].map(([p, d], i) => ufuk(p, d, 5501 + i));
D.forEach((o, i) => console.log(`E${i + 1} ${o.p} yol x derinlik ${o.d}`.padEnd(24) +
  g(o.p, 3) + g(o.d, 10) + g(v(o.bulunan, 2), 10) + g(v(o.disi, 2), 13) + g(v(o.testsiz, 2), 10) +
  g(v((o.disi + o.testsiz) / o.bulunan, 2), 10) + g(v(o.an), 12)));

console.log("\nufkun sonu — ikinci dersin yogunluk sirasi tek basina 18 kusur kaciriyordu");
console.log("kapsam   kacan hata   kacan risk   oturum dk   donusum dk   kacan hatadan kazanc   net dk");
D.forEach((o, i) => console.log(`E${i + 1}`.padEnd(9) + g(v(o.kacan, 2), 10) + g(v(o.risk), 13) +
  g(ADAY * o.butce, 12) + g(v(o.bedel, 0), 13) + g(v(KAT * (TABANDUZ - o.duz), 0), 23) + g(v(net(o), 0), 9)));
console.log(`oturumsuz taban: 18 kacan, 155 risk, duzeltme ${TABANDUZ} dk (TS5 ile ${KAT * TABANDUZ} dk)`);

console.log("\nTS11'in duyarliligi — E4'un derinligi (5) uc butcede; 120 dk satiri E4'un ta kendisi");
console.log("butce dk   gezilen yol   bulunan   testsiz   kacan hata   net dk");
for (const b of [60, 120, 240]) { const o = ufuk(YOL, 5, 5504, b);
  console.log(g(b, 8) + g(o.p, 14) + g(v(o.bulunan, 2), 10) + g(v(o.testsiz, 2), 10) +
    g(v(o.kacan, 2), 13) + g(v(net(o), 0), 9)); }
kusur kumesi tohum 20260731, 60 kusur; yol ve derinlik tohum 31417, 20 yol
betikli kume ikinci dersin dokuz kalemi -> 9 sinif kapali; ufukta 18 betiksiz kusur, 10'i testsiz iki sinifta
derinlik dagilimi (kusur sayisi): 1:18  2:10  3:13  4:8  5:11

oturum butcesi TS11 = 120 dk = 20 yol-derinlik birimi; 20 surum, 200 cizim ortalamasi
kapsam                  yol  derinlik   bulunan   butce disi   testsiz   donusum   bulma ani
E1 20 yol x derinlik 1   20         1      3,00         3,00      0,00      1,00        64,4
E2 10 yol x derinlik 2   10         2      2,04         2,04      0,00      1,00        63,0
E3 5 yol x derinlik 4     5         4      2,53         1,35      1,12      0,98        73,3
E4 4 yol x derinlik 5     4         5      2,75         1,29      1,42      0,98        74,8

ufkun sonu — ikinci dersin yogunluk sirasi tek basina 18 kusur kaciriyordu
kapsam   kacan hata   kacan risk   oturum dk   donusum dk   kacan hatadan kazanc   net dk
E1            12,00        114,0        2400         1281                   2610    -1071
E2            13,85        126,5        2400          867                   1881    -1386
E3            11,32        104,1        2400          729                   2831     -298
E4            10,39         95,2        2400          735                   3280      145
oturumsuz taban: 18 kacan, 155 risk, duzeltme 1260 dk (TS5 ile 7560 dk)

TS11'in duyarliligi — E4'un derinligi (5) uc butcede; 120 dk satiri E4'un ta kendisi
butce dk   gezilen yol   bulunan   testsiz   kacan hata   net dk
      60             2      1,57      0,90        13,81      202
     120             4      2,75      1,42        10,39      145
     240             8      4,09      1,79         6,27     -923

Genişliğin Göremediği Sınıf

E1 yirmi yolun hepsini gezer ve bulabileceği her şeyi bulur: derinlik 1’de duran üç betiksiz kusur. Bu üçü tam sayıdır, çizimden çizime değişmez — tam genişlikte rastlantı kalmaz. Buna karşılık E1’in testsiz sütunu 0,00‘dır ve ne kadar çizim yapılırsa yapılsın 0,00 kalır. Nedeni bir olasılık değil bir kısıttır: senaryo dışı kalan iki sınıfın kusurları 3’ten sığ bir derinlikte durmaz, çünkü sığ olsalardı yazılı senaryolar zaten oradan geçerdi.

E4 aynı bütçeyle yalnız dört yola bakar ve daha az kusur bulur (2,75’e karşı 3,00) — ama bulduklarının 1,42’si testsiz sınıftandır. Genişlik daha çok kusur bulur, derinlik başka türden kusur bulur. İki dağıtım arasındaki fark bir verimlilik farkı değil, bir erişim farkıdır; E1’in eksiği daha az çalışması değil, bakmadığı bir yerin olmasıdır.

Bulma anı bu erişimin bedelini gösterir: 63,0 ile 74,8 dakika arasında. Önceki dersin yoğunluk sırası kusuru ortalama 18,2 dakikada haber veriyordu; keşifsel oturum dört katına yakın bir gecikmeyle haber verir ve gecikme derinlikle birlikte artar, çünkü derin bir yolun sonuna varmak zaman alır.

Dönüşüm Oranı ve İki Tür Bulgu

Bulunan kusurun betikli kümeye dönüşme oranı dört dağıtımda da 0,98 ile 1,00 arasındadır: oturumun bulduğu hemen her kusur yeni bir betikli teste yol açar. Bu, göründüğü kadar iyi bir haber değildir, çünkü dönüşenler iki türdür.

Bütçe dışı sınıf, envanterde testi olan ama 120 dakikaya sığmadığı için plandan düşmüş sınıftır — E1’in bulduğu üç sınıfın üçü de budur. Böyle bir bulgu yeni bilgi getirmez; birinci dersin bütçe kararını tekrar sorar ve dönüşümü de o kararın bedelini geri getirir: E1’in dönüşüm bedeli 1.281 dakikadır, çünkü çevrilen testler kalan her sürümde koşar. Testsiz sınıf ise envanterde karşılığı olmayan sınıftır; dönüşümü 45 dakika yazım artı sürüm başına 5 dakikadır ve E4’ün 735 dakikalık dönüşüm bedelinin küçük parçasıdır.

Ayrım kararı değiştirir. Bütçe dışı bir sınıfın bulunması, keşifsel oturumun değil planın sonucudur ve cevabı bir oturum değil bir bütçe tartışmasıdır. Testsiz bir sınıfın bulunması ise oturumun kendi ürünüdür ve karşılığında kalıcı bir test bırakır.

Ufkun Sonundaki Sayı

Yirmi sürümün sonunda önceki dersin sırası tek başına 18 kusur kaçırıyordu, 155 risk puanıyla. Keşifsel oturum bu sayıyı her dağıtımda düşürür: E2 13,85’e, E1 12,00’a, E3 11,32’ye, E4 10,39’a. Kaçan risk 155’ten 95,2’ye iner — yani oturum yalnız daha az kusur kaçırmakla kalmaz, kaçırdıkları da daha hafiftir, çünkü testsiz iki sınıf kaçanların ağır ucundaydı.

Net sütunu bunu bir karara bağlar. Yirmi sürümlük oturum bedeli 2.400 dakikadır ve yalnız E4 artıya geçer: 145 dakika. E1 kaçan hatayı 12’ye indirir ama 1.071 dakika zarar eder, çünkü bulduğu her şey pahalı bir testin yeniden bütçeye alınmasını gerektirir. Bir dağıtımın kaçan hatayı azaltması onu haklı çıkarmaz; azalttığı kadarının bedeli de sayılır.

TS11 taraması bunun sınırını verir. Bütçe 60 dakikaya indiğinde kaçan hata 13,81’e çıkar ama net 202’ye yükselir; 240 dakikaya çıkarıldığında kaçan hata 6,27’ye iner ve net −923 olur. Kaçan hata bütçeyle birlikte düşmeye devam eder, net etmez. Sıfır kaçan hata isteyen bir plan bütçeyi tüketir; bu kursun her dersinde tekrarlanan takasın buradaki biçimi budur.

Kırmızı sonucun karşılığı da farklıdır. Bir keşifsel oturum yeşil ya da kırmızı vermez, bir bulgu listesi verir; listedeki her satır ya bir kayıt açar ya bir test doğurur. Sürümü durdurma yetkisi oturumda değildir — oturumun ürünü, sonraki sürümlerde sürümü durdurabilecek bir betikli testtir.

Özet

  • Oturum kapsamı bir bütçedir: genişlik ile derinliğin çarpımı sabittir (TS11–TS12), biri ötekinin karşılığıdır.
  • Tam genişlik (E1) derinlik 1’deki üç kusuru kesin bulur ama testsiz iki sınıfı hiç göremez; bu bir olasılık değil, sınıfın tanımından gelen bir kısıttır (TS13).
  • Derin kapsam (E4) daha az kusur bulur (2,75’e karşı 3,00) ama bulduklarının 1,42’si testsiz sınıftandır; fark verimlilik değil erişim farkıdır.
  • Dönüşüm oranı 0,98–1,00’dır, ama dönüşenlerin çoğu bütçe dışı sınıftır: yeni bilgi değil, bütçe kararının yeniden sorulmasıdır.
  • Yirmi sürümde kaçan hata 18’den 10,39’a, kaçan risk 155’ten 95,2’ye iner; 2.400 dakikalık oturum bedeliyle yalnız derin kapsam artıya geçer (145 dakika).
  • Bütçe 240 dakikaya çıkarıldığında kaçan hata 6,27’ye iner ve net −923 olur: kaçan hata bütçeyle düşmeye devam eder, net etmez.

Sonraki Adım

Buraya kadar üç ders sınamanın kendisini dağıttı: hangi test, hangi sırayla, hangi kapsamda. Üçünün de ortak bir sessiz varsayımı var — bir kusurun kusur olduğu, bir işin bittiğinin belli olduğu. Oysa keşifsel oturumun anlam-sapmasi sınıfındaki bulguları tam bu noktada tartışmaya açılır: sınayan bunu bir kusur sayar, geliştiren beklenen davranış sayar, ürün tarafı ikisine de katılmaz. Kaçan hata sayısı bu durumda bir ölçüm değil bir anlaşmazlıktır. Sonraki ders tamamlanma tanımını ve kabul ölçütünü ele alır: ölçütü kimin yazdığı, ne zaman kapandığı ve belirsiz bırakılan bir ölçütün kaç birim yeniden açılan iş ürettiği.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat