Unit Testing C ++: Was soll getestet werden?

20

TL; DR

Gute, nützliche Tests zu schreiben ist schwierig und mit hohen Kosten in C ++ verbunden. Können Sie erfahrenen Entwicklern Ihre Überlegungen mitteilen, was und wann zu testen ist?

Lange Geschichte

Früher habe ich testgetrieben entwickelt, mein gesamtes Team, aber es hat bei uns nicht gut funktioniert. Wir haben viele Tests, aber sie scheinen nie die Fälle abzudecken, in denen wir tatsächliche Fehler und Rückschritte haben - die normalerweise auftreten, wenn Einheiten interagieren, und nicht aufgrund ihres isolierten Verhaltens.

Dies ist auf Geräteebene oft so schwierig zu testen, dass wir TDD eingestellt haben (mit Ausnahme von Komponenten, bei denen die Entwicklung wirklich beschleunigt wird) und stattdessen mehr Zeit investiert haben, um die Abdeckung der Integrationstests zu erhöhen. Während die kleinen Unit-Tests keine wirklichen Fehler auffingen und im Grunde nur Wartungsaufwand waren, haben sich die Integrationstests wirklich gelohnt.

Jetzt habe ich ein neues Projekt geerbt und frage mich, wie ich es testen soll. Da es sich um eine native C ++ / OpenGL-Anwendung handelt, kommen Integrationstests nicht in Frage. Unit-Tests in C ++ sind jedoch etwas schwieriger als in Java (Sie müssen explizit Dinge erstellen virtual), und das Programm ist nicht stark objektorientiert, sodass ich einige Dinge nicht verspotten oder wegstupsen kann.

Ich möchte das Ganze nicht auseinandernehmen und nur ein paar Tests schreiben, um Tests schreiben zu können. Also frage ich dich: Wofür soll ich Tests schreiben? z.B:

  • Funktionen / Klassen, die sich voraussichtlich häufig ändern?
  • Funktionen / Klassen, die schwieriger manuell zu testen sind?
  • Funktionen / Klassen, die bereits leicht zu testen sind?

Ich fing an, einige respektvolle C ++ - Codebasen zu untersuchen, um zu sehen, wie sie getestet werden. Im Moment befasse ich mich mit dem Chromium-Quellcode, aber es fällt mir schwer, die Testgründe aus dem Code zu extrahieren. Wenn jemand ein gutes Beispiel oder einen guten Beitrag dazu hat, wie beliebte C ++ - Benutzer (Leute vom Komitee, Buchautoren, Google, Facebook, Microsoft, ...) dies angehen, wäre das besonders hilfreich.

Aktualisieren

Ich habe mich auf dieser Site und im Web umgesehen, seit ich das geschrieben habe. Einige gute Sachen gefunden:

Leider sind alle eher Java / C # -zentriert. Das Schreiben vieler Tests in Java / C # ist kein großes Problem, daher überwiegt der Nutzen in der Regel die Kosten.

Aber wie ich oben geschrieben habe, ist es schwieriger in C ++. Besonders wenn Ihre Codebasis nicht so gut ist, müssen Sie die Dinge ernsthaft durcheinander bringen, um eine gute Abdeckung durch Unit-Tests zu erhalten. Zum Beispiel: Die von mir geerbte Anwendung hat einen GraphicsNamensraum, der eine dünne Schicht über OpenGL ist. Um eine der Entitäten zu testen, die alle ihre Funktionen direkt verwenden, müsste ich diese in eine Schnittstelle und eine Klasse umwandeln und in alle Entitäten einfügen. Das ist nur ein Beispiel.

Denken Sie bei der Beantwortung dieser Frage bitte daran, dass ich für das Schreiben von Tests ziemlich viel Geld investieren muss.

futlib
quelle
3
+1 für die Schwierigkeit beim Unit-Testen von C ++. Wenn Sie bei Ihrem Unit-Test den Code ändern müssen, tun Sie dies nicht.
DPD
2
@DPD: Ich bin mir nicht so sicher, was ist, wenn etwas wirklich einen Test wert ist? In der aktuellen Codebasis kann ich kaum etwas im Simulationscode testen, da alle Grafikfunktionen direkt aufgerufen werden und ich sie nicht verspotten / stubben kann. Alles, was ich jetzt testen kann, sind Utility-Funktionen. Aber ich bin damit einverstanden, dass es sich falsch anfühlt, den Code so zu ändern, dass er "testbar" ist. TDD-Befürworter sagen oft, dass dies Ihren gesamten Code auf alle erdenklichen Arten verbessern wird, aber ich bin demütig anderer Meinung. Nicht alles benötigt eine Schnittstelle und mehrere Implementierungen.
Futlib
Lassen Sie mich ein aktuelles Beispiel geben: Ich habe einen ganzen Tag lang versucht, eine einzelne Funktion (in C ++ / CLI geschrieben) zu testen, und das Testtool MS Test stürzte für diesen Test immer ab. Es schien ein Problem mit einfachen CPP-Referenzen zu geben. Stattdessen habe ich nur die Ausgabe der aufrufenden Funktion getestet und es hat gut funktioniert. Ich habe einen ganzen Tag mit einer Funktion für UT verschwendet. Das war ein Verlust an kostbarer Zeit. Außerdem konnte ich kein für meine Bedürfnisse geeignetes Stummelwerkzeug bekommen. Ich habe, wo immer möglich, manuell gestubbt.
DPD
Das ist genau das, was ich vermeiden möchte: Ich denke, wir C ++ - Entwickler müssen beim Testen besonders pragmatisch sein. Du hast es letztendlich getestet, also denke ich, dass das in Ordnung ist.
Futlib
@DPD: Ich habe noch etwas darüber nachgedacht, und ich denke, Sie haben Recht, die Frage ist, welche Art von Kompromiss ich eingehen möchte. Lohnt es sich, das gesamte Grafiksystem zu überarbeiten, um einige Entitäten zu testen? Es gab keinen Fehler, von dem ich weiß, also wahrscheinlich: Nein. Wenn es sich fehlerhaft anfühlt, schreibe ich Tests. Schade, ich kann deine Antwort nicht akzeptieren, weil es ein Kommentar ist :)
Futlib

Antworten:

5

Unit Testing ist nur ein Teil. Integrationstests helfen Ihnen bei den Problemen Ihres Teams. Integrationstests können für alle Arten von Anwendungen geschrieben werden, auch für native und OpenGL-Anwendungen. Sie sollten sich "Growing Object Oriented Software Guided by Tests" von Steve Freemann und Nat Pryce ansehen (z . B. http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Es führt Sie Schritt für Schritt durch die Entwicklung einer Anwendung mit GUI und Netzwerkkommunikation.

Testen von Software, die nicht getestet wurde, ist eine andere Geschichte. Überprüfen Sie Michael Feathers "Effektiv mit Legacy-Code arbeiten" (http://www.amazon.com/Working-Effective-Legacy-Michael-Feathers/dp/0131177052).

EricSchaefer
quelle
Ich kenne beide Bücher. Die Dinge sind: 1. Wir wollen nicht mit TDD gehen, weil es bei uns nicht gut funktioniert hat. Wir wollen Tests, aber nicht religiös. 2. Ich bin mir sicher, dass Integrationstests für OpenGL-Anwendungen möglich sind, aber es ist zu aufwändig. Ich möchte die App weiter verbessern und kein Forschungsprojekt starten.
Futlib
Beachten Sie, dass Unit-Tests "nachträglich" immer schwieriger sind als "zuerst testen", da der Code nicht testbar (wiederverwendbar, wartbar usw.) ist. Wenn Sie es trotzdem tun möchten, versuchen Sie, Michael Feathers Tricks (z. B. Nähte) beizubehalten, auch wenn Sie TDD vermeiden. Wenn Sie beispielsweise eine Funktion erweitern möchten, versuchen Sie etwas wie "Sprout-Methode" und versuchen Sie, die neue Methode testbar zu halten. Es ist möglich zu tun, aber meiner Meinung nach schwieriger.
EricSchaefer
Ich bin damit einverstanden, dass nicht-TDD-Code nicht testbar ist, aber ich würde nicht sagen, dass er per se nicht wartbar oder wiederverwendbar ist - wie ich oben ausgeführt habe, benötigen einige Dinge einfach keine Schnittstellen und mehrere Implementierungen. Mit Mockito überhaupt kein Problem, aber in C ++ muss ich alle Funktionen, die ich stummschalten / verspotten möchte, virtuell machen. Jedenfalls ist derzeit nicht testbarer Code mein größtes Problem: Ich muss einige grundlegende Dinge ändern, um einige Teile testbar zu machen, und daher möchte ich eine gute Begründung dafür, was zu testen ist, um sicherzustellen, dass es sich lohnt.
Futlib
Sie haben natürlich Recht, ich werde darauf achten, jeden neuen Code, den ich schreibe, testbar zu machen. Mit der Art und Weise, wie die Dinge in dieser Codebasis derzeit funktionieren, wird es jedoch nicht einfach.
Futlib
Überlegen Sie sich beim Hinzufügen einer Funktion, wie Sie sie testen könnten. Könnten Sie irgendwelche hässlichen Abhängigkeiten einschleusen? Woher wissen Sie, dass die Funktion das tut, was sie tun soll? Könnten Sie irgendein Verhalten beobachten? Gibt es ein Ergebnis, das Sie auf Richtigkeit überprüfen könnten? Gibt es Invarianten, die Sie überprüfen können?
EricSchaefer
2

Es ist eine Schande, TDD "hat nicht gut für Sie funktioniert." Ich denke, das ist der Schlüssel zum Verständnis, wohin man sich wenden muss. Sehen Sie sich noch einmal an und verstehen Sie, warum TDD nicht funktioniert hat, was Sie besser hätten machen können und warum es Schwierigkeiten gab.

Natürlich haben Ihre Unit-Tests die gefundenen Bugs nicht gefunden. Das ist genau der Punkt. :-) Sie haben diese Fehler nicht gefunden, weil Sie sie von vornherein verhindert haben, indem Sie darüber nachgedacht haben, wie die Schnittstellen funktionieren sollten und wie sichergestellt werden kann, dass sie ordnungsgemäß getestet wurden.

Wie Sie festgestellt haben, ist es schwierig, einen Unit-Test-Code zu beantworten, der nicht für den Test vorgesehen ist. Für vorhandenen Code ist es möglicherweise effektiver, eine Funktions- oder Integrationstestumgebung als eine Komponententestumgebung zu verwenden. Testen Sie das System insgesamt und konzentrieren Sie sich dabei auf bestimmte Bereiche.

Natürlich wird die Neuentwicklung von TDD profitieren. Wenn neue Funktionen hinzugefügt werden, kann das Refactoring für TDD dazu beitragen, die neue Entwicklung zu testen und gleichzeitig die Entwicklung eines neuen Komponententests für die älteren Funktionen zu ermöglichen.

Bill Door
quelle
4
Wir haben ungefähr eineinhalb Jahre lang TDD gemacht, alle ziemlich leidenschaftlich. Beim Vergleich der TDD-Projekte mit früheren Projekten, die ohne TDD (aber nicht ohne Tests) erstellt wurden, würde ich nicht sagen, dass sie tatsächlich stabiler sind oder über besser gestalteten Code verfügen. Vielleicht ist es unser Team: Wir paaren und überprüfen viel, unsere Codequalität war schon immer ziemlich gut.
Futlib
1
Je mehr ich darüber nachdenke, desto mehr denke ich, dass TDD einfach nicht zu der Technologie dieses bestimmten Projekts passt: Flex / Swiz. Es gibt viele Ereignisse und Bindungen und Injektionen, die die Interaktion zwischen Objekten erschweren und einen Komponententest fast unmöglich machen. Das Entkoppeln dieser Objekte macht es nicht besser, da sie in erster Linie korrekt funktionieren.
Futlib
2

Ich habe TDD in C ++ noch nicht durchgeführt, daher kann ich das nicht kommentieren, aber Sie sollten das erwartete Verhalten Ihres Codes testen. Während sich die Implementierung ändern kann, sollte das Verhalten (normalerweise?) Gleich bleiben. In Java \ C # -zentrierten Umgebungen bedeutet dies, dass Sie nur die öffentlichen Methoden testen, Tests für das erwartete Verhalten schreiben und dies vor der Implementierung tun (was normalerweise besser gesagt als getan ist :)).

Dante
quelle