Skrivet av klk:
Vad är det för typ av fel som du kan fånga med enhetstester som inte går med andra typer av tester?
Det är fel fråga!
Rätt fråga är: finns det något som är mer effektivt än enhetstester för att göra X?
För egen del är svaret "nej" när TDD/enhetstester används på rätt sätt.
Det skrivet så är inte det den enda formen av testing som behövs!
Skrivet av klk:
Enligt mig är det bättre att investera den extra tid som det tar att jobba med testerna och istället förbättra koden, det brukar vara rätt mycket tid dessutom för utvecklare som skriver mycket tester är långsamma.
Man kan använda allt på dåliga sätt. Men rätt använt har TDD, som primärt måste bygga på unit-tester, massor med fördelar som gör att utvecklingen går snabbare med ett bättre resultat.
1. Testerna blir den första användaren av det API man skapar. Är det svårt att skriva tester är det en stark indikation på att APIet är dåligt. Och med TDD är detta något man får väldigt tidigt, innan man ens börjat skriva koden som man testar -> ger snabbare iteration av en de viktigaste delarna (publika APIet).
2. Testerna blir det första lackmustestet av arkitekturen. En "code-smell" här är om mer än en handfull av testerna behöver saker som mock-up och/eller väldigt komplicerad setup/tear-down. En förkrossande majoritet av alla tester bör gå att köra utan mock-ups.
3. Andra former av tester kommer behövas. Rätt gjorda ska tiden det tar att köra alla unit-tester som mest handla om en handfull sekunder. Det gör möjligt att köra den typen av tester hela tiden och det är en enkelt sätt att sätta upp ett specifikt fall man kanske vill undersöka m.h.a. av debugger eller liknande. Finns tester som av nödvändighet kommer ta lite tid att köra, men de ska inte vara unit-tester!
package main
import "testing"
var input18 = []string{
"2,2,2",
"1,2,2",
"3,2,2",
"2,1,2",
"2,3,2",
"2,2,1",
"2,2,3",
"2,2,4",
"2,2,6",
"1,2,5",
"3,2,5",
"2,1,5",
"2,3,5",
}
func TestDay18_1(t *testing.T) {
world := parseCubeWorld([]string{"1,1,1", "2,1,1"})
numFaces := CountFreeFaces(world.Droplets)
if numFaces != 10 {
t.Fatalf("Expected 10 free faces, got %d", numFaces)
}
}
func TestDay18_2(t *testing.T) {
world := parseCubeWorld(input18)
numFaces := CountFreeFaces(world.Droplets)
if numFaces != 64 {
t.Fatalf("Expected 64 free faces, got %d", numFaces)
}
}
func TestDay18_3(t *testing.T) {
world := parseCubeWorld([]string{"0,0,0"})
numFaces := world.CountExteriorFreeFaces(CountFreeFaces(world.Droplets))
if numFaces != 6 {
t.Fatalf("Expected 6 free exterior faces after filling with water, got %d", numFaces)
}
}
func TestDay18_4(t *testing.T) {
world := parseCubeWorld(input18)
numFaces := world.CountExteriorFreeFaces(CountFreeFaces(world.Droplets))
if numFaces != 58 {
t.Fatalf("Expected 58 free exterior faces after filling with water, got %d", numFaces)
}
}
Unit-tester ska inte behöva vara mer komplicerade än så här, detta råkar vara tester för Advent of Code 2022, dag 18
Edit: Då debuggern är ett väldigt bra och kraftfullt verktyg är det rätt naturligt att utvecklingsmiljöer gör det trivialt att debug:a ett specifikt test eller benchmark (mikrobenchmarks är typiskt väldigt lika unit-tester). Unit-tester/benchmarks är del av standard tool-chain i Go, vilket gör det naturligt för VS Code och liknande att integrera detta
Skrivet av klk:
Måste inte all kod vara säker?
Har i princip uteslutande jobbat med produkter, programvara som säljs till en hel hög kunder. När jag började tryckte vi upp mjukvara på CD, det var dyrt och en mycket arbetsam process för kunder att installera om. Att göra fel i den koden var mer eller mindre katastrof.
Finns två typer av säkerhet.
"Safety", skydda världen från programvaran/systemet. Är primärt detta man behöver garantera med de programvaror som kräver certifiering för att få användas.
"Security" är den andra formen där programvaran/systemet skyddas från världen. I de säkerhetskritiska fallen är nog detta rätt ofta löst genom att dessa system körs på isolerade system som är certifierade i sin helhet.
Vad dessa certifieringsmetoder visat är att men tillräckligt grundligg testing är det fullt möjligt att skriva nära nog perfekt programvara. I DO-178C är det inte ens C/C++ koden man certifierar, det är den assembler som faktiskt kör och kravet på de högsta nivåerna är 100 % line- (finns det instruktioner som inte körs ska de bort alt. så får man skriva en rapport på varför instruktionen finns när den inte körs) och state-coverage (d.v.s. varje villkorat hopp måste ha testfall för alla möjliga utfall).
Så det är i praktiken "perfekt" programvara, men enda sättet vi vet hur man kan skapa detta idag är så löjligt dyrt att det inte är användbart utanför de fall där den nivån är ett hårt krav.
Skrivet av klk:
För att det är ett meningslöst test för ett programmeringsfel. utvecklaren som anropar koden anropar koden på fel sätt och det behöver du inte skriva felhantering för. Räcker med att du fångar upp det med en assert och så får utvecklaren rätta koden och så återkommer inte felet
Finns massor med anledningar varför det i praktiken inte alls är så enkelt och varför det kan vara fullt vettigt att även i release-läget ha kod som upptäcker fall som "inte borde hända".
För inte hävdar du att normalkodnivån är nära nog perfekt programvara???
I debug kan man då på en lite högre nivå just bara göra "assert(X)" för att få information om felet och försöka fixa det.
I release kan det vara så att man på en högre nivå har extra kontext. Man har nu konstaterat ett fel som t.ex. resulterat i att någon beräkning inte kan göras.
Om man måste ha resultatet finns inget annat att göra än att döda programmet och logga felet.
Men kan finnas andra fall där en sådant fel mest ger lite irritation, i det läget vill kan fortfarande avbryta i debug (för målet är att fixa felet) men i release kan man välja att köra vidare (vilket t.ex. leder till att inget händer när man trycker på en UI-knapp eller liknande) då det är mindre dåligt för upplevelsen än att döda hela programmet.
Då kan man inte använda asserts() för huvudsyftet med dessa är att testa saker som måste vara sanna, om de inte är det har programmet hamnat i ett tillstånd där det inte finns någon poäng att fortsätta -> enda vettiga är att logga felet och avsluta.