C++ och dess framtid att programmera minnessäkert - Hur går utvecklingen?

Permalänk
Medlem
Skrivet av klk:

Koden testar sig själv, är du med? Känner du till "defensive programming", har säkert bättre namn men det ingår en hel del och om du inte direktanropar metoder kan du i princip byta ut delar i drift utan att systemet går ner.

Defensive programming är inte ett alternativ till testning utan används ihop med testning. Hur vet du att du inte har luckor i din defensive programming utan testning samt hur vet du att det inte kommer uppstå luckor när dina kollegor patchar din kod?

Jag använder defensive programming ihop med testning och jag misstänker att jag inte är ensam.

Permalänk
Medlem
Skrivet av orp:

Defensive programming är inte ett alternativ till testning utan används ihop med testning. Hur vet du att du inte har luckor i din defensive programming utan testning samt hur vet du att det inte kommer uppstå luckor när dina kollegor patchar din kod?

Jag använder defensive programming ihop med testning och jag misstänker att jag inte är ensam.

Kan du beskriva kod som du inte kan kontrollera på annat sätt än med enhetstester?

Permalänk
Skrivet av klk:

Kan du beskriva kod som du inte kan kontrollera på annat sätt än med enhetstester?

@orp ifrågasatte om defensive programming var tillräckligt för att hitta alla typer av fel. Han sade inte att det bara går att hitta vissa typer av fel med enhetstestning. Han sade heller inte att enhetstester är allena saliggörande. Enhetstesterna är en del i testkedjan.

Det här vara bara ett försök att styra diskussionen bort från något du inte vill dyka djupare i.

Skrivet av klk:

Koden testar sig själv, är du med?

Yeah, right!

Permalänk
Medlem
Skrivet av klk:

Kan du beskriva kod som du inte kan kontrollera på annat sätt än med enhetstester?

Det är faktiskt din tur att beskriva hur du jobbar med testning först, det räcker nu med sidospår.

Permalänk
Medlem
Skrivet av klk:

Kan du beskriva kod som du inte kan kontrollera på annat sätt än med enhetstester?

Inte kan och inte kan, det går väl att hacka runt det mesta men detta handlar väl framförallt om rätt verktyg för jobbet. Samt att du inte beskriver hur du kvalitetssäkrar motsvarigheten till unit-test.

Men exempelvis om du har:

int foo() { int b = bar(); switch (b) { case 1: // OK return 0; case 2: // Inte OK (fel 1) return 1; case 3: // OK return 0; default: // Inte OK (fel 2) return -1; } }

Hur kontrollerar du programmatiskt att du får förväntad output från foo() givet alla möjlig output från bar()?

Permalänk
Medlem
Skrivet av orp:

Inte kan och inte kan, det går väl att hacka runt det mesta men detta handlar väl framförallt om rätt verktyg för jobbet. Samt att du inte beskriver hur du kvalitetssäkrar motsvarigheten till unit-test.

Men exempelvis om du har:

int foo() { int b = bar(); switch (b) { case 1: // OK return 0; case 2: // Inte OK (fel 1) return 1; case 3: // OK return 0; default: // Inte OK (fel 2) return -1; } }

Hur kontrollerar du programmatiskt att du får förväntad output från foo() givet alla möjlig output från bar()?

Kan ju börja med och fråga, hur kontrollerar du det där med unittest?

Det där är kod som behöver refaktoreras. Om bar returnerar en integer där vissa värden är ok och andra är fel och metoder som använder bar behöver kontrollera så kommer den koden som är skriven på det viset snart vara full med problem.

acceptabel lösning hade möjligen varit att om du skall returnera en int så sätt en flagga (exempelvis att alla negativa värden) indikerar ett fel. Eller så något sådant här "std::pair<bool, std::string> bar( int& iResult )" eller variant på det.

Att bara anropa en metod utan att den tar parametrar där du kan returnera olika värden kan du inte kontrollera med enhetstester heller. Det måste du kontrollera i runtime

Permalänk
Skrivet av klk:

Att bara anropa en metod utan att den tar parametrar där du kan returnera olika värden kan du inte kontrollera med enhetstester heller. Det måste du kontrollera i runtime

Visst kan du det. Nu är exemplet onödigt basalt, men om du har en mock för funktionen bar() kan du i enhetstestet låta mocken returnera vilket värde som helst. Kom ihåg att nu är vi bara intresserade av att testa hur foo() beter sig. Det enda bar() gör ur foo()s synvinkel är att returnera ett värde, resten kan vi strunta i.

Sätt upp fyra tester där bar()-mocken returnerar fyra olika värden och du har täckt alla flöden i foo(). Krångligt? Jo, lite pyssel är det, men definitivt lättare än att sätta upp en testmiljö där du kör applikationen "på riktigt" och får bar() att returnera alla tre olika värden.

Bytte tre -> fyra
Permalänk
Medlem
Skrivet av Ingetledigtnamn:

Visst kan du det. Nu är exemplet onödigt basalt, men om du har en mock för funktionen bar() kan du i enhetstestet låta mocken returnera vilket värde som helst. Kom ihåg att nu är vi bara intresserade av att testa hur foo() beter sig. Det enda bar() gör ur foo()s synvinkel är att returnera ett värde, resten kan vi strunta i.

Och vad skulle det tillföra för värde sett till koden?

Givetvis kan du skriva hur mycket kod som helst som kör olika metoder men det bör ju fylla en funktion sett till att projekt drivs framåt

Permalänk
Medlem
Skrivet av klk:

Och vad skulle det tillföra för värde sett till koden?

Givetvis kan du skriva hur mycket kod som helst som kör olika metoder men det bör ju fylla en funktion sett till att projekt drivs framåt

Att du testar alla utfall på ett kontrollerat sätt. Om du har en funktion som exempelvis öppnar en fil men när du testar det i ett funktional test så kommer troligen alltid kunna öppna filen medans om du mockar open() eller fopen() så kan du kontrollera returvärdet och se till att din kod klarar hantera oväntade fall, som garanterat kommer inträffa i fält!... du kan kontrollera att dina asserts triggas som förväntat och dina dumma kollegor får ett fallerande test när dom råkat plocka bort en av dina asserter medans du är på semester

Permalänk
Medlem
Skrivet av orp:

Att du testar alla utfall på ett kontrollerat sätt. Om du har en funktion som exempelvis öppnar en fil men när du testar det i ett funktional test så kommer troligen alltid kunna öppna filen medans om du mockar open() eller fopen() så kan du kontrollera returvärdet och se till att din kod klarar hantera oväntade fall, som garanterat kommer inträffa i fält!... du kan kontrollera att dina asserts triggas som förväntat och dina dumma kollegor får ett fallerande test när dom råkat plocka bort en av dina asserter medans du är på semester

Jag förstår fortfarande inte, antingen är din kod inte komplett och det är saker som du behöver utveckla för att jag skall förstå vad du menar eller så får du förtydliga.

Är det så att ditt exempel skall testa "min" kod, alltså att min kod beter sig som förväntat och det är en metod som inte tar några argument. Hur skall jag veta hur det beter sig mer än att metoden kan returnera ett nummer

Är det så att det öppnas en fil i bar måste jag alltid kontrollera det, det hjälper inte med enhetstest, det måste jag kontrollera runtime. samma med wrappern foo eftersom den också bara returnerar ett nummer.

Permalänk
Skrivet av klk:

Jag förstår fortfarande inte, antingen är din kod inte komplett och det är saker som du behöver utveckla för att jag skall förstå vad du menar eller så får du förtydliga.

Är det så att ditt exempel skall testa "min" kod, alltså att min kod beter sig som förväntat och det är en metod som inte tar några argument. Hur skall jag veta hur det beter sig mer än att metoden kan returnera ett nummer

Är det så att det öppnas en fil i bar måste jag alltid kontrollera det, det hjälper inte med enhetstest, det måste jag kontrollera runtime. samma med wrappern foo eftersom den också bara returnerar ett nummer.

Våra enhetstester skall testa att foo() beter sig korrekt. Det skulle kunna se ut så här-ish:

Filen foo.c (orps funktion):

int foo() { int b = bar(); switch (b) { case 1: // OK return 0; case 2: // Inte OK (fel 1) return 1; case 3: // OK return 0; default: // Inte OK (fel 2) return -1; } }

Till det lägger vi en parallel fil (foo-test.c?) med enhetstesterna i:

int bar_return_value; int bar() { return bar_return_value; } void test_foo_n(int bar_value, int expected) { bar_return_value = bar_value; int result = foo(); if (result != expected) { printf("Test failed: expected %d, got %d\n", expected, result); } else { printf("Test passed: expected %d, got %d\n", expected, result); } } void test_foo() { test_foo_n(1, 0); // OK test_foo_n(2, 1); // Inte OK (fel 1) test_foo_n(3, 0); // OK test_foo_n(4, -1); // Inte OK (fel 2) } int main() { test_foo(); return 0; }

Med ett normal testramverk har du mer stöd, men detta illustrerar idén. När du kompilerar och länkar foo.c och foo-test.c får du ett litet program som motionerar funktionen foo och du kan på ett enkelt sätt kontrollera att alla konstiga fall hanteras korrekt i foo(). Vad bar() gör i din riktiga applikation, öppnar filer eller kopplar upp sig mot en server på nätet, kan vi bortse ifrån. Här testar vi bara att foo() kan hantera de olika returvärden bar() kan returnera.

Permalänk
Medlem
Skrivet av Ingetledigtnamn:

Med ett normal testramverk har du mer stöd, men detta illustrerar idén. När du kompilerar och länkar foo.c och foo-test.c får du ett litet program som motionerar funktionen foo och du kan på ett enkelt sätt kontrollera att alla konstiga fall hanteras korrekt i foo(). Vad bar() gör i din riktiga applikation, öppnar filer eller kopplar upp sig mot en server på nätet, kan vi bortse ifrån. Här testar vi bara att foo() kan hantera de olika returvärden bar() kan returnera.

Hmm, om det var ett försök att övertyga att tester är bra med det där exemplet så tror jag tvärtom det isåfall är ett bevis för det motsatta. Går det inte att skaka fram något bättre case?

Annars så kan jag hålla med om att tester är bra för utvecklare som inte kan programmera och det var ovanstående kod ett exempel på, hemsk kod.

För som koden är nu är testet meningslöst.

Permalänk
Medlem
Skrivet av klk:

Hmm, om det var ett försök att övertyga att tester är bra med det där exemplet så tror jag tvärtom det isåfall är ett bevis för det motsatta. Går det inte att skaka fram något bättre case?

Jag ville hålla det simpelt för att du inte ska sväva ute som du brukar göra, samt att jag inte vill lägga hela min dag på att producera bogus-kod för att polletten ska trilla ner för dig.

Skrivet av klk:

Annars så kan jag hålla med om att tester är bra för utvecklare som inte kan programmera och det var ovanstående kod ett exempel på, hemsk kod.

Om du nu anser dig vara bättre än andra så kan du se testerna som ett skydd för din kod mot alla inkompetenta kollegor som är inne och ändrar i din kodbas.

Utöver att du missar poängen helt. Du anropar troligen andra funktioner/metoder(från tredjeparts lib, libc annan intern kod etc) ifrån dina funktioner/metoder och dessa kan i vissa fall fallera och då bör du testat hur dina funktioner/metoder beter sig när du får väntade och oväntade returvärden/exceptions(? Har ingen aning om hur det fungerar i C++).

Skrivet av klk:

För som koden är nu är testet meningslöst.

Du beklagade dig om meningslösa saker som att returnera int, visst du kan väl skapa en enum, macro eller en rad andra lösningar för hur du vill representera returvärdet men det är inte en ovanlig implementation i C. Hur fungerar getaddrinfo() för din del, vad returnerar den?

Permalänk
Medlem
Skrivet av orp:

Jag ville hålla det simpelt för att du inte ska sväva ute som du brukar göra, samt att jag inte vill lägga hela min dag på att producera bogus-kod för att polletten ska trilla ner för dig.

Du hade kunnat länka till något, är du van att skriva test tror jag inte det är svårt att hitta något bättre exempel eller förklara med ord. Kan man så vet man

Skrivet av orp:

Om du nu anser dig vara bättre än andra så kan du se testerna som ett skydd för din kod mot alla inkompetenta kollegor som är inne och ändrar i din kodbas.

Grundproblemet idag är att det är fruktansvärt svårt att prata teknik och lösningar. Följer du inte trenden som gäller just vid aktuellt tillfälle så kommer vederbörande få precis de reaktioner jag får i den här tråden om inte värre. Man får massa konstiga argument mot sig från utvecklare som inte riktigt har koll på tekniken och heller inte förstår varför de gör som de gör.

Det här är så lätt att vända men det krävs att utvecklare klarar av att tänka friare, fundera över andra tekniker än det man är van vid. Alternativt att man ser alternativa lösningar. Det går inte alltid och ibland krävs det att de hamnar i skiten innan de fattar för det är ganska lätt och se hur långt en utvecklare kommer med sin kod, beror lite på deras minneskapacitet. Utvecklare med hästminne (kan minnas mycket) har en tendens till att producera mer skit De behöver inte skriva välstrukturerad kod eftersom de minns för bra. Men det är bara de själva som minns koden.

Skrivet av orp:

Utöver att du missar poängen helt. Du anropar troligen andra funktioner/metoder(från tredjeparts lib, libc annan intern kod etc) ifrån dina funktioner/metoder och dessa kan i vissa fall fallera och då bör du testat hur dina funktioner/metoder beter sig när du får väntade och oväntade returvärden/exceptions(? Har ingen aning om hur det fungerar i C++).

Du skrev inte det men visst, jag kunde räkna ut det och då spelar inte ditt test någon roll eftersom jag ändå behöver kolla felmeddelande i runtime. Att skriva test för något som också testat i runtime förstår jag inte poängen med. Möjligen isåfall att man testar felmeddelanden från runtime testet, alltså text som skrivs ut.

Skrivet av orp:

Du beklagade dig om meningslösa saker som att returnera int, visst du kan väl skapa en enum, macro eller en rad andra lösningar för hur du vill representera returvärdet men det är inte en ovanlig implementation i C. Hur fungerar getaddrinfo() för din del?

Ja alltså som jag försökt beskriva, felhantering är viktigt. För fjärde gången så enligt mig så duger inte enhetstester för de är för osäkra. Att kod kontrollerar alla möjliga saker som kan gå fel live måste in. Annars blir det jobbgt. Det enda jag själv inte kontrollerar är att minnet tar slut, sitter inte i den typen av miljöer så det är ett krav för snåla hårdvarukrav beöver än mer inbyggd kontroll.

Permalänk
Skrivet av klk:

Hmm, om det var ett försök att övertyga att tester är bra med det där exemplet så tror jag tvärtom det isåfall är ett bevis för det motsatta. Går det inte att skaka fram något bättre case?

Annars så kan jag hålla med om att tester är bra för utvecklare som inte kan programmera och det var ovanstående kod ett exempel på, hemsk kod.

För som koden är nu är testet meningslöst.

Du sade "Att bara anropa en metod utan att den tar parametrar där du kan returnera olika värden kan du inte kontrollera med enhetstester",

jag sade "Visst kan du det",

du undrade "Och vad skulle det tillföra för värde sett till koden?",

orp sade "Att du testar alla utfall på ett kontrollerat sätt."

du sade "Jag förstår fortfarande inte, antingen är din kod inte komplett och det är saker som du behöver utveckla för att jag skall förstå vad du menar eller så får du förtydliga" och

jag försökte "förtydliga" genom att ge ett exempel på hur man med hjälp av enhetstester och mocking kan "testa alla utfall på ett kontrollerat sätt" i det ursprungliga exemplet.

Jag håller med om att exempelkoden i sig är ganska meningslös, men jag håller inte med om att testet är meningslöst. Om dessa tester passerar vet vi att foo() kan hantera de sällan förekommande felfallen från bar(). Att testa samma sak genom att köra den fulla applikationen kommer sannolikt att kräva avsevärt större instats. Dessutom kan vi köra dessa tester på de olika modulerna innan vi har byggt hela applikationen och det borde underlätta integrationen avsevärt.

Jag tänker inte posta produktionskod här, det skulle inte min arbetsgivare gilla, men ChatGPT gav följande förslag på något real world problem som kunde mockas:

temp-reader.c

// Forward declaration of the hardware function. // In production this is provided by the hardware driver. extern int sensor_read_raw(int *raw_value); float tr_get_temperature_celsius() { int raw = 0; int status = sensor_read_raw(&raw); if (status != 0) { // Sensor failure return TEMP_SENSOR_ERROR; } // Suppose the sensor gives value in tenths of a degree return raw / 10.0f; } float tr_get_temperature_fahrenheit() { float celsius = tr_get_temperature_celsius(); if (celsius == TEMP_SENSOR_ERROR) { return TEMP_SENSOR_ERROR; } return (celsius * 9.0f / 5.0f) + 32.0f; }

temp-reader-test.c

// --- Mock section --- static int mock_status = 0; static int mock_value = 0; int sensor_read_raw(int *raw_value) { if (mock_status != 0) { return mock_status; // simulate error } *raw_value = mock_value; return 0; // success } // --- Tests --- static void test_success_celsius() { mock_status = 0; mock_value = 253; // represents 25.3 °C float t = tr_get_temperature_celsius(); assert(t > 25.29f && t < 25.31f); } static void test_success_fahrenheit() { mock_status = 0; mock_value = 100; // 10.0 °C float t = tr_get_temperature_fahrenheit(); assert(t > 50.0f && t < 50.1f); } static void test_error_handling() { mock_status = -1; // simulate hardware failure float t = tr_get_temperature_celsius(); assert(t == TEMP_SENSOR_ERROR); t = tr_get_temperature_fahrenheit(); assert(t == TEMP_SENSOR_ERROR); } int main() { test_success_celsius(); test_success_fahrenheit(); test_error_handling(); printf("All temp_reader tests passed!\n"); return 0; }

Dessa kanske är lite mer illustrativa än testerna Copilot skrev till vårt ursprungliga exempel. Med mock-funktionen i enhetstesten isolerar vi testerna från den riktiga sensorn och kan testa att funktionerna i temp-reader.c räknar rätt utgående från ett par råa värden och att felfallet hanteras korrekt.

Permalänk
Skrivet av klk:

Du skrev inte det men visst, jag kunde räkna ut det och då spelar inte ditt test någon roll eftersom jag ändå behöver kolla felmeddelande i runtime. Att skriva test för något som också testat i runtime förstår jag inte poängen med. Möjligen isåfall att man testar felmeddelanden från runtime testet, alltså text som skrivs ut.

Enhetstesterna ger dig möjlighet att skriva tester för dina runtime-tester och kontrollera att de beter sig korrekt. Jag har vid mer än ett tillfälle hittat kod som gjort en runtime-test, identifierat ett felfall och sedan kraschat när den skulle producera felmeddelandet Om man testar på om det är en NULL-pekare är det inte bra om man cut-and-paste:at ett felmeddelande som innehåller det pekaren pekar på. (Ett av dessa tillfällen var min kod.)

Permalänk
Medlem
Skrivet av Ingetledigtnamn:

Dessa kanske är lite mer illustrativa än testerna Copilot skrev till vårt ursprungliga exempel. Med mock-funktionen i enhetstesten isolerar vi testerna från den riktiga sensorn och kan testa att funktionerna i temp-reader.c räknar rätt utgående från ett par råa värden och att felfallet hanteras korrekt.

Ja det var bättre, även om jag också ser dessa tester som något överdrivna för det lilla, förstår självklart att man normalt inte gör tester för så lite kod utan att detta var exempel.
Men det här hade lätt plockats i ett större regressionstest (om jag har rätt namn)
För det är mycket kod för att testa funktionalitet och förändras funktionaliteten tar det tid i och med att det troligen kommer smälla i testet och då måste utvecklaren städa upp där.

Förutom att koden blir jobbigare att underhålla blir också resultatet att man struntar i förbättringar eftersom det tar sådan tid med all extra kod.

Enligt mig plockar också regressionstest bättre den faktiska funktionaliteten då det brukar efterlikna faktiskt användande. När utvecklare skriver egna test brukar de inte ha samma "dumhet" i tankarna som en en användare kan ha, alla utvecklare är hemmablinda

Permalänk
Medlem
Skrivet av Ingetledigtnamn:

Om man testar på om det är en NULL-pekare är det inte bra om man cut-and-paste:at ett felmeddelande som innehåller det pekaren pekar på. (Ett av dessa tillfällen var min kod.)

Använder du inte debugger?

Permalänk
Skrivet av klk:

Använder du inte debugger?

Självklart använder jag debugger, men inte för att provocera fram felfall. Om detta fel råkar inträffa en gång på miljonen så är det ju givetvis inget som testsviten hittar utan det dyker upp ute hos kund...

Permalänk
Medlem
Skrivet av klk:

Du hade kunnat länka till något, är du van att skriva test tror jag inte det är svårt att hitta något bättre exempel eller förklara med ord. Kan man så vet man

Vad var dåligt med exemplet och frågeställningen "Hur kontrollerar du programmatiskt att du får förväntad output från foo() givet alla möjlig output från bar()?"? Hur kommer det sig att @Ingetledigtnamn förstår frågan utan problem?

Vill du tvunget ha mer realistiska exempel så här är:
Implementationen av strnlen(): https://github.com/bminor/glibc/blob/master/string/strnlen.c
Unit testerna för strnlen(): https://github.com/bminor/glibc/blob/master/string/test-strnl...

Om du inte begriper exemplet från @Ingetledigtnamn så tvivlar jag att detta kommer göra dig klokare. Jag tror att du i princip kan ta vilket open source projekt som helst och hitta unit-test någonstans i deras git-repo.

Skrivet av klk:

Grundproblemet idag är att det är fruktansvärt svårt att prata teknik och lösningar.

Nej, det är det inte. Alla andra i tråden kan prata teknik och lösningar utan svårigheter.

Skrivet av klk:

Följer du inte trenden som gäller just vid aktuellt tillfälle så kommer vederbörande få precis de reaktioner jag får i den här tråden om inte värre. Man får massa konstiga argument mot sig från utvecklare som inte riktigt har koll på tekniken och heller inte förstår varför de gör som de gör.

1. Det är ditt jobb som utvecklare att hålla dig uppdaterad, tror du att folk som arbetar inom redovisning kan strunta i att hålla sig uppdaterad om nya skatteregler?

2. Väldigt få av sakerna vi diskuterat i denna tråden är nypåhittade saker. Unit-test enl. wiki kan dateras tillbaka till mitten av 50-talet men har väl gissningsvis varit industristandard sedan 15 år tillbaka.

3. Väldigt få av sakerna som diskuterats här handlar om detaljerade diskussioner kring koncepten utan diskussionerna dras igång pga av att du kastar ur dig absurda påstående som "Unit-tester är värdelösa". Du får ogilla att skriva unit-tester, jag bryr mig inte om du unit-testar din kod eller för den delen testar alls men unit-tester är inte värdelösa (dvs 0-värde) för industrin har bevisat motsatsen.

När vi sedan konfronterar dig så drar du massa helt orelaterade begrepp som defensive programming, debugger, regression test utan att förklara hur du testar regressionerna på en lägre nivå. Stundvis känns det som du har en stor skål med lappar och på lapparna står random programmeringsrelaterad term och varje gång du pressad på att förklara dig så drar du en lapp från din skål och försöker spåra ur diskussionen med ytterligare absurditeter.

Skrivet av klk:

Det här är så lätt att vända men det krävs att utvecklare klarar av att tänka friare, fundera över andra tekniker än det man är van vid.

Du säger tänka friare men det är samtidigt du som har mest cementerad åsikter i tråden.

Jag hade kunnat kasta ur mig ett påstående om att "X är värdelöst" i ren frustration men om någon konfronterar mig kring det så kan jag ge med mig om att "X troligen inte är värdelöst även om jag inte gillar X".

Du saknar ju också en nyanserad bild av industrin, vilket verkar leda till dina cementerade åsikter, många av påståendena är ju i konflikt med safety. Vidare, när jag nämnde asserter i produktionskod så sa du "då har man missat något i utbildningen" även om det finns scenarios för det också, kanske dags att leva som du lär och tänka friare?

Skrivet av klk:

Ja alltså som jag försökt beskriva, felhantering är viktigt. För fjärde gången så enligt mig så duger inte enhetstester för de är för osäkra.

Nu får du förklara varför?

Permalänk
Medlem
Skrivet av klk:

Ja det var bättre, även om jag också ser dessa tester som något överdrivna för det lilla, förstår självklart att man normalt inte gör tester för så lite kod utan att detta var exempel.
Men det här hade lätt plockats i ett större regressionstest (om jag har rätt namn)
För det är mycket kod för att testa funktionalitet och förändras funktionaliteten tar det tid i och med att det troligen kommer smälla i testet och då måste utvecklaren städa upp där.

Förutom att koden blir jobbigare att underhålla blir också resultatet att man struntar i förbättringar eftersom det tar sådan tid med all extra kod.

Enligt mig plockar också regressionstest bättre den faktiska funktionaliteten då det brukar efterlikna faktiskt användande. När utvecklare skriver egna test brukar de inte ha samma "dumhet" i tankarna som en en användare kan ha, alla utvecklare är hemmablinda

Du har inte rätt namn.
Regressionstester är tester som kontrollerar att rättade problem förblir rättade. Kan göras på alla nivåer - från enhetstester till systemtester.
Vad du pratar om verkar vara systemtester, där man testar hela det färdiga programmet.

Om man ändrar funktionalitet så kan tester naturligtvis behöva skrivas om. Men det gäller ju för alla typer av tester.

Eftersom enhetstester bara tester en liten bit av koden åt gången - typiskt en enskild funktion - så är det ju bara ett test som (eventuellt) behöver uppdateras om den funktionen ändras. Och om du ändrar funktionen utan att testa den, hur vet du då att ändringen blev rätt?

Permalänk
Medlem
Skrivet av Erik_T:

Om man ändrar funktionalitet så kan tester naturligtvis behöva skrivas om. Men det gäller ju för alla typer av tester.

Inte systemtester för där försöker i alla fall jag för det mesta testa output samt att man granskar loggar och annat. Det behöver inte vara exakt lika, beror en del på vad som skall göras.

Skrivet av Erik_T:

Eftersom enhetstester bara tester en liten bit av koden åt gången - typiskt en enskild funktion - så är det ju bara ett test som (eventuellt) behöver uppdateras om den funktionen ändras. Och om du ändrar funktionen utan att testa den, hur vet du då att ändringen blev rätt?

Det här begriper inte jag att inte fler är vaksamma på. Begreppet "BARA", vet inte hur många gånger jag hört "men det är ju bara" och så skall den utvecklaren som sagt att det är ju "bara" men så var det inte bara utan det där bara var bara 10% av allt som egentligen behövde göras. Det är inte bara för det bli massor och det blir massor att hålla reda på.

Permalänk
Medlem
Skrivet av klk:

Inte systemtester för där försöker i alla fall jag för det mesta testa output samt att man granskar loggar och annat. Det behöver inte vara exakt lika, beror en del på vad som skall göras.

Det här begriper inte jag att inte fler är vaksamma på. Begreppet "BARA", vet inte hur många gånger jag hört "men det är ju bara" och så skall den utvecklaren som sagt att det är ju "bara" men så var det inte bara utan det där bara var bara 10% av allt som egentligen behövde göras. Det är inte bara för det bli massor och det blir massor att hålla reda på.

Det kommer ju självklart vara en ökad arbetsbelastning när testerna initialt ska skrivas men det kommer ju reducera exempelvis granskningstiden av patchar som inte fungerar och bidrar till en kvalitetsökning som gör att du kommer spendera mindre tid att felsöka eventuella buggar. Om du väljer att inte skriva enhetstester för din hobbykod är ju helt upp till dig eftersom du tar kostnaden för implementationen och eventuella buggar men att din arbetsgivare skulle acceptera det är aningen förvånande. :S

Permalänk
Medlem
Skrivet av orp:

Det kommer ju självklart vara en ökad arbetsbelastning när testerna initialt ska skrivas men det kommer ju reducera exempelvis granskningstiden av patchar som inte fungerar och bidrar till en kvalitetsökning som gör att du kommer spendera mindre tid att felsöka eventuella buggar. Om du väljer att inte skriva enhetstester för din hobbykod är ju helt upp till dig eftersom du tar kostnaden för implementationen och eventuella buggar men att din arbetsgivare skulle acceptera det är aningen förvånande. :S

Om den exempelkod som du klistrat in är representativ för hur du kodar så absolut, då är det bra med enhetstester. Gärna mer än mindre kontroll på andra områden med.

Hobbykod brukar vara av högre kvalitet än ute hos företag, folk tenderar bara att göra det som de blir tillsagda att göra. Hobbykod är något man själv vårdar och då sköts det bättre. Tänker då på projekt man gör som en hobby, inte lärogrejjer

Permalänk
Medlem
Skrivet av klk:

Om den exempelkod som du klistrat in är representativ för hur du kodar så absolut, då är det bra med enhetstester. Gärna mer än mindre kontroll på andra områden med.

Vilken kod syftar du på? Du syftar på en exempel-funktion som anropar en annan exempel-funktion och ett switch-statement? Vad har du nu hängt upp dig på?

Permalänk
Medlem
Skrivet av klk:

Det här begriper inte jag att inte fler är vaksamma på. Begreppet "BARA", vet inte hur många gånger jag hört "men det är ju bara" och så skall den utvecklaren som sagt att det är ju "bara" men så var det inte bara utan det där bara var bara 10% av allt som egentligen behövde göras. Det är inte bara för det bli massor och det blir massor att hålla reda på.

Du missar min poäng.
Om du ändrar i en funktionen måste du ju testa funktionen efteråt om du vill vara säker på att den fungerar som den skall.
Det är knappast mer jobb att uppdatera ett existerande test än att skriva ett helt nytt test.

Om du bara gör tester på systemnivå, och aldrig testar enskilda komponenter, då hoppas jag att din kod aldrig används till någonting viktigt.

Jag menar, det du beskriver att du gör, det duger ju för små hobbyprojekt, eller för den delen lite större projekt där det inte är så viktigt att koden alltid gör rätt. (Och, tyvärr, i företag där man inte tänker långsiktigt utan där nästa kvartalsrapport är allt som räknas.)
Men för kod som gör lite viktigare saker, där brukar (och bör) det vara högre krav på kvalitet i testningen. Även om det kräver mer jobb och tar längre tid.

Permalänk
Medlem
Skrivet av Erik_T:

Du missar min poäng.
Om du ändrar i en funktionen måste du ju testa funktionen efteråt om du vill vara säker på att den fungerar som den skall.
Det är knappast mer jobb att uppdatera ett existerande test än att skriva ett helt nytt test.

Menar du att man skall skriva separata test för varje metod? Det tror jag inte va, då blir man ju aldrig klar

Skrivet av Erik_T:

Om du bara gör tester på systemnivå, och aldrig testar enskilda komponenter, då hoppas jag att din kod aldrig används till någonting viktigt.

Hur skulle du skriva test för följande kod (alltså mer ett resonemang, skriv ingen kod för den är svår att skriva test för), satt precis och gjorde den så det kanske är någon bugg

/** --------------------------------------------------------------------------- * @brief Updates the pattern list for files in the cache using regex patterns and multithreading. * * This method processes a list of regex patterns and applies them to the files stored in the cache. * It generates a list of lines in each file where the patterns are found and stores the results * in the "file-linelist" cache table. The method uses multithreading to process files in parallel. * * @param vectorRegexPatterns A vector of pairs containing regex patterns and their names. * The vector must not be empty. * @param argumentsList Arguments for pattern processing (e.g., segment specification, max lines) * @param iThreadCount Number of threads to use (0 = auto-detect) * @return A pair containing: * - `bool`: `true` if the operation was successful, `false` otherwise. * - `std::string`: An empty string on success, or an error message on failure. * * @pre The `vectorRegexPatterns` must not be empty. * @post The "file-linelist" cache table is updated with the lines where the patterns are found. * * @note COMMAND_ListLinesWithPattern must be thread-safe. */ std::pair<bool, std::string> CDocument::FILE_UpdatePatternList(const std::vector<std::pair<boost::regex, std::string>>& vectorRegexPatterns, const gd::argument::shared::arguments& argumentsList, int iThreadCount) { assert(vectorRegexPatterns.empty() == false); // Ensure the regex pattern list is not empty using namespace gd::table; auto* ptableFile = CACHE_Get("file"); // Retrieve the "file" cache table auto* ptableLineList = CACHE_Get("file-linelist", true); // Ensure the "file-linelist" table is in cache assert(ptableFile != nullptr); assert(ptableLineList != nullptr); std::string_view stringSegment; if( argumentsList.exists("segment") == true ) { stringSegment = argumentsList["segment"].as_string_view(); } // Get the segment (code, comment, string) to search in uint64_t uMax = argumentsList["max"].as_uint64(); // Get the maximum number of lines to be printed auto uFileCount = ptableFile->get_row_count(); // Total number of files to process // ## Thread synchronization variables std::atomic<uint64_t> uAtomicFileIndex(0); // Current file being processed std::atomic<uint64_t> uAtomicProcessedCount(0); // Count of processed files std::atomic<uint64_t> uAtomicTotalLines(0); // Total lines found across all threads std::mutex mutexProgress; // Mutex to protect progress updates std::mutex mutexLineList; // Mutex to protect ptableLineList access std::vector<std::string> vectorErrors; // Collect errors from all threads std::mutex mutexErrors; // Mutex to protect access to vectorErrors // ## Worker function to process files in parallel ......................... auto process_ = [&](int iThreadId) -> void { // Create thread-local table for collecting results detail::columns* pcolumnsThread = new detail::columns{}; ptableLineList->to_columns( *pcolumnsThread ); std::unique_ptr<table> ptableLineListLocal = std::make_unique<table>(pcolumnsThread, 10, ptableLineList->get_flags(), 10); // Create local table with 10 rows pre-allocated while( true ) { uint64_t uRowIndex = uAtomicFileIndex.fetch_add(1); // get thread safe current index and increment it if(uRowIndex >= uFileCount) { break; } try { // STEP 1: Get file info (ptableFile is read-only so no mutex needed) auto stringFolder = ptableFile->cell_get_variant_view(uRowIndex, "folder").as_string(); auto stringFilename = ptableFile->cell_get_variant_view(uRowIndex, "filename").as_string(); // STEP 2: Build full file path gd::file::path pathFile(stringFolder); pathFile += stringFilename; std::string stringFile = pathFile.string(); auto uKey = ptableFile->cell_get_variant_view(uRowIndex, "key").as_uint64(); // STEP 3: Find lines with regex patterns gd::argument::shared::arguments arguments_({{"source", stringFile}, {"file-key", uKey}}); if( stringSegment.empty() == false ) arguments_.set("segment", stringSegment.data()); // Set the segment (code, comment, string) to search in auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get()); // Find lines with regex patterns and update the local table if(result_.first == false) { std::lock_guard<std::mutex> lockErrors(mutexErrors); vectorErrors.push_back("File: " + stringFile + " - " + result_.second); uint64_t uProcessed = uAtomicProcessedCount.fetch_add(1) + 1; // Update progress even on failure if(uProcessed % 10 == 0) { std::lock_guard<std::mutex> lockProgress(mutexProgress); uint64_t uPercent = (uProcessed * 100) / uFileCount; MESSAGE_Progress("", {{"percent", uPercent}, {"label", "Find in files"}, {"sticky", true}}); } continue; // Skip to next file on error } // STEP 4: Append results to main table (FAST operation - mutex needed for thread safety) { std::lock_guard<std::mutex> lockLineList(mutexLineList); ptableLineList->append(ptableLineListLocal.get()); // Append the results from the local table to the main table // Update total line count and check if we've exceeded the maximum uint64_t uCurrentLines = uAtomicTotalLines.fetch_add(ptableLineListLocal->get_row_count()) + ptableLineListLocal->get_row_count(); if( uCurrentLines > uMax ) { uAtomicFileIndex.store(uFileCount); // Signal other threads to stop by setting file index to max } } ptableLineListLocal->row_clear(); // Clear local table rows for next iteration uint64_t uProcessed = uAtomicProcessedCount.fetch_add(1) + 1; // Update progress (thread-safe) if(uProcessed % 10 == 0) // Show progress every 10 files { std::lock_guard<std::mutex> lockProgress(mutexProgress); uint64_t uPercent = (uProcessed * 100) / uFileCount; MESSAGE_Progress("", {{"percent", uPercent}, {"label", "Find in files"}, {"sticky", true}}); } } catch(const std::exception& exception_) { std::lock_guard<std::mutex> lockErrors(mutexErrors); vectorErrors.push_back(std::string("Thread ") + std::to_string(iThreadId) + " error: " + exception_.what()); } } }; // ## Prepare and run threads .............................................. if( iThreadCount <= 0 ) { iThreadCount = std::thread::hardware_concurrency(); } // Use hardware concurrency if no thread count is specified if(iThreadCount <= 0) { iThreadCount = 1; } // Fallback to single thread if hardware_concurrency returns 0 if( iThreadCount > 8 ) { iThreadCount = 8; } // Limit to 8 threads for performance and resource management if( ptableFile->size() < iThreadCount ) { iThreadCount = (int)ptableFile->size(); } // Limit threads to number of files // Create and launch worker threads std::vector<std::thread> vectorPatternThread; vectorPatternThread.reserve(iThreadCount); for(int i = 0; i < iThreadCount; ++i) { vectorPatternThread.emplace_back(process_, i); } // Wait for all threads to complete for(auto& threadWorker : vectorPatternThread) { threadWorker.join(); } MESSAGE_Progress("", {{"clear", true}}); // Clear progress message // ### Handle any collected errors if(!vectorErrors.empty()) { for(const auto& stringError : vectorErrors) { ERROR_Add(stringError); } } return {true, ""}; }

Dold text
Permalänk
Datavetare

Här är ett verkligt exempel på en custom-YAML parser (tillåter en syntax som har flera optimeringar för den specifika domänen, tillåter bl.a. cirkulära referens och referenser till saker som kommer definieras senare i filen)

Går detta att testa direkt i applikationen? Absolut, men kräver ju att man har en fil som triggar just den specifika feature (här testas ett fall där en kompaktare YAML-syntax tillåts på typer med potentiell många fällt men där ett är annoterat "primärt"). För att ens komma till punken där parsen används i programmet behöver man gå igenom ett par menyer -> tar minst sekunder och manuellt arbete.

Med unit-test kan man välja exakt det fall man vill testa. Endera köra som program eller köra i debugger. Tar <1 ms att köra varje deltest och är ju knappast så att testkoden är komplex...

public class WithPrimaryProperty { [PrimaryProperty] public int Id { get; init; } public string Description { get; init; } = string.Empty; } public class TestRoot { public required WithPrimaryProperty WithPrimaryProperty { get; init; } } ... [Fact] public void TestCanParseVerbosePrimary() { var content = """ TestRoot: WithPrimaryProperty: Id: 17 Description: "This is a normal property, Id is primary" """; var result = YamlParser<TestRoot>.Parse(content); Assert.Equal(17, result.WithPrimaryProperty.Id); } [Fact] public void TestCanParseShortPrimary() { var content = """ TestRoot: WithPrimaryProperty: 17 """; var result = YamlParser<TestRoot>.Parse(content); Assert.Equal(17, result.WithPrimaryProperty.Id); }

Dold text
Skrivet av klk:

Menar du att man skall skriva separata test för varje metod? Det tror jag inte va, då blir man ju aldrig klar

Hur skulle du skriva test för följande kod (alltså mer ett resonemang, skriv ingen kod för den är svår att skriva test för), satt precis och gjorde den så det kanske är någon bugg

/** --------------------------------------------------------------------------- * @brief Updates the pattern list for files in the cache using regex patterns and multithreading. * * This method processes a list of regex patterns and applies them to the files stored in the cache. * It generates a list of lines in each file where the patterns are found and stores the results * in the "file-linelist" cache table. The method uses multithreading to process files in parallel. * * @param vectorRegexPatterns A vector of pairs containing regex patterns and their names. * The vector must not be empty. * @param argumentsList Arguments for pattern processing (e.g., segment specification, max lines) * @param iThreadCount Number of threads to use (0 = auto-detect) * @return A pair containing: * - `bool`: `true` if the operation was successful, `false` otherwise. * - `std::string`: An empty string on success, or an error message on failure. * * @pre The `vectorRegexPatterns` must not be empty. * @post The "file-linelist" cache table is updated with the lines where the patterns are found. * * @note COMMAND_ListLinesWithPattern must be thread-safe. */ std::pair<bool, std::string> CDocument::FILE_UpdatePatternList(const std::vector<std::pair<boost::regex, std::string>>& vectorRegexPatterns, const gd::argument::shared::arguments& argumentsList, int iThreadCount) { assert(vectorRegexPatterns.empty() == false); // Ensure the regex pattern list is not empty using namespace gd::table; auto* ptableFile = CACHE_Get("file"); // Retrieve the "file" cache table auto* ptableLineList = CACHE_Get("file-linelist", true); // Ensure the "file-linelist" table is in cache assert(ptableFile != nullptr); assert(ptableLineList != nullptr); std::string_view stringSegment; if( argumentsList.exists("segment") == true ) { stringSegment = argumentsList["segment"].as_string_view(); } // Get the segment (code, comment, string) to search in uint64_t uMax = argumentsList["max"].as_uint64(); // Get the maximum number of lines to be printed auto uFileCount = ptableFile->get_row_count(); // Total number of files to process // ## Thread synchronization variables std::atomic<uint64_t> uAtomicFileIndex(0); // Current file being processed std::atomic<uint64_t> uAtomicProcessedCount(0); // Count of processed files std::atomic<uint64_t> uAtomicTotalLines(0); // Total lines found across all threads std::mutex mutexProgress; // Mutex to protect progress updates std::mutex mutexLineList; // Mutex to protect ptableLineList access std::vector<std::string> vectorErrors; // Collect errors from all threads std::mutex mutexErrors; // Mutex to protect access to vectorErrors // ## Worker function to process files in parallel ......................... auto process_ = [&](int iThreadId) -> void { // Create thread-local table for collecting results detail::columns* pcolumnsThread = new detail::columns{}; ptableLineList->to_columns( *pcolumnsThread ); std::unique_ptr<table> ptableLineListLocal = std::make_unique<table>(pcolumnsThread, 10, ptableLineList->get_flags(), 10); // Create local table with 10 rows pre-allocated while( true ) { uint64_t uRowIndex = uAtomicFileIndex.fetch_add(1); // get thread safe current index and increment it if(uRowIndex >= uFileCount) { break; } try { // STEP 1: Get file info (ptableFile is read-only so no mutex needed) auto stringFolder = ptableFile->cell_get_variant_view(uRowIndex, "folder").as_string(); auto stringFilename = ptableFile->cell_get_variant_view(uRowIndex, "filename").as_string(); // STEP 2: Build full file path gd::file::path pathFile(stringFolder); pathFile += stringFilename; std::string stringFile = pathFile.string(); auto uKey = ptableFile->cell_get_variant_view(uRowIndex, "key").as_uint64(); // STEP 3: Find lines with regex patterns gd::argument::shared::arguments arguments_({{"source", stringFile}, {"file-key", uKey}}); if( stringSegment.empty() == false ) arguments_.set("segment", stringSegment.data()); // Set the segment (code, comment, string) to search in auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get()); // Find lines with regex patterns and update the local table if(result_.first == false) { std::lock_guard<std::mutex> lockErrors(mutexErrors); vectorErrors.push_back("File: " + stringFile + " - " + result_.second); uint64_t uProcessed = uAtomicProcessedCount.fetch_add(1) + 1; // Update progress even on failure if(uProcessed % 10 == 0) { std::lock_guard<std::mutex> lockProgress(mutexProgress); uint64_t uPercent = (uProcessed * 100) / uFileCount; MESSAGE_Progress("", {{"percent", uPercent}, {"label", "Find in files"}, {"sticky", true}}); } continue; // Skip to next file on error } // STEP 4: Append results to main table (FAST operation - mutex needed for thread safety) { std::lock_guard<std::mutex> lockLineList(mutexLineList); ptableLineList->append(ptableLineListLocal.get()); // Append the results from the local table to the main table // Update total line count and check if we've exceeded the maximum uint64_t uCurrentLines = uAtomicTotalLines.fetch_add(ptableLineListLocal->get_row_count()) + ptableLineListLocal->get_row_count(); if( uCurrentLines > uMax ) { uAtomicFileIndex.store(uFileCount); // Signal other threads to stop by setting file index to max } } ptableLineListLocal->row_clear(); // Clear local table rows for next iteration uint64_t uProcessed = uAtomicProcessedCount.fetch_add(1) + 1; // Update progress (thread-safe) if(uProcessed % 10 == 0) // Show progress every 10 files { std::lock_guard<std::mutex> lockProgress(mutexProgress); uint64_t uPercent = (uProcessed * 100) / uFileCount; MESSAGE_Progress("", {{"percent", uPercent}, {"label", "Find in files"}, {"sticky", true}}); } } catch(const std::exception& exception_) { std::lock_guard<std::mutex> lockErrors(mutexErrors); vectorErrors.push_back(std::string("Thread ") + std::to_string(iThreadId) + " error: " + exception_.what()); } } }; // ## Prepare and run threads .............................................. if( iThreadCount <= 0 ) { iThreadCount = std::thread::hardware_concurrency(); } // Use hardware concurrency if no thread count is specified if(iThreadCount <= 0) { iThreadCount = 1; } // Fallback to single thread if hardware_concurrency returns 0 if( iThreadCount > 8 ) { iThreadCount = 8; } // Limit to 8 threads for performance and resource management if( ptableFile->size() < iThreadCount ) { iThreadCount = (int)ptableFile->size(); } // Limit threads to number of files // Create and launch worker threads std::vector<std::thread> vectorPatternThread; vectorPatternThread.reserve(iThreadCount); for(int i = 0; i < iThreadCount; ++i) { vectorPatternThread.emplace_back(process_, i); } // Wait for all threads to complete for(auto& threadWorker : vectorPatternThread) { threadWorker.join(); } MESSAGE_Progress("", {{"clear", true}}); // Clear progress message // ### Handle any collected errors if(!vectorErrors.empty()) { for(const auto& stringError : vectorErrors) { ERROR_Add(stringError); } } return {true, ""}; }

Dold text

En sak du rätt direkt upptäckt med unit-tester är att din kod inte kommer ge {false, ERROR} även när det går fel.

Sen finns ett gäng prestandaproblem som false-sharing och en arkitektur som skalar långt sämre med kärnor än vad som vore möjligt om du i stället använt högnivå-tekniker som t.ex. Rust Rayon.

Dina atomics är över-fence:ade, uAtomicTotalLines är bättre att räkna ut mer worker med icke-atomics och sedan slå ihop på slutet (är trivialt i de flesta fork-join ramverk, t.ex. Rayon).

Med lite mer avancerade unit-tester hade du också kunna noterat att ditt terminal condition kan missas så workers forsätter jobba även när man egentligen är klar. Det är _nog_ bara en prestandabug, men har inte kolla supernoga.

Kommer läcka minne om ptableLineList->to_columns(...) eller std::make_unique<table>(...) kastar, det kan man lätt fånga med unit-tester.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av Yoshman:

En sak du rätt direkt upptäckt med unit-tester är att din kod inte kommer ge {false, ERROR} även när det går fel.

Byggde om metoden för trådade upp den under kvällen istället för att returnera fel samlar den felen i "std::vector<std::string> vectorError;" men just felhanteringen är något jag skall titta över.

Skrivet av Yoshman:

Sen finns ett gäng prestandaproblem som false-sharing och en arkitektur som skalar långt sämre med kärnor än vad som vore möjligt om du i stället använt högnivå-tekniker som t.ex. Rust Rayon.

Den metoden som tar klart mest tid är "COMMAND_CollectPatternStatistics", allt är byggt för att den skall skala så bra som möjligt och jag testar mot ripgrep. Vad som görs är att räkna saker i källkodsfiler och det går i princip inte att märka någon skillnad på ripgrep och den här, möjligen är ripgrep något snabbare men det märks först om det är flera miljoner rader kod (+3 miljoner) och bara första gången för sedan går det så snabbt att det är svårt att uppskatta. Ripgrep söker vad jag sett i all text, det gör inte den här utan den delar först upp koden i kod/kommentarer/strängar så inte helt lätt att jämföra.

Ganska intressant skalningsproblem i alla fall för så här:
"auto* ptableFile = CACHE_Get("file");" där ligger det filer som skall räknas och man vet inte hur många filer som finns där eller hur stora de är. Därför går det exempelvis inte att bara dela den i lika stora tårtbitar och köra i olika trådar utan trådarna behöver ta "nästa på tur" om skalningen skall bli så effektivt som möjligt. Också därför som det kanske är väl många synkroninseringar men ser inte det som ett så stort problem då just räkning av information i filen tar så mycket längre tid.

Skrivet av Yoshman:

Med lite mer avancerade unit-tester hade du också kunna noterat att ditt terminal condition kan missas så workers forsätter jobba även när man egentligen är klar. Det är _nog_ bara en prestandabug, men har inte kolla supernoga.

Där är egentligen min fråga, jag hade fått lägga massor av tid på att testa den här och då måste man ju väga det emot att få det färdigt samt den här typen av kod lever ordentligt. Det är saker som kan ändra sig mycket tills man är nöjd. Att skriva tester mot den här tar längre tid än att skriva logiken, mycket mer och vad händer när jag ändrar.

Skrivet av Yoshman:

Kommer läcka minne om ptableLineList->to_columns(...) eller std::make_unique<table>(...) kastar, det kan man lätt fånga med unit-tester.

Den lösningen ganska speciell men just nu läcker det inte kolumn informationen ligger i ett separat objekt med referensräknare. Det är två olika tabeller, den ena tabellen äger allt sitt minne, men så finns en annan tabell som är nära nog en spegelbild metodmässigt men som fungerar annorlunda internt just för att kunna skala i trådar. Onödigt att skapa upp information om det ändå bara är kopior. När tabellen som inte äger kolumndata får med kolumninformationen ökar de referensräknaren med ett och räknar ner när de släpper den. Däremot så ligger referensräknaren på 0 efter "ptableLineList->to_columns(...)" så behöver inte göra en release. Får fundera om det är optimalt sett till att det skall gå och förstå. Men den är lurig.

EDIT:
Såg att jag klistrat in inte den färdiga koden, ingen stor ändring men här

// ## Prepare columns for line list table detail::columns* pcolumnsThread = new detail::columns{}; ptableLineList->to_columns( *pcolumnsThread ); // ## Worker function to process files in parallel ......................... auto process_ = [&](int iThreadId) -> void { // Create thread-local table for collecting results std::unique_ptr<table> ptableLineListLocal = std::make_unique<table>(pcolumnsThread, 10, ptableLineList->get_flags(), 10); // Create local table with 10 rows pre-allocated while( true ) { // Get next file index to process

Permalänk
Skrivet av klk:

Där är egentligen min fråga, jag hade fått lägga massor av tid på att testa den här och då måste man ju väga det emot att få det färdigt samt den här typen av kod lever ordentligt. Det är saker som kan ändra sig mycket tills man är nöjd. Att skriva tester mot den här tar längre tid än att skriva logiken, mycket mer och vad händer när jag ändrar.

Samma invändningar återkommer igen och igen: det kommer ta för lång tid och det kommer vara jobbigt när man ändrar i koden. Då undrar jag om du över huvud taget utför någon testning av dina gjorda ändringar. Det kommer väl vara ännu jobbigare att sätta upp systemtester för att testa dessa ändringar? Eller kör du ditt grep++-program med lite olika inputs och är nöjd om det inte kraschar? Med testning på den nivån vet du ju inte ens om den ändrade koden är körd.

Hur klarar ni er med testning på den nivån? Vad har du för folk i ditt team? Alla kan inte ha samma Rain Man-lika kvalitéer som du. Tar ni aldrig in nya? Eller får de hålla sig i sandlådan i två år och göra alla misstag man kan göra innan de släpps lös i produktionskoden?