Praktische Softwaretechnik - Exam
Aufgabe 1)
Du arbeitest als Scrum Master in einem Softwareentwicklungsteam, welches nach den Prinzipien des Agile Manifests arbeitet. Dein Team entwickelt eine Anwendung zur Verwaltung von Patientenakten und hat wöchentliches Sprint-Planning sowie tägliche Stand-Up-Meetings eingerichtet. Während des aktuellen Sprints hat das Kundenmanagement darum gebeten, eine neue Funktion hinzuzufügen, die ursprünglich nicht in der Sprint-Planung enthalten war. Bei der Betrachtung der Gesamtleistung des Teams stellst Du fest, dass einige Teammitglieder regelmäßig Überstunden machen, um die Sprint-Ziele zu erreichen. Zusätzlich bemerkst Du, dass die Kommunikation zwischen den Entwicklern und den Kunden oft nur über E-Mails stattfindet, was zu Missverständnissen führt. Zudem gibt es Diskussionen im Team darüber, wie viele Ressourcen für das Design der Software und die langfristige Wartbarkeit aufgewendet werden sollen.
a)
Teilaufgabe 1: Analysiere die aktuelle Situation Deines Teams anhand der Prinzipien des Agile Manifests und identifiziere drei spezifische Probleme, die gegen diese Prinzipien verstoßen. Begründe Deine Antwort und gib Vorschläge, wie diese Probleme behoben werden können.
Lösung:
Teilaufgabe 1:Analyse der aktuellen Situation unter Berücksichtigung der Prinzipien des Agile Manifests:
- Erstens: Das Kundenmanagement hat während des laufenden Sprints eine neue Funktion angefordert, die ursprünglich nicht geplant war. Dies widerspricht dem Prinzip der agilen Veränderungsakzeptanz für Wettbewerbsvorteile des Kunden. Das Team sollte Änderungen willkommen heißen, selbst wenn sie spät in der Entwicklung auftreten. Allerdings sollte dies strukturiert und durch entsprechende Prozesse wie das Backlog Refinement und Change Control geschehen, um die Teamleistung nicht negativ zu beeinflussen. Vorschlag: Änderungen sollten immer zuerst in das Backlog aufgenommen und in der nächsten Sprint-Planung berücksichtigt werden, um eine stabile und vorhersehbare Sprint-Arbeit zu gewährleisten.
- Zweitens: Einige Teammitglieder machen regelmäßig Überstunden, um die Sprint-Ziele zu erreichen. Dies widerspricht dem Prinzip einer nachhaltigen Entwicklungsweise, bei der die Beteiligten ein gleichmäßiges Tempo auf unbestimmte Zeit halten können sollten. Unverhältnismäßiger Einsatz führt langfristig zu Burnout und sinkender Produktivität. Vorschlag: Es sollte sichergestellt werden, dass die Sprint-Ziele realistisch gesetzt werden, womit eine Überarbeitung des Velocity-Indikators des Teams notwendig sein könnte. Zudem könnte eine regelmäßige Retrospektive helfen, diese Problematik anzusprechen und Maßnahmen zu entwickeln, um die Arbeitslast während der Sprints nachhaltiger zu gestalten.
- Drittens: Die Kommunikation zwischen Entwicklern und Kunden findet hauptsächlich über E-Mails statt, was zu Missverständnissen führen kann. Dies widerspricht dem agilen Prinzip der bevorzugten Nutzung von persönlicher Kommunikation (face-to-face communication). Direkte Gespräche sind effektiver und effizienter. Vorschlag: Regelmäßige Meetings mit den Kunden, wie zum Beispiel Sprint Reviews und Ad-hoc Meetings per Telefon oder Videokonferenz, sollten eingeführt werden, um Missverständnisse zu verringern und eine effizientere Kommunikation sicherzustellen. Hierbei sollte ein agiler Coach oder Scrum Master diese Interaktionen erleichtern.
b)
Teilaufgabe 2: Ein Mitglied deines Teams hat vorgeschlagen, dass die Teammitglieder in regelmäßig geplanten Retrospektiven ihre Ideen und Verbesserungsvorschläge einbringen sollten. Erkläre, welches Prinzip des Agile Manifests dadurch unterstützt wird und wie solch eine Retrospektive strukturiert werden könnte, um die Effektivität des Teams zu maximieren. Nutze geeignete Beispiele, um Deine Antwort zu untermauern.
Lösung:
Teilaufgabe 2:Wenn das Teammitglied vorschlägt, regelmäßig geplante Retrospektiven durchzuführen, unterstützt dies insbesondere das Prinzip des Agile Manifests „Reflektiere regelmäßig, wie man effektiver werden kann, und passe das Verhalten entsprechend an“. Diese Prinzip zielt darauf ab, kontinuierliche Verbesserungen und Lernprozesse im Team zu fördern.
- Struktur einer effektiven Retrospektive:
- 1. Vorbereitung: Der Scrum Master bereitet die Retrospektive vor, indem er relevante Daten und Berichte sammelt (zum Beispiel Burndown Charts, Velocity Reports usw.). Der Raum oder die Online-Umgebung sollte vorbereitet und für eine offene Kommunikation geeignet sein.
- 2. Einleitung: Der Scrum Master startet die Retrospektive mit einer kurzen Einleitung. Dies könnte ein Recap des letzten Sprints sein, in dem die erzielten Ziele und Herausforderungen erläutert werden.
- 3. Datensammlung: In dieser Phase bringen die Teammitglieder ihre Beobachtungen und Gedanken ein. Es kann hilfreich sein, dies in drei Kategorien zu unterteilen: Was lief gut? Was lief nicht so gut? Was können wir verbessern? Zum Beispiel könnte ein Entwickler darauf hinweisen, dass die Kommunikation zwischen den Teams und den Kunden verbessert werden muss, um Missverständnisse zu reduzieren.
- 4. Diskussion und Analyse: Hier werden die gesammelten Punkte diskutiert und vertieft. Das Team identifiziert die Ursachen der Probleme und findet heraus, was zu den positiven Aspekten beigetragen hat. Zum Beispiel könnte das Team feststellen, dass regelmäßige Check-ins mit den Kunden über Videoanrufe effektiver sind als E-Mails.
- 5. Maßnahmenplanung: Das Team einigt sich auf konkrete, umsetzbare Maßnahmen, um die identifizierten Probleme zu lösen und positive Praktiken zu verstärken. Zum Beispiel könnte das Team entscheiden, wöchentliche Videoanrufe mit dem Kunden einzuplanen und die Überstunden-Problematik durch eine realistischere Sprint-Planung anzugehen.
- 6. Abschluss: Abschließend fasst der Scrum Master die besprochenen Punkte und geplanten Maßnahmen zusammen und schließt die Retrospektive mit einem positiven Ausblick ab, um das Team zu motivieren.
Durch eine klare Struktur kann die Retrospektive sicherstellen, dass alle Teammitglieder ihre Meinungen und Ideen einbringen können, was zur kontinuierlichen Verbesserung der Teamleistung führt. Dies fördert auch eine Kultur des Vertrauens und der Zusammenarbeit im Team, da jeder die Möglichkeit hat, Verbesserungsvorschläge zu machen und diese auch umzusetzen.
Aufgabe 2)
Scrum-Prozess und RollenScrum ist ein agiles Framework zur Softwareentwicklung mit iterativen Entwicklungszyklen (Sprints). Es betont Teamarbeit, kontinuierliche Verbesserung und Kundenzufriedenheit.
- Product Owner: Verantwortlich für Produktvision und Backlog-Management.
- Scrum Master: Sicherstellt das Verständnis und die Einhaltung von Scrum-Prinzipien, beseitigt Hindernisse.
- Entwicklungsteam: Interdisziplinäres Team, das Arbeit während des Sprints erledigt.
- Sprint: Feste Zeitspanne (1-4 Wochen) zur Implementierung von Inkrementen.
- Sprint Planning: Treffen zur Sprint-Zielsetzung und Aufgabenverteilung.
- Daily Scrum: Tägliches 15-minütiges Meeting zur Synchronisation.
- Sprint Review: Präsentation des Arbeitsergebnisses am Ende des Sprints.
- Sprint Retrospective: Reflexion und Verbesserung des Prozesses nach jedem Sprint.
- Produkt-Backlog: Priorisierte Liste von Aufgaben und Anforderungen.
- Sprint-Backlog: Ausgewählte Aufgaben für den aktuellen Sprint.
- Inkrement: Fertiges, nutzbares Produktteil.
a)
Du bist der Scrum Master eines Entwicklungsteams. Während des letzten Sprints bemerkst Du, dass einige Teammitglieder Schwierigkeiten haben, ihre Aufgaben rechtzeitig abzuschließen. Welche spezifischen Maßnahmen könntest Du in der nächsten Sprint Retrospective vorschlagen, um diesen Herausforderungen zu begegnen und die Teamleistung zu verbessern? Begründe Deine Vorschläge.
Lösung:
Maßnahmen in der nächsten Sprint RetrospectiveAls Scrum Master könntest Du die folgenden spezifischen Maßnahmen vorschlagen, um die Schwierigkeiten der Teammitglieder bei der rechtzeitigen Abschluss der Aufgaben anzugehen und die Teamleistung zu verbessern:
- Bessere Aufgabenschätzung und -planung: Schlage vor, dass das Team die Story Points oder andere Schätzungsmethoden überdenkt und möglicherweise Schulungen zur genauen Schätzung durchführt. Exakte Schätzungen helfen dabei, die Komplexität und den benötigten Aufwand realistischer einzuschätzen und Überlastungen zu vermeiden.
- Verstärkte Kommunikation: Betone die Wichtigkeit der Kommunikation im Team. Ermutige das Team zu häufigeren kurzen Abstimmungen, insbesondere außerhalb des täglichen Stand-ups, um Unsicherheiten und Blockaden frühzeitig zu identifizieren und zu beseitigen.
- Identifikation und Beseitigung von Blockaden: Setze den Fokus auf die frühzeitige Identifikation von Hindernissen im Sprint. Ermögliche es dem Team, Blockaden offen anzusprechen und entwickle gemeinsam Pläne zur schnellen Beseitigung dieser Hindernisse.
- Pair Programming und Peer Reviews: Ermutige das Team, häufiger in Paaren zu arbeiten und gegenseitige Code Reviews durchzuführen. Dies kann nicht nur die Codequalität verbessern, sondern auch das Wissen und die Fähigkeiten im Team verteilen, wodurch der Abschluss von Aufgaben beschleunigt wird.
- Verbesserung der Selbstorganisation: Führe Workshops und Schulungen durch, um die Fähigkeiten des Teams zur Selbstorganisation zu stärken. Ein stark selbstorganisiertes Team kann effektiver und effizienter arbeiten, da es seine Aufgaben autonom planen und umsetzen kann.
- Schulung und Weiterbildung: Identifiziere Wissenslücken oder fehlende Fähigkeiten im Team und plane gezielte Schulungs- und Weiterbildungsmaßnahmen. Dies befähigt die Teammitglieder, ihre Aufgaben kompetenter und schneller abzuschließen.
- Überprüfung der Work-in-Progress (WIP)-Grenzen: Analysiere und passe gegebenenfalls die WIP-Grenzen an, um sicherzustellen, dass das Team nicht zu viele Aufgaben gleichzeitig in Angriff nimmt. Weniger parallele Arbeiten können zu einem fokussierteren und produktiveren Arbeitsumfeld führen.
- Retrospektiven mit Fokus auf kontinuierliche Verbesserung: Führe regelmäßige Retrospektiven durch, um kontinuierlich Verbesserungsmöglichkeiten zu identifizieren. Stelle sicher, dass Aktionspunkte aus den Retrospektiven nachverfolgt und umgesetzt werden, um nachhaltige Veränderungen herbeizuführen.
Begründung:- Durch bessere Aufgabenschätzung hilft es bei der Vermeidung von Überlastung und unrealistischen Erwartungen.
- Verbesserte Kommunikation fördert die Transparenz und frühzeitige Identifikation von Problemen.
- Die Beseitigung von Blockaden ermöglicht es dem Team, ungehindert zu arbeiten.
- Pair Programming und Peer Reviews fördern Wissensaustausch und erzeugen höhere Codequalität.
- Stärkere Selbstorganisation erhöht die Effizienz und Effektivität des Teams.
- Gezielte Schulungen und Fortbildungen schließen Wissenslücken und verbessern die Fähigkeiten der Teammitglieder.
- Die Anpassung der WIP-Grenzen fördert den Fokus und steigert die Produktivität.
- Kontinuierliche Retrospektiven tragen zur ständigen Verbesserung und Anpassung der Arbeitsprozesse bei.
b)
Der Product Owner hat festgestellt, dass ein wesentliches Feature im Produkt Backlog vorhanden ist aber noch nicht vom Entwicklungsteam für den aktuellen Sprint ausgewählt wurde. Erkläre, wie der Product Owner die Priorisierung und Einplanung dieses Features in den nächsten Sprint beeinflussen kann. Welche Meetings und Methoden stehen ihm zur Verfügung?
Lösung:
Einfluss des Product Owners auf die Priorisierung und Einplanung eines wesentlichen FeaturesDer Product Owner hat verschiedene Möglichkeiten und Methoden, um die Priorisierung und Einplanung eines wesentlichen Features im nächsten Sprint zu beeinflussen. Hier sind die wichtigsten Meetings und Methoden, die ihm dafür zur Verfügung stehen:
- Produkt-Backlog-Priorisierung: Der Product Owner kann die Priorisierung des Features im Produkt-Backlog anpassen. Durch die Aktualisierung der Priorität kann er sicherstellen, dass das Feature stärker in den Fokus rückt und vom Entwicklungsteam in Betracht gezogen wird.
- Backlog Refinement (Backlog-Verfeinerung): Während der regelmäßigen Backlog-Refinement-Sitzungen kann der Product Owner das Feature dem Team detaillierter erklären und seine Wichtigkeit betonen. Hier können auch Anmerkungen und Anforderungen präzisiert werden, um das Feature besser verständlich zu machen.
- Sprint Planning: Im Sprint Planning trifft sich das gesamte Team, um die Ziele und Aufgaben für den kommenden Sprint festzulegen. Der Product Owner hat hier die Möglichkeit, die Wichtigkeit des Features zu erläutern und das Team davon zu überzeugen, es in den nächsten Sprint aufzunehmen. Er kann darlegen, wie dieses Feature zur Produktvision beiträgt und welche Kundenvorteile damit verbunden sind.
- Definition of Done (DoD): Der Product Owner kann sicherstellen, dass das wesentliche Feature klar definierte Akzeptanzkriterien hat, die im Einklang mit der Definition of Done stehen. Dies kann helfen, das Feature schneller durch den Entwicklungsprozess zu bringen.
- Zusammenarbeit und Kommunikation: Kontinuierliche Kommunikation mit dem Entwicklungsteam ist entscheidend. Der Product Owner sollte den Wert und die Dringlichkeit des Features regelmäßig hervorheben und Verständnis dafür schaffen, warum es eine hohe Priorität hat.
- Stakeholder Feedback und Reviews: Der Product Owner kann auch Feedback von Stakeholdern und Kunden einholen und präsentieren, um die Bedeutung des Features weiter zu untermauern. Dies kann während der Sprint Reviews oder anderer relevanter Meetings geschehen.
Begründung:- Durch die Produkt-Backlog-Priorisierung wird sichergestellt, dass das Feature gut sichtbar und für den nächsten Sprint in Betracht gezogen wird.
- Backlog Refinements sorgen dafür, dass das Team das Feature vollständig versteht und dass es keine Unklarheiten oder Missverständnisse gibt.
- Sprint Planning ermöglicht es dem Product Owner, das Feature direkt in den Planungsprozess einzubringen und das Team von seinem Wert zu überzeugen.
- Klare Akzeptanzkriterien und eine gut definierte Definition of Done tragen zur besseren Planung und Umsetzung des Features bei.
- Regelmäßige und offene Kommunikation fördert ein gemeinsames Verständnis und Prioritätenalignment zwischen dem Product Owner und dem Entwicklungsteam.
- Stakeholder-Feedback hilft dabei, die dringenden Bedürfnisse der Kunden und Stakeholder hervorzuheben und die Priorität des Features weiter zu rechtfertigen.
c)
Nehmen wir an, dass Dein Entwicklungsteam während eines Sprints auf unvorhergesehene Probleme stößt, die ihre Arbeitsgeschwindigkeit beeinträchtigen. Wie solltest Du als Scrum Master auf solche Hindernisse reagieren? Beschreibe die Tools und Methoden, die Du verwenden kannst, um diese Probleme zu lösen und den Sprint erfolgreich abzuschließen.
Lösung:
Reaktion auf unvorhergesehene Probleme während eines Sprints als Scrum MasterAls Scrum Master ist es Deine Aufgabe, das Entwicklungsteam bei der Bewältigung von Hindernissen zu unterstützen und sicherzustellen, dass der Sprint erfolgreich abgeschlossen werden kann. Hier sind einige der wichtigsten Tools und Methoden, die Du verwenden kannst, um auf unvorhergesehene Probleme zu reagieren:
- Daily Scrum: Nutze das tägliche Stand-up-Meeting, um rasch Informationen über den aktuellen Stand der Arbeit und die auftretenden Probleme einzuholen. Ermutige die Teammitglieder, Hindernisse offen anzusprechen, damit sie frühzeitig erkannt und bearbeitet werden können.
- Impediment Backlog: Führe ein Impediment Backlog, um alle Hindernisse zu dokumentieren und deren Bearbeitung nachzuverfolgen. Priorisiere die Hindernisse und sorge für eine schnelle Behebung, um den Fortschritt des Teams nicht zu beeinträchtigen.
- Enger Kontakt und Unterstützung: Stehe dem Entwicklungsteam zur Seite, indem Du regelmäßig nach dem Fortschritt fragst und anbietest, Barrieren zu beseitigen. Dies kann durch direkte Unterstützung oder durch die Organisation von Meetings mit relevanten Stakeholdern und Experten geschehen.
- Timeboxing: Setze Timeboxes für die Untersuchung und Behebung von Problemen. Dies hilft dem Team, fokussiert und effizient zu arbeiten, ohne zu viel Zeit für die Problemlösung zu verwenden.
- Pair Programming und Swarming: Ermutige das Team, bei komplexen oder schwierigen Aufgaben in Paaren oder als Gruppe zu arbeiten. Pair Programming ermöglicht es zwei Entwicklern, gemeinsam an einer Aufgabe zu arbeiten, während Swarming das gesamte Team oder große Teile des Teams auf ein bestimmtes Problem konzentriert.
- Sprint Retrospective: Nutze die Sprint Retrospective, um über die im Sprint aufgetretenen Probleme zu reflektieren und Maßnahmen zur zukünftigen Vermeidung und Bewältigung solcher Probleme zu entwickeln. Dies trägt zur kontinuierlichen Verbesserung des Prozesses bei.
- Zusätzliche Ressourcenzuweisung: Falls notwendig und möglich, können zusätzliche Ressourcen oder externe Experten hinzugezogen werden, um bestimmte Hindernisse zu bewältigen.
- Wissenstransfer und Weiterbildung: Organisiere Schulungen und Workshops, um Wissenslücken zu schließen und das Team besser auf unvorhergesehene Probleme vorzubereiten.
- Flexibilität und Anpassung: Sei bereit, den Sprint-Plan anzupassen, falls Hindernisse erhebliche Auswirkungen haben. Dies kann bedeuten, dass einige weniger wichtige Aufgaben verschoben oder neu priorisiert werden, um dem Team Zeit zur Problembewältigung zu geben.
Begründung:- Daily Scrums stellen sicher, dass Probleme frühzeitig erkannt und angesprochen werden.
- Ein Impediment Backlog hilft dabei, Hindernisse systematisch und priorisiert zu bearbeiten.
- Enger Kontakt und Unterstützung vermitteln dem Team, dass sie nicht allein bei der Problembewältigung sind.
- Timeboxing verhindert, dass zu viel Zeit für die Lösung eines einzelnen Problems verwendet wird.
- Pair Programming und Swarming fördern die Zusammenarbeit und ermöglichen das schnelle Erarbeiten von Lösungen.
- Sprint Retrospectives bieten eine Gelegenheit zur Reflexion und Verbesserung des zukünftigen Vorgehens.
- Zusätzliche Ressourcenzuweisung kann helfen, besonders schwierige Hindernisse schneller zu bewältigen.
- Wissenstransfer und Weiterbildung stärken die Fähigkeiten des Teams und bereiten sie besser auf Herausforderungen vor.
- Flexibilität und Anpassung des Sprint-Plans ermöglichen es dem Team, sich auf die wichtigsten Aufgaben zu konzentrieren.
d)
Stelle Dir vor, Du seist Teil des Entwicklungsteams und bemerkst, dass die täglichen Stand-up Meetings (Daily Scrums) oft länger als die vorgeschriebenen 15 Minuten dauern. Analysiere die möglichen Ursachen dafür und schlage Lösungen vor, wie die Meetings effizienter gestaltet werden können. Begründe Deine Vorschläge anhand von Scrum-Prinzipien.
Lösung:
Ursachenanalyse und Verbesserungsvorschläge für zu lange Daily ScrumsWenn die täglichen Stand-up Meetings (Daily Scrums) regelmäßig länger als die vorgeschriebenen 15 Minuten dauern, können verschiedene Ursachen dafür verantwortlich sein. Hier sind einige mögliche Ursachen und die entsprechenden Lösungen:Mögliche Ursachen:
- Zu detaillierte Diskussionen: Teammitglieder gehen zu sehr ins Detail und diskutieren technische Probleme oder Lösungen ausführlich.
- Nicht fokussierte Beiträge: Teilnehmer weichen vom eigentlichen Zweck des Daily Scrums ab und besprechen Themen, die nicht zum Meeting gehören.
- Unklarer Meetingablauf: Es gibt keine klare Struktur oder Agenda, die das Meeting leitet.
- Zu viele Teilnehmer: Es sind auch Personen anwesend, die nicht direkt am aktuellen Sprint beteiligt sind und unnötig Zeit beanspruchen.
- Unvorbereitete Teilnehmer: Teammitglieder sind nicht ausreichend auf das Meeting vorbereitet und überlegen sich ihre Beiträge erst während des Meetings.
Vorschläge zur Effizienzsteigerung:- Einhalten der Timebox: Betone die Wichtigkeit der Timebox und achte darauf, dass das Meeting strikt auf 15 Minuten begrenzt ist. Der Scrum Master kann als Timekeeper fungieren.
- Klar strukturierte Agenda: Nutze die vorgegebene Struktur von drei Fragen, um das Meeting fokussiert zu halten:
- Was habe ich seit dem letzten Daily Scrum erledigt?
- Was werde ich bis zum nächsten Daily Scrum erledigen?
- Welche Hindernisse stehen mir im Weg?
- Problematische Details auslagern: Ermutige das Team, detaillierte Diskussionen und technische Probleme nach dem Daily Scrum zu besprechen. Vereinbare separate Meetings für tiefergehende Themen.
- Vorbereitung der Teilnehmer: Stelle sicher, dass alle Teammitglieder sich vor dem Meeting Gedanken über ihre Beiträge machen. Dies kann durch eine Erinnerungs-E-Mail oder eine kurze Vorbereitungsphase vor dem Meeting geschehen.
- Relevante Teilnehmer: Begrenze die Teilnahme am Daily Scrum auf Personen, die direkt in den aktuellen Sprint involviert sind. Stakeholder oder andere Interessierte können über andere Kanäle informiert werden.
- Scrum-Prinzipien betonen: Erinnere das Team regelmäßig an die Scrum-Prinzipien und die Rolle des Daily Scrums als kurze Synchronisations- und Planungsplattform.
Begründung anhand der Scrum-Prinzipien:- Time-boxing: Das Einhalten der 15-minütigen Timebox fördert die Effizienz und Disziplin im Team.
- Fokus: Durch eine klare Struktur und fokussierte Beiträge wird sichergestellt, dass das Daily Scrum seinen Zweck als kurzes Synchronisations-Meeting erfüllt.
- Transparenz: Klare und vorbereitete Beiträge tragen zur Transparenz über den Fortschritt und die Hindernisse im Sprint bei.
- Selbstorganisation: Eine effiziente Meetingstruktur unterstützt die Selbstorganisation des Teams, indem es klare Kommunikationsregeln gibt.
- Kontinuierliche Verbesserung: Das regelmäßige Überprüfen und Anpassen der Meetingeffizienz ist ein Zeichen für kontinuierliche Verbesserung und Anpassung an die Bedürfnisse des Teams.
Aufgabe 3)
Du bist beauftragt, eine kleine Bibliothek zum Verwalten von Buchdaten zu entwickeln. Verwende den Test-First-Ansatz, um sicherzustellen, dass alle Funktionalitäten korrekt implementiert und überprüft werden. Dabei soll die Bibliothek folgende Funktionen bieten: Hinzufügen eines Buches, Entfernen eines Buches, und das Abrufen aller Bücher. Ein Buch hat die Attribute Titel, Autor und ISBN.
a)
Erster Teil: Schreib zuerst die Tests für das Hinzufügen eines Buches. Was muss der Test überprüfen? Implementiere die Tests in einer Programmiersprache Deiner Wahl. Wie gehst Du vor sicherzustellen, dass der Test fehlschlägt, bevor Du die Funktionalität implementierst?
Lösung:
Um den Test-First-Ansatz zu verwenden, schreiben wir zunächst die Tests für das Hinzufügen eines Buches, bevor wir die eigentliche Funktionalität implementieren. Dies stellt sicher, dass unsere Tests die Funktionalität korrekt überprüfen und unser Code wie erwartet funktioniert.
Was muss der Test überprüfen?
- Der Test muss überprüfen, ob ein neues Buch erfolgreich zur Bibliothek hinzugefügt wurde.
- Er muss sicherstellen, dass das Buch in der Liste der Bücher vorhanden ist, nachdem es hinzugefügt wurde.
- Der Test sollte auch überprüfen, ob die korrekten Attribute (Titel, Autor, ISBN) des Buches gespeichert wurden.
Implementierung der Tests in Python
Wir verwenden die unittest-Bibliothek in Python, um die Tests zu schreiben:
import unittest class TestBibliothek(unittest.TestCase): def setUp(self): self.bibliothek = Bibliothek() def test_buch_hinzufuegen(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) buecher = self.bibliothek.alle_buecher_abrufen() self.assertIn(buch, buecher) if __name__ == '__main__': unittest.main()
Wie gehen wir vor, um sicherzustellen, dass der Test fehlschlägt, bevor wir die Funktionalität implementieren?
- Erstelle zunächst die Testklasse und die Testmethoden wie oben gezeigt.
- Definiere die Bibliothek-Klasse ohne die Methode
buch_hinzufuegen()
. - Führe die Tests aus. Der Test wird fehlschlagen, da die Methode
buch_hinzufuegen()
nicht existiert. - Implementiere nun die Methode
buch_hinzufuegen()
und führe die Tests erneut aus.
Hier ist die leere Definition der Bibliothek-Klasse:
class Bibliothek: def __init__(self): self.buecher = [] def alle_buecher_abrufen(self): return self.buecher
Wenn Du nun die Tests ausführen würdest, würden sie fehlschlagen, da die buch_hinzufuegen()
-Methode fehlt. Dies bestätigt, dass unsere Tests korrekt konzipiert sind. Anschließend können wir die buch_hinzufuegen()
-Methode implementieren und die Tests sollten bestehen. Hier ist die vollständige Implementierung:
class Bibliothek: def __init__(self): self.buecher = [] def buch_hinzufuegen(self, buch): self.buecher.append(buch) def alle_buecher_abrufen(self): return self.buecher if __name__ == '__main__': unittest.main()
Nun sollten die Tests erfolgreich durchlaufen.
b)
Zweiter Teil: Implementiere den minimalen Code, um den Test für das Hinzufügen eines Buches zu bestehen. Schreibe den Code so, dass er nur den Test erfüllt, ohne überflüssige Funktionalität hinzuzufügen.
Lösung:
Um den minimalen Code zu implementieren, der den Test für das Hinzufügen eines Buches besteht, müssen wir sicherstellen, dass unsere Methode buch_hinzufuegen()
funktioniert und unser Test erfolgreich ist. Hier ist der minimal notwendige Code:
Bibliothek-Klasse mit der Methode buch_hinzufuegen()
class Bibliothek: def __init__(self): self.buecher = [] def buch_hinzufuegen(self, buch): self.buecher.append(buch) def alle_buecher_abrufen(self): return self.buecher
Dieser Code stellt sicher, dass wir Bücher zur Bibliothek hinzufügen und diese Liste von Büchern abrufen können.
Vollständiger Testcode:
import unittest class TestBibliothek(unittest.TestCase): def setUp(self): self.bibliothek = Bibliothek() def test_buch_hinzufuegen(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) buecher = self.bibliothek.alle_buecher_abrufen() self.assertIn(buch, buecher) if __name__ == '__main__': unittest.main()
Mit dieser Implementierung sollte der Test erfolgreich durchlaufen, da wir das Hinzufügen und Abrufen eines Buches minimal implementiert haben.
c)
Dritter Teil: Wiederhole den Test-First-Ansatz, um die Funktionalität des Entfernens eines Buches zu implementieren. Was sind die wichtigsten Punkte, die der Test abdecken sollte? Wie überprüfst Du, dass der Test korrekt fehlschlägt?
Lösung:
Der nächste Schritt besteht darin, die Funktionalität des Entfernens eines Buches zu implementieren. Auch hier verwenden wir den Test-First-Ansatz. Zuerst definieren wir die Tests, um sicherzustellen, dass die Implementierung korrekt ist.
Was sind die wichtigsten Punkte, die der Test abdecken sollte?
- Der Test muss überprüfen, dass ein bestimmtes Buch erfolgreich aus der Bibliothek entfernt wird.
- Nach dem Entfernen darf das Buch nicht mehr in der Liste der Bücher vorhanden sein.
- Der Test sollte auch überprüfen, dass das Entfernen eines nicht existierenden Buches keine Fehler verursacht und die Liste der Bücher unverändert bleibt.
Wie überprüfen wir, dass der Test korrekt fehlschlägt?
- Erstelle zunächst die Testklasse und die Testmethoden ohne die Methode
buch_entfernen()
zu implementieren. - Führe die Tests aus. Der Test wird fehlschlagen, da die Methode
buch_entfernen()
nicht existiert. - Implementiere nun die Methode
buch_entfernen()
und führe die Tests erneut aus.
Hier ist der Testcode:
import unittest class TestBibliothek(unittest.TestCase): def setUp(self): self.bibliothek = Bibliothek() def test_buch_hinzufuegen(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) buecher = self.bibliothek.alle_buecher_abrufen() self.assertIn(buch, buecher) def test_buch_entfernen(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) self.bibliothek.buch_entfernen(buch['isbn']) buecher = self.bibliothek.alle_buecher_abrufen() self.assertNotIn(buch, buecher) def test_buch_entfernen_nicht_existierend(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) self.bibliothek.buch_entfernen('nicht_existierende_isbn') buecher = self.bibliothek.alle_buecher_abrufen() self.assertIn(buch, buecher) if __name__ == '__main__': unittest.main()
Hier ist die leere Definition der Bibliothek-Klasse:
class Bibliothek: def __init__(self): self.buecher = [] def buch_hinzufuegen(self, buch): self.buecher.append(buch) def alle_buecher_abrufen(self): return self.buecher
Wenn Du nun die Tests ausführst, werden sie fehlschlagen, weil die Methode buch_entfernen()
nicht existiert. Dies bestätigt, dass die Tests korrekt konzipiert sind.
Implementierung der Methode buch_entfernen()
:
class Bibliothek: def __init__(self): self.buecher = [] def buch_hinzufuegen(self, buch): self.buecher.append(buch) def buch_entfernen(self, isbn): self.buecher = [buch for buch in self.buecher if buch['isbn'] != isbn] def alle_buecher_abrufen(self): return self.buecher if __name__ == '__main__': unittest.main()
Mit dieser Implementierung sollte der Test erfolgreich durchlaufen, da wir die Methode buch_entfernen()
implementiert und die Tests bestanden haben.
d)
Vierter Teil: Erstelle Tests und implementiere die Funktionalität zum Abrufen aller Bücher. Erkläre, wie Du sicherstellst, dass alle vorherigen Funktionalitäten nach einer eventuellen Refaktorisierung weiterhin korrekt funktionieren.
Lösung:
Im letzten Teil erstellen wir Tests für die Funktionalität zum Abrufen aller Bücher und implementieren diese. Anschließend stellen wir sicher, dass alle vorherigen Funktionalitäten weiterhin korrekt funktionieren, insbesondere nach einer eventuellen Refaktorisierung.
Tests für das Abrufen aller Bücher:
- Der Test muss überprüfen, dass alle hinzugefügten Bücher korrekt abgerufen werden.
- Er sollte sicherstellen, dass keine Bücher fehlen oder zusätzliche Bücher in der Liste vorhanden sind.
Hier ist der Testcode:
import unittest class TestBibliothek(unittest.TestCase): def setUp(self): self.bibliothek = Bibliothek() def test_buch_hinzufuegen(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) buecher = self.bibliothek.alle_buecher_abrufen() self.assertIn(buch, buecher) def test_buch_entfernen(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) self.bibliothek.buch_entfernen(buch['isbn']) buecher = self.bibliothek.alle_buecher_abrufen() self.assertNotIn(buch, buecher) def test_buch_entfernen_nicht_existierend(self): buch = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} self.bibliothek.buch_hinzufuegen(buch) self.bibliothek.buch_entfernen('nicht_existierende_isbn') buecher = self.bibliothek.alle_buecher_abrufen() self.assertIn(buch, buecher) def test_alle_buecher_abrufen(self): buch1 = {'titel': '1984', 'autor': 'George Orwell', 'isbn': '1234567890'} buch2 = {'titel': 'Brave New World', 'autor': 'Aldous Huxley', 'isbn': '0987654321'} self.bibliothek.buch_hinzufuegen(buch1) self.bibliothek.buch_hinzufuegen(buch2) buecher = self.bibliothek.alle_buecher_abrufen() self.assertEqual(buecher, [buch1, buch2]) if __name__ == '__main__': unittest.main()
Hier ist die Bibliothek-Klasse mit allen notwendigen Methoden:
class Bibliothek: def __init__(self): self.buecher = [] def buch_hinzufuegen(self, buch): self.buecher.append(buch) def buch_entfernen(self, isbn): self.buecher = [buch for buch in self.buecher if buch['isbn'] != isbn] def alle_buecher_abrufen(self): return self.buecher if __name__ == '__main__': unittest.main()
Jetzt verfügbar:
buch_hinzufuegen()
, um Bücher hinzuzufügen buch_entfernen()
, um Bücher zu entfernen alle_buecher_abrufen()
, um alle Bücher abzurufen
Um sicherzustellen, dass alle vorherigen Funktionalitäten auch nach einer eventuellen Refaktorisierung weiterhin korrekt funktionieren, können wir Folgendes tun:
- Stelle sicher, dass umfassende Tests vorhanden sind, die alle Funktionen der Bibliothek abdecken. Diese Tests sollten vor und nach jeder Refaktorisierung ausgeführt werden, um sicherzustellen, dass keine Funktionalität gebrochen wurde.
- Führe kontinuierlich Integrationssysteme (CI) ein, um sicherzustellen, dass bei jeder Änderung im Code alle Tests automatisch ausgeführt werden. Dies hilft, unerwünschte Nebeneffekte frühzeitig zu erkennen.
- Verwende Versionskontrollsysteme (wie Git), um Änderungen nachzuverfolgen und bei Bedarf alte funktionierende Zustände wiederherzustellen.
Nach einer Refaktorisierung ist es wichtig, alle Tests durchzuführen und sicherzustellen, dass sie alle bestehen. Auf diese Weise stellen wir sicher, dass unsere Bibliothek weiterhin wie erwartet funktioniert.
Aufgabe 4)
Kontext: Du bist Software-Ingenieur bei einem Unternehmen, das eine umfassende Bibliothekssoftware entwickelt. Diese Software verwendet verschiedene Design-Patterns, um die Struktur und Modularität zu verbessern. Drei der wichtigsten Design-Patterns sind Adapter, Decorator und Composite. Der Adapter wird verwendet, um die Schnittstelle eines vorhandenen Systems an neue Anforderungen anzupassen. Der Decorator wird verwendet, um Objekten zur Laufzeit zusätzliche Verantwortlichkeiten hinzuzufügen, ohne die ursprüngliche Klassenstruktur zu ändern. Das Composite-Pattern erlaubt es, Objekte zu Baumstrukturen zu kombinieren, um hierarchische, Teil-Ganzes Beziehungen darzustellen.
- Adapter: Konvertiert die Schnittstelle einer Klasse in eine andere, die der Kunde erwartet. Nutzt Zusammensetzung bzw. Vererbung.
- Decorator: Fügt dynamisch Verantwortung hinzu, ohne die Klasse zu ändern. Nutzt Rekursion bzw. Aggregation.
- Composite: Komposition von Objekten zu Baumstrukturen zur Darstellung von Teil-Ganzes Hierarchien. Behandelt individuelle Objekte und Zusammensetzungen einheitlich.
a)
Angenommen, Du implementierst die Bibliothekssoftware und hast eine Klassenbibliothek, die über eine vorhandene Schnittstelle benutzt wird. Die Schnittstelle heißt OldLibraryInterface
, die Methode void fetchBook(String title)
enthält. Die neue Schnittstelle, die Du anpassen musst, heißt NewLibraryInterface
und enthält eine Methode Book getBookByTitle(String title)
. Du musst einen Adapter implementieren, der die Produktionseinheit OldLibraryInterface
mit der neuen NewLibraryInterface
kompatibel macht. Aufgabe: Implementiere den Adapter, damit die Methoden der beiden Schnittstellen kompatibel sind. Verwende Zusammensetzung in Deiner Lösung.
Lösung:
Um einen Adapter zu implementieren, der die Methoden der beiden Schnittstellen kompatibel macht, müssen wir eine Klasse erstellen, die sowohl die neue Schnittstelle NewLibraryInterface
implementiert als auch eine Instanz der alten Schnittstelle OldLibraryInterface
enthält. Der Adapter wird die Methode der neuen Schnittstelle aufrufen und dabei die Methode der alten Schnittstelle nutzen.
OldLibraryInterface
:NewLibraryInterface
:Book
:LibraryAdapter
(der Adapter):
Hier ist der Code:
interface OldLibraryInterface { void fetchBook(String title); } class Book { private String title; public Book(String title) { this.title = title; } public String getTitle() { return title; } } interface NewLibraryInterface { Book getBookByTitle(String title); } class LibraryAdapter implements NewLibraryInterface { private OldLibraryInterface oldLibrary; public LibraryAdapter(OldLibraryInterface oldLibrary) { this.oldLibrary = oldLibrary; } @Override public Book getBookByTitle(String title) { oldLibrary.fetchBook(title); return new Book(title); } }
In diesem Code:
- Die
OldLibraryInterface
definiert die Methode fetchBook(String title)
. - Die
NewLibraryInterface
definiert die Methode Book getBookByTitle(String title)
. - Die Klasse
Book
repräsentiert ein Buchobjekt mit einem Titel. - Der
LibraryAdapter
implementiert die NewLibraryInterface
und enthält eine Instanz der OldLibraryInterface
. In der Methode getBookByTitle(String title)
wird die Methode fetchBook
der alten Schnittstelle aufgerufen und ein neues Book
-Objekt zurückgegeben.