TDD - Generowanie grafiki

0

Cześć,
Chciałbym napisać program, który dla zadanych parametrów (wysokość, szerokość, głębokość) wygeneruje mi zestaw linii składających się na grafikę wektorową (szablon pudełka do wycięcia z papieru o zadanych rozmiarach). I teraz pytanie: jak podejść do testowania takiego kodu? Mam wątpliwości czy w teście powinno być sprawdzane, że istnieje linia z punktu A do punktu B, albo, że linia A-B jest rysowana przed linią B-C. Linii składających się na szablon może być nawet kilkadziesiąt. Mógłbym wynik przekształcić do postaci SVG i sprawdzać czy wygenerowany dokument XML ma określoną treść ale takie rozwiązanie też mi się do końca nie podoba. Może ktoś miał doświadczenia z podobnym problemem i podzieli się doświadczeniami.

1

Co jest wynikiem działania twojego kodu?
Jeśli zestaw współrzędnych linii lub innych prymitywów graficznych, to napisanie testów do tego nie powinno być problemem.
Po prostu asercjami sprawdzasz, występowanie oczekiwanych wartości. Dopisanie odpowiednich helperów znacznie uprości sprawę (przykładowo, jeden trójkąt można opisać na 6 rożnych sposobów, więc nie można robić naiwnego porównania).

Jeśli wynikiem jest to grafika rastrowa/widok na ekranie, to to się źle testuje, w sensie czytelne opisanie oczekiwanego wyniku jest na tyle trudne i skomplikowane, że nie jest warte zachodu. Zresztą proces rysowania prymitywów graficznych prawie na pewno masz zrealizowane w zewnętrznej zależności, więc nie ma sensu tego testować.

0
MarekR22 napisał(a):

Co jest wynikiem działania twojego kodu?
Jeśli zestaw współrzędnych linii lub innych prymitywów graficznych, to napisanie testów do tego nie powinno być problemem.

Chciałem właśnie iść w tym kierunku, tzn. aby ta część programu zwracała definicje prymitywów.

Po prostu asercjami sprawdzasz, występowanie oczekiwanych wartości. Dopisanie odpowiednich helperów znacznie uprości sprawę (przykładowo, jeden trójkąt można opisać na 6 rożnych sposobów, więc nie można robić naiwnego porównania).

Tylko, aby sprawdzić czy dostałem oczekiwaną definicję linii będę musiał w teście wyliczyć/wyznaczyć jego współrzędne. Pytanie czy to nie będzie powielenie algorytmu z kodu produkcyjnego? Z drugiej strony mogę te wartości podać na sztywno, ale wtedy obawiam się, że testy będą bardzo wrażliwe na jakiekolwiek zmiany w sposobie.

Zresztą proces rysowania prymitywów graficznych prawie na pewno masz zrealizowane w zewnętrznej zależności, więc nie ma sensu tego testować.

Na początek chciałem zrobić w osobnej części konwersję prymitywów do SVG. Z SVG jest jeszcze jedna kwestia. Jeśli chcę aby prymitywy zostały przekształcone w zamkniętą powierzchnię to muszę je rysować w odpowiedniej kolejności. Początkowo chciałem to wrzucić w testy modułu generującego prymitywy, ale uświadomiłem sobie, że to jest raczej szczegół techniczny formatu SVG a nie mojej "domeny".

1
parsons napisał(a):

Tylko, aby sprawdzić czy dostałem oczekiwaną definicję linii będę musiał w teście wyliczyć/wyznaczyć jego współrzędne. Pytanie czy to nie będzie powielenie algorytmu z kodu produkcyjnego? Z drugiej strony mogę te wartości podać na sztywno, ale wtedy obawiam się, że testy będą bardzo wrażliwe na jakiekolwiek zmiany w sposobie.

No na tym to polega. Piszesz jakiś test dla bardzo prostego przypadku, gdzie jesteś w stanie w głowie lub na kartce wyznaczyć oczekiwany wynik (w ekstremalnych wypadkach polecam wolframalpha.com), a potem piszesz kod produkcyjny do tego.
Test wykorzystuje zawsze te same dane wejściowe i oczekuje zawsze tych samych danych wyjściowych. Obie wartości są parametrem testu (polecam, testy sparametryzowane). Sam test nie musi nic liczyć.
Innym sposobem weryfikacji poprawności jest wyznaczanie poprawności jakiś niezmienników.
Masz racje, że duplikowanie obliczeń z kodu produkcyjnego w teście jest błędnym podejściem.

Dyskusja na takim poziomie abstrakcji będzie bardzo sucha, wiec może skup się na konkretach.

0

Mi się wydaje, że podgląd graficzny np. tego wykrojnika będzie wystarczający, bo klient i tak będzie chciał zweryfikować go wizualnie.
A jak byś miał zrobić wykrojnik do bardziej skomplikowanych siatek?

0
MarekR22 napisał(a):

No na tym to polega. Piszesz jakiś test dla bardzo prostego przypadku, gdzie jesteś w stanie w głowie lub na kartce wyznaczyć oczekiwany wynik (w ekstremalnych wypadkach polecam wolframalpha.com), a potem piszesz kod produkcyjny do tego.

Z tym nie ma problemu.

Test wykorzystuje zawsze te same dane wejściowe i oczekuje zawsze tych samych danych wyjściowych. Obie wartości są parametrem testu (polecam, testy sparametryzowane). Sam test nie musi nic liczyć.

Zastanawiam się czy testy parametryzowane zawsze mają sens. Tutaj musiałbym dostarczyć zestaw wyznaczonych (w głowie na kartce) danych dość potężnych rozmiarów (kilka zestawów rozmiarów wejściowych * 30 linii).

Innym sposobem weryfikacji poprawności jest wyznaczanie poprawności jakiś niezmienników.

Możesz rozwinąć temat?

Dyskusja na takim poziomie abstrakcji będzie bardzo sucha, wiec może skup się na konkretach.

Problem nie leży w samej implementacji, bo z tym sobie lepiej lub gorzej poradzę, lecz z nakreśleniem kierunku w jakim powinno się podążać. Także dla mnie dyskusja nie jest sucha :) Jak siądę i napiszę to na pewno podzielę się kodem :)
Gdzieś tam we wszechświecie ktoś już pewnie taki problem rozwiązywał, np. tutaj

donPietro napisał(a):

Mi się wydaje, że podgląd graficzny np. tego wykrojnika będzie wystarczający, bo klient i tak będzie chciał zweryfikować go wizualnie.
A jak byś miał zrobić wykrojnik do bardziej skomplikowanych siatek?

Stopień skomplikowania siatki rzędu tego co w odnośniku powyżej.


Przykład

package tbm

package object vector {
  trait Shape
  case class Line(x1: Double, y1: Double, x2: Double, y2: Double) extends Shape
}
package tbm

import tbm.vector.{Line, Shape}

package object tuckbox {
  def cuttingLines(width: Double, height: Double, depth: Double): Seq[Shape] = List(
    Line(0, 0, width, 0),
    Line(0, 0, 0, height),
    Line(0, height, width, height)
  )
}
package tbm.tuckbox

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import tbm.vector.Line

class CuttingLinesSpec extends AnyFunSpec with Matchers {
  describe("cuttingLines") {
    describe("for box 50x100x20") {
      def lines = cuttingLines(width=50, height=100, depth=20)
      describe("on left face") {
        it("should have top line") {
          lines should contain(Line(0, 0, 50, 0))
        }
        it("should have left line") {
          lines should contain(Line(0, 0, 0, 100))
        }
        it("should have bottom line") {
          lines should contain(Line(0, 100, 50, 100))
        }
      }
    }
  }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1