Archiv der Kategorie: Projects

Aus der täglichen Retrospektive #3

2014-07-31 12.08.30Unsere Retrospektiven gehen weiter und die Teammitglieder entwickeln meiner Einschätzung nach ein immer besseres Gespür für Impediments. Das ist für mich als Scrum Master besonders erfreulich. Ich habe mir wieder einmal etwas herausgegriffen, von dem ich denke, dass es dem Leser helfen kann.

Es hat sich herausgestellt, dass ein wichtiger Prozess bisher nicht explizit gemacht wurde: Der Go Live.

Das führte unter anderem dazu, dass das Update erst mit mehreren Tagen Verzögerung und teilweise sogar mit Änderungen aus dem neuen Sprint durchgeführt wurde. Darüber hinaus war nicht gewährleistet, dass mit der Codebasis auch die Datenbasis aktualisiert wurde. Deshalb haben wir uns zusammengesetzt, den Prozess besprochen und diesen im Wiki festgehalten.

image

Darin wird klar zw. Daten- und Code- Go Live unterschieden. Damit einhergehen auch unterschiedliche Termine und Verantwortliche. Interessanterweise wurde in der Diskussion deutlich, dass der Prozess noch detailliert visualisiert und kommuniziert werden muss. In Kanban ist das fest als Regel aufgelistet:

                  Visualize Your Workflow

Einen Einblick in unser Wiki und den Prozess werde ich demnächst in Form eines Screencasts auf YouTube und auf meinen Blog stellen. Als Abonnent kriegt ihr das automatisch mit.

Mit welchem Tool verfasst bzw. persistiert ihr eure Regeln? Wer macht das? Wird das kontinuierlich erweitert oder war das nicht mehr notwendig? Ist es euch auch schon öfters passiert, dass ihr feststellen musstet, dass Regeln nicht explizit gemacht und dadurch Probleme verursacht wurden? Gerne könnt ihr mir einen Kommentar schreiben.

Advertisements

Testen von Datenbankabfragen – Es geht auch einfach

In diesem Screencast zeige ich wie selbst Abfragen mit mehreren Bedingungen und Sortierungen einfach getestet werden können. Unter der Haube kommt das Entity Framework zum Einsatz. Davon kriegt man dank Dependency Injection aber nichts mit.

Die erwähnte Webcast Serie, sowie weiteren Beispielcode und das Projekt auf Github gibt es hier. Feedback oder Fragen nehme ich gerne entgegen. Wenn ihr wollt, dass ich die Serie fortsetze, sprecht mich über einen Kanal eurer Wahl an.

Flexibles Bootstrapping durch Composition

In diesem Webcast zeige ich unseren Kompositions-Ansatz für das Bootstrapping. Die Interfaces sind schmal, die Implementierungen überschaubar, das Ganze ist flexibel und lässt sich gut testen.

Hier der gezeigte Code:

   1: public class BootstrapperContext

   2: {

   3:     public BootstrapperContext()

   4:     {

   5:         AppConfig = new ApplicationConfig();

   6:         Container = new WindsorContainer();

   7:     }

   8:  

   9:     public IWindsorContainer Container { get; private set; }

  10:     public ApplicationConfig AppConfig { get; private set; }

  11: }

   1: public interface IAmABootstrapperAction

   2: {

   3:     void Execute(BootstrapperContext context);

   4: }

 

   1: public interface IAmABootstrapperComposition

   2: {

   3:     IEnumerable<IAmABootstrapperAction> Actions { get; }

   4: }

 

   1: public class BootstrapperExecutor

   2: {

   3:     public static void StartupApplication(IAmABootstrapperComposition bootstrapperComposition)

   4:     {

   5:         var exceptionMessage = "Beim Starten der Anwendung ist ein Fehler aufgetreten. Bitte den Support kontaktieren.\n\n";

   6:  

   7:         if (bootstrapperComposition.Actions == null || !bootstrapperComposition.Actions.Any())

   8:         {

   9:             throw new BootstrapperException(exceptionMessage, new ArgumentOutOfRangeException("Auf dem Bootstrapper waren keine Actions definiert"));

  10:         }

  11:  

  12:         var context = new BootstrapperContext();

  13:  

  14:         var time = TimedAction.Run(() =>

  15:                                    {

  16:                                        foreach (var action in bootstrapperComposition.Actions)

  17:                                        {

  18:                                            var actionName = action.GetType().Name;

  19:                                            SiAuto.Main.LogMessage(string.Format("{0} gestartet", actionName));

  20:  

  21:                                            var timeTaken = TimedAction.Run(() => { ExecuteAction(action, context, exceptionMessage); });

  22:  

  23:                                            SiAuto.Main.LogMessage(string.Format("{0} in {1} erfolgreich durchgeführt",

  24:                                                                                 actionName,

  25:                                                                                 timeTaken.Format())

  26:                                                );

  27:                                        }

  28:                                    });

  29:     }

  30:  

  31:     private static void ExecuteAction(IAmABootstrapperAction action, BootstrapperContext context, string exceptionMessage)

  32:     {

  33:         try

  34:         {

  35:             action.Execute(context);

  36:         }

  37:         catch (Exception ex)

  38:         {

  39:             SiAuto.Main.LogException(string.Format("Fehler beim Bootstrapping: {0}", ex.GetFullExceptionMessage()), ex);

  40:             throw new BootstrapperException(string.Format("{0}{1}", exceptionMessage, ex.GetFullExceptionMessage()), ex);

  41:         }

  42:     }

  43: }

 

Welchen Ansatz verfolgt ihr?

Fragen oder Feedback könnt ihr mir gerne als Kommentar hinterlassen.

Aus der täglichen Retrospektive #2

Aufgrund von unterschiedlichen Browserversionen und Extensions oder wegen gecachten Inhalten kam es bei der Abnahme von User Stories bei uns häufig zu Problemen. Beispielsweise wurden alte Styles oder falsche Bilder geladen. Eine weitere Störquelle waren Fehler in Fremdsprachen oder in besonderen Daten-Konstellationen (sehr lange Breadcrumb, tiefe Hierarchien).

Daraus zogen wir zwei Konsequenzen:

  • Alle Tester und der PO bekamen die PortableApps.com Plattform, in welcher Firefox und Chrome mit einheitlichen Extensions und Konfigurationen installiert sind. Zwei Konfigurationen bewirken unter anderem, dass nichts gecacht wird und nach dem Schließen des Browsers alle Daten entfernt werden. Außerdem ist das NoTracking-Kennzeichen aktiviert. Per copy & paste kann ein weiterer Tester schnell “die Testumgebung” erhalten.
  • Darüber hinaus legten wir für besagte Konstellationen Lesezeichen in den Leisten an und dokumentierten den Soll-Stand. Im Unternehmenswiki ist der formale Abnahmeablauf festgehalten.

Certified Scrum Product Owner Schulung

Im Zeitraum vom 18. bis 20. Juni nach ich an einer Certified Scrum Product Owner Schulung bei den Trainern Dr. Jürgen Hoffmann und Peter Beck teil. Den Kurs hatte über die wibas GmbH gebucht. Als Veranstaltungsort wählte ich die NTT DATA Ettlingen Academy.

 

Location

Das Angebot an kostenfreien Parkplätzen war optimal, sodass unnötig langes Suchen am frühen Morgen ausblieb. Die verfügbaren Räume waren schön hell mit genügend Platz für die knapp 20 Kursteilnehmer. Leider mangelte es an einer Klimaanlage, was mir bei tropischen 35°C besonders negativ auffiel. In den kleinen Pausen konnte man sich mit frischem Obst und Gebäck stärken; zur Mittagszeit gab es Essen in der Kantine, welche einen schönen Balkon mit Teich zur geistigen Erholung bot.  Bedenkt man die kurze Strecke für Karlsruher war es geradezu ideal..

 

Die Trainer

Eine kurze Recherche ergab, dass es sich um zwei etablierte, langjährige Trainer handelte, die in der Community recht arriviert sind. Deshalb bekamen die Teilnehmer das, was sie vermutlich am meisten hören wollten: Praxisberichte. Die Frage “Wie habt ihr das bei euch gemacht” kam so ziemlich jedem über die Lippen. Hierfür gibt es von mir “volle Punktzahl”. Die Aussagen waren klar, die didaktischen Methoden variierten in einem gesunden Maße und durch die Backlogs der einzelnen Gruppen konnte jeder in die Themenfokussierung eingreifen. Negativ fielen mir folgende Dinge auf:

Aufgrund meiner T-Shirt Wahl, welche meine Entwicklerwurzeln offen zeigte, wurde wohl der Eindruck bei den Trainern perpetuiert, dass man mich auch als solchen “coachen” sollte. Zumindest empfand ich dies teilweise so bei Gesprächen. Als ich mich zur Aussage, dass es gängige Praxis sei die Entwickler die Product Backlog Items schreiben zu lassen, kritisch äußerte und darüber diskutieren wollte, wurde dies einfach abgetan. Inzwischen habe ich vermehrt in der Community, unter anderem am .NET Open Space, das Thema aufgeworfen. Mein Resümee: Gängige Praxis sieht anders aus. Allerdings möchte ich fairerweise an dieser Stelle anmerken, dass selbst in der Fachliteratur (u.a. im Werk von Boris Gloger) ähnliche Aussagen stehen. Mir geht es jedoch darum, dass ich mich eine Schublade gesteckt sah, sodass eine konstruktive Diskussion in diesem konkreten Fall nicht zu Stande kam.

Ein weiterer Punkt waren ein paar wenige (!) fragwürdige Aussagen. Eine davon implizierte, dass die Definition of Done immanent unvollständig sein müsse, wenn das Produkt keine 100%ige Evolvierbarkeit aufweise.

Rein subjektiv gefielen mir die ausgewählten Planspiele nicht. Das lag vielleicht daran, dass es sich nicht um Geschäftsprozesse handelte. Vielleicht auch an den konkret gewählten Szenarien, wie das Bauen eines Lego-Parks für Kinder. Bei der zu Beginn gewählten Teambildungsmaßnahme mussten Seesterne gelegt werden. Eine gängige Praxis, wie ich von Bekannten mit selbiger Zertifizierung erfahren habe. Allerdings wurde es so umgesetzt, dass die Teams bereits blind in den Raum kommen, die Materialien finden und Position begeben mussten. Wenn aber die erste Gruppe direkt am Eingang stehen bleibt, dann ist das Resultat, dass mehrere Teams vorerst einfach rumstehen müssen, bis der erste Durchgang beendet wurde. Der Lerneffekt ist für mich genauso gegeben, wenn die Teams bereits im Raum mit den Materialien platziert wären. Sehr positiv wiederum kann ich über das Fallbeispiel zur Schätzung in Story Points im warmen Garten berichten. Die Teams sollten sich hierbei auf die Komplexität zum Entfernen von Gegenständen für den bevorstehenden Besuch von Obama (der an diesem Tag tatsächlich in Berlin war) einigen. Das fing bei Aschenbechern an, ging über Tische und hörte bei Autos auf.

 

Zur Schulung

Als einziges Angebot, welches ich in der näheren Umgebung finden konnte, ging dieser Kurs über 3 Tage. Die üblichen Schulungen sind in der Regel auf 2 Tage ausgelegt. Planspiele sind aber relativ zeitaufwendig und der Wunsch nach Erfahrungsberichten der Trainer führt zu starken Verzögerungen. Deshalb kann ich nur jedem empfehlen 3-tägige Seminare zu bevorzugen.

Die Kosten lagen auf gängigem Marktniveau. Bei entsprechendem Vorlauf wird Frühbucherrabatt gewährt.

Als Schulungszeiten waren 10-18 Uhr am ersten Tag und 9-17 Uhr an den darauffolgenden Tagen geplant. Diese wurden recht gut eingehalten, was nach meiner Erfahrung bei Schulungen sonst nur selten funktioniert.

Bereits vor Kursbeginn erhielt ich eine E-Mail mit Vorschlägen zur Vorbereitung. Während der Schulung wurde ein eigens dafür eingerichteter DropBox Ordner kontinuierlich aktualisiert. Darin landeten neben kostenlosen Ebooks die Fotoaufnahmen der erstellten Materialien (quasi eine Timeline der Flip Chart Blätter), sowie diverse Vorlagen und weiterführende Materialien. Ein dediziertes Dokument mit den erarbeiteten Inhalten gab es nicht.

Das klar kommunizierte Ziel war es jeden in die Lage zu versetzen, entscheiden zu können, ob Scrum mit seinen Prinzipien und Werten für das eigene Unternehmen geeignet ist und sich etablieren lässt. Der Fokus lag natürlich auf der Rolle des Product Owners, ist aber auch nach Aussagen der Trainer mit der des Scrum Master zu 80-90% deckungsgleich. Die restlichen 10-20% wurden dann aber speziell hervorgehoben bzw. ggf. auch intensiver erörtert.

Wer viele Planspiele, viel Eigenarbeit, viel Stehen und wenig PowerPoint Slides bevorzugt, kommt definitiv auf seine Kosten. Mir persönlich hätte ein wenig mehr “klassische” Schulung mit theorielastigerem Vorgehen besser zugesagt. Nach meinem Gefühl kommen dadurch mehr Fragen auf, v.a. abseits der der trivialen Oberflächenthemen. Das bestätigte sich für mich im Nachhinein beim Lesen der oben genannten Lektüre von Boris Gloger wieder. (Wenn Folien übrigens schlecht sind, dann weil der Autor Fehler gemacht hat, nicht weil diese per sé schlecht sein müssen, siehe meine Buchempfehlung.) Allerdings verhält es sich so, dass ich nach einer theorielastigen Sitzung der Einzige war,  der diese positiv bewertete. Deswegen gilt: Die Schulung muss nach der eigenen Präferenz ausgewählt werden.

 

Fazit

Auf einer Skala von 1 bis 10 gibt es von mir eine 7 mit dem klaren Hinweis, dass ich die Schulung für Menschen mit anderem Lernmodus durchaus bei 9-10 sehen würde. Darüber hinaus halte ich eine derartige Schulung für ganze Scrum Teams durchaus für förderlich, speziell wenn sich Fallbeispiele und Themen mitbestimmen lassen. Es bleibt ein positiver Rückblick mit kleinen Makeln und dem Ratschlag, dass auch das Seminar das Lesen entsprechender Fachliteratur nicht ersetzt (vice versa!).

Entity Framework Webcasts – Testing

Im siebten Teil meiner Entity Framework Webcast Serie widme ich mich dem Thema Testing. Zunächst nehmen wir uns die fertigen Queries vor, welche sich auch gut ohne Datenbank testen lassen. Schließlich will nicht jeder zum Testen einer Abfrage immer eine entsprechende Datenbank hochfahren und die Daten dafür erzeugen.

Danach stellen wir in einem Mini-Business-Pseudo-Layer ein Szenario nach, welches uns dedizierte Daten für die weitere Verarbeitung liefert, sodass das Testen der Geschäftslogik ebenfalls ohne Datenbank erfolgen kann.

Als BDD Framework für die Unit Tests verwende ich Machine.Specifications und als Mocking Framework kommt FakeItEasy zum Einsatz.

Hier noch die zwei Code Beispiele:

 

Beispiel 1: Testen der Query

   1: [Subject(typeof(GetAddressByCity))]

   2: public class When_addresses_contains_exactly_one_matching_entry

   3: {

   4:     static GetAddressByCity Sut;

   5:     static List<Address> Addresses;

   6:     static Address Actual;

   7:     static Address TestCity;

   8:  

   9:     Establish context = () =>

  10:     {

  11:         TestCity = new Address {City = "test city"};

  12:         Addresses = new List<Address>

  13:         {

  14:             new Address {City = "Karlsruhe"},

  15:             TestCity

  16:         };

  17:         Sut = new GetAddressByCity("test city");

  18:     };

  19:  

  20:     Because of = () =>

  21:     {

  22:         Actual = Sut.Execute(Addresses.AsQueryable());

  23:     };

  24:  

  25:     It should_return_exactly_this_address = () => 

  26:             Actual.ShouldEqual(TestCity);

  27: }

 

Beispiel 2: Testen von Geschäftslogik

   1: class EmployeeBusinessLogicSpecs

   2: {

   3:     [Subject(typeof(EmployeeBusinessLogic))]

   4:     class When_hire_date_is_unknown

   5:     {

   6:         static IUnitOfWork Uow;

   7:         static EmployeeBusinessLogic Sut;

   8:         static Employee DummyEmployee;

   9:  

  10:         Establish context = () =>

  11:         {

  12:             DummyEmployee = new Employee {EmployeeID = 1, 

  13:                     FirstName = "Uli", LastName = "Armbruster"};

  14:  

  15:             Uow = A.Fake<IUnitOfWork>();

  16:  

  17:             A

  18:                 .CallTo(() => Uow.ExecuteQuery(

  19:                     A<GetEmployeeById>

  20:                     .That

  21:                     .Matches(q => q.EmployeeId == DummyEmployee.EmployeeID)

  22:                                   ))

  23:                 .Returns(DummyEmployee);

  24:  

  25:             Sut = new EmployeeBusinessLogic(Uow);

  26:         };

  27:  

  28:         Because of = () => Sut.EnsureValidHireDate(DummyEmployee.EmployeeID);

  29:  

  30:         It should_update_it = () => DummyEmployee.HireDate.ShouldNotBeNull();

  31:  

  32:         It should_save_the_changed_hire_date = () => A

  33:             .CallTo(() => Uow.Commit())

  34:             .MustHaveHappened(Repeated.Exactly.Once);

  35:     }

  36: }

 

 

Weitere Quellen:

Entity Framework Webcasts – Stored Procedures und SQL Queries

Im sechsten Teil meiner Entity Framework Webcast Serie zeige ich, wie die IUnitOfWork erweitert werden muss, um SQL Abfragen und Befehle auszuführen. Im Fokus stehen Value Functions und Stored Procedures.

 

 

Dafür stellt das Entity Framework 2 Schnittstellen bereit:

Ein wichtiger Hinweis an dieser Stelle, welcher eingehender in meinem Webcast erwähnt wird: Zum Ausführen einer SQL Query existieren 2 Implementierungen:

Der wesentliche Unterschied besteht darin, dass im ersten Fall die zurückgegebenen Entitäten nicht getrackt werden, im zweiten hingegen schon. Das ist leicht nachvollziehbar, so muss bei DbContext.Database.SqlQuery ein generischer Parameter, der den Rückgabewert darstellt, übergeben werden. Im Falle von DbSet.SqlQuery ist dies nicht nötig, schließlich befindet man sich zum Zeitpunkt der Abfrage auf einem konkreten Set an Entitäten, die über den Context aufgelöst wurden.

Meine vorgestellte Lösung geht den gleichen Weg wie die in Teil 3 beschriebenen Queries, d.h. es werden quasi SQL Query und Command Repositories angelegt, um einer Streuung innerhalb der Anwendungslandschaft entgegenzuwirken.

Schließlich noch zwei Sicherheitshinweise:

  • Die SQL Befehle werden mit den Berechtigungen des Contexts bzw. des darunterliegenden Sicherheitstoken ausgeführt
  • Beim Erstellen des SQL Strings sollte aus Sicherheitsgründen gegenüber Injections mit String.Format() oder alternativ mit SqlParameters gearbeitet werden.

 

I wrote this blog post as an answer of a question from the Netherlands, so I want to complete this article with a quote:

“When you execute a SqlQuery from Database, the results are never tracked by the context, even if the query returns types that are in the model an known by the context. If you do not want the results to be changed-tracked, use DbContext.Databae.SqlQuery.

Results of a DbSet.SqlQuery will be tracked by the context. Ensuring that results are change-tracked is the primary reason you would choose to use DbSet.SqlQuery over Database.SqlQuery.” (Programming Entity Framework, Page 226)

 

Weitere Quellen:

%d Bloggern gefällt das: