Anno 2015 har även de flesta större systemutvecklade företag tagit sig så pass långt i mognadsnivå att någon form av agilmetodik tillämpas. Den vanligast förekommande metodiken idag är utan tvekan Scrum.  Låt oss för enkelhetensskull anta att man valt att applicera en klassisk tvånivåsmodell för ärendehantering: Product backlog item (PBI) och uppgift.  En PBI kan ha noll till många uppgifter; oftast ett par stycken. Denna modell är inte tänkt att vara komplett utan främst vara en grund för resonemanget kring Definition of Done (DoD).

Definition av Definition of Done (DoD)

Definition of Done är något som beskriver när en PBI och dess ingående uppgifter anses klara av Scrum -teamet.  Eftersom Produktägaren ingår i teamet så känner även denne till DoD och förstår dess innebörd.

DoDens konsumenter

Den är till för att skapa transparens inom Scrum -teamet (men kan även användas av produktägaren för att spegla status mot stakeholders). Det är därför centralt att alla i teamet förstår vad DoD innebär för de olika typer av aktivteter som teamet åtar sig. Jobbar man i ett företag där flera Scrum -team jobbar i en och samma produkt så behöver det finnas en gemensam DoD för att undvika tråkiga överraskningar i kritiska lägen. Reflektera själv över hur det kan sluta om TeamX har sin DoD och teamY har en helt annant och deras kod sedan ska sammanstråla. Kommer det smärtfritt om deras ändringar överlappar?

DoD behövs

Studier visar att en överväldigande majoritet av alla människor uppskattar att känna att de gör konkret nytta och de tar sig framåt med åtagna uppgifter. Utan en DoD hur ska man någonsin veta när man är klar? Hur ska någon annan veta? Hur ska hen få en känsla för hur hen ligger till?
Således är DoD inte bara viktigt för teamet utan även för individen.

En DoD per typ

Det är viktigt att särskilja mellan DoD för en PBI och dess uppgifter. En uppgift är en väldefinierad avgränsad del av det som behöver göras för att förverkliga PBIn.  Det är av yttersta vikt att alla i teamet förstår innebörden av varje uppgift och dess DoD. Har man inte förstått vad som ska göras eller när det anses klart blir det svårt att uppskatta insatsen som krävs.
Det samma gäller givetvis PBIn. Om den inte går att göra väldefinierad under en grooming eller Sprint-planering är PBIn sannolikt inte mogen för att ingå i en Sprint ännu.

Vår DoD håller inte

Givetvis får man återvända till sin DoD och justera den efterhand. Det är till och med rekommenderat. Se dock till att inte justera den under pågående Sprint! Det kan skapa total förvirring i ledet. Insikterna teamet får efter att ha arbetat tillsammans är guld värda. Det är en av anledningarna till att ”statiska” team är så högt värderad i den agila världen.  
Se således inte er DoD som slutgiltig utan ett pågående arbete.
Kom ihåg:
Det enda som är konstant i världen är förändring!

Skrev i ett tidigare inlägg om mockning på grund-nivå. Tänkte i detta inlägg beskriva partiell mockning; hur vi kan mocka en del av ett objekt, istället för ett helt objekt. Vi har en klass UserServiceImpl enligt nedan:

public class UserServiceImpl implements UserService {

   private UserDao userDao;

   public String createUser(User user) {
      …
      String code = generateUniqueCode();
      user.setCode(code);
      userDao.save(user);
      return code;
   }

   protected String generateUniqueCode () {
      …
   }

   public void setUserDao(UserDao userDao) {
      this.userDao = userDao;
   }
}

För att skriva ett test som mockar anropet till UserDao kan vi t ex göra enligt nedan (använder även här Mockito som mockningsramverk):

public class UserServiceCreateUserTest {
   @Test
   public void generatedCodeShouldBeReturnedWhenUserSaved() {
      UserServiceImpl userService = new UserServiceImpl();
      UserDao userDao = Mockito.mock(UserDao.class);
      userService.setUserDao(userDao);
      String result = userService.createUser(new UserImpl());
      //Utför något test
   }
}

I detta fallet har vi ett "riktig" objekt som är userService (genom new UserServiceImpl()) och ett mockat objekt som är userDao (genom mock(UserDao.class)). Från metoden createUser(User user) i UserServiceImpl görs anrop till en metod i UserServiceImpl (riktiga objektet) och en metod i UserDao (mockade objektet). Ibland så uppstår behov där det skulle förenkla att kunna mocka vissa metoder i ett ej mockad objekt, dvs att det riktiga objektet ska vara en sorts hybrid mellan ett riktigt objekt och ett mockat objekt; att vissa metoder i det ska vara "riktiga" medan andra är mockade.

Vi kan göra UserServiceImpl till en sådan hybrid genom tillämpa Spy i Mockito enligt nedan.

public class UserServiceCreateUserTest {
   @Test
   public void generatedCodeShouldBeReturnedWhenUserSaved() {
      UserServiceImpl userService = Mockito.spy(new UserServiceImpl());
      UserDao userDao = Mockito.mock(UserDao.class);
      userService.setUserDao(userDao);
      Mockito.doReturn("my mocked code").when(userService).generateUniqueCode();
      String result = userService.createUser(new UserImpl());
      //Utför något test
   }
}

I senaste exemplet så har vi ändra initialisering av userService genom att skicka in 'new UserServiceImpl()' som argument till spy-anrop. Detta betyder att userService kommer att bete sig precis likadant som i exemplet ovan, dvs vara ett riktigt objekt. När vi väljer att mocka ett av dess metoder så gör vi det genom doReturn enligt ovan. Detta betyder nu att alla metoder i userService är riktiga förutom generateUniqueCode() som är mockad och kommer i senaste testet att returnera "my mocked code".

Tänkte i detta inlägg fortsätt med TDD och visa hur man enkelt mockar bort beroenden för att på så sätt isolera det objekt som man vill testa.

Nedan har vi klassen CustomerManagerImpl som i metoden getCustomerFullName() gör ett anrop till findCustomer(int customerId) i CustomerLookupClient som den har ett beroende till. Sedan är den lite dummy-kod som konkatenerar ihop för- och efternamn för att sedan returnera dessa.

public class CustomerManagerImpl implements CustomerManager {

   private CustomerLookupClient customerLookupClient;

   public String getCustomerFullName(int customerId) {
      Customer customer = customerLookupClient.findCustomer(customerId);
      String fullName = customer.getFirstName() + " " + customer.getLastName();
      return fullName;
   }

   public void setCustomerLookupClient (CustomerLookupClient customerLookupClient) {
      this.customerLookupClient = customerLookupClient;
   }
}

Skulle vi skriva ett test på detta enligt nedan så skulle det kastas ett NullPointerException eftersom customerLookupClient ej har initialiserats. Dessutom har vi inget förväntat resultat att testa mot, så länge vi kör ett rent unit-test utan någon container etc (vilket vi helst vill).

public class CustomerManagerGetCustomerFullNameTest {
   @Test
   public void firstAndLastNameShouldBeConcatinatedFromFoundCustomer() {
      CustomerManager customerManager = new CustomerManagerImpl();
      String result = customerManager.getCustomerFullName(1);
      //Utföra ett test på ???
   }
}

Vad vid får göra i detta fall är att mocka bort beroendet till customerLookupClient och på så sätt isolera testandet till endast den logik som sker i CustomerManagerImpl. Detta gör vi med i detta fall manuell mockning (kommer även titta på användning av ramverk längre ner).

public class CustomerLookupClientMock implements CustomerLookupClient {
   public Customer findCustomer(int id) {
      Customer mock = new CustomerImpl("John", "Smith");
      return mock;
   }
}

Här är en (av flera) bra motivering till att jobba med interface. Vi skapar ett objekt som returnerar ett för oss på förhand känt värde. Nedan sätter vi customerLookupClient-beroendet till det mockade objektet. Här också en bra motivering till varför man alltid till initiera beroenden via setter-metoder eller konstruktor, för att möjliggöra testbarhet. Notera också att vi nu på båda sidor av =-tecknet behöver använda CustomerManagerImpl för att komma ut setter-metoden.

public class CustomerManagerGetCustomerFullNameTest {
   @Test
   public void firstAndLastNameShouldBeConcatinatedFromFoundCustomer() {
      CustomerManagerImpl customerManager = new CustomerManagerImpl();
      customerManager.setCustomerLookupClient(new CustomerLookupClientMock());
      String result = customerManager.getCustomerFullName(1);
      assertEquals(result, "John Smith");
   }
}

Vi kan även utöka mockade objektet till att ta in för- och efternamn i sin konstruktor för att exempelvis gör den användbar för flera fall. Att mocka manuellt kan bli lite besvärligt när projekten växer då de ska underhålla och det blir många rader för att sätta upp mockningsobjekt som används av flera tester. Därför ska vi till sista titta på hur vi slipper manuell mockning genom att tillämpa ramverket Mockito för detta. Här nedan är föregående test omskrivet till att använda Mockito för mockningen, vi slipper alltså skapa separata mockningsobjekt.

public class CustomerManagerGetCustomerFullNameTest {
   @Test
   public void firstAndLastNameShouldBeConcatinatedFromFoundCustomer() {
      CustomerManagerImpl customerManager = new CustomerManagerImpl();
      CustomerLookupClient customerLookupClient = Mockito.mock(CustomerLookupClient.class);
      Mockito.doReturn(new Customer("John", "Smith")).when(customerLookupClient).findCustomer(1);
      customerManager.setCustomerLookupClient(customerLookupClient);
      String result = customerManager.getCustomerFullName(1);
      assertEquals(result, "John Smith");
   }
}

 

En enkel strategi när man utvecklar itsystem är att man använder sig av release brancher. Det fungerar enkelt så att alla nyutvecklare lägger in sin kod i trunk/main-spåret och när man anser sig vara färdig för en release så skapar man en branch för det. Behöver man sedan göra buggfixar så gör man det i release branchen för den givna versionen. På så vis kan man hålla flera versioner levandes parallellt och all nyutveckling trycks alltid till samma ställe

Enkelt är väl bra? Nja... inte alltid. Vad händer om man vill utveckla parallellt där man inte vet vilken release man skall gå med i? En viss funktionalitet är kanske beroende av ett stödsystems nästkommande version. Om stödsystemets release blir för försenad så sitter man antagligen en dålig situation. Ett alternativ man har är att backa all den funktionaliten som var beroende av stödsystemet. Detta kan vara väldigt svårt, speciellt om olika projekt har varit inne i samma moduler. Ett annat alternativ man har är att vänta med hela releasen till stödsystemet är klart. Detta kan kanske vara en vettig lösning om man har få stödsystem och man har förtroende för att de kommer att leverera. När man sitter i dessa sitationer så passar sig det bättre att använda sig av features brancher. Detta pattern fungerar som så att det är trunk/main som man releasar ifrån och nyutveckling sker i brancher. När man är klar med en feature/funktion/buggrättning så mergar man in det till main och därifrån gör man en release. Detta gör arbetat enklare om man är osäker på när man kan gå ut med en viss funktion. Samtidigt har man i grund och botten bara stöd för vidareutveckling av en version, något som iochförsig duger gott till de flesta.


Om man vill röra till det lite extra så kan man faktiskt blanda dessa patterns så man får stöd för både release och feature brancher. Ett bra systemstöd är då väldigt viktigt då det lätt kan bli rörigt.

- Trött på den där fula bannern på din favoritsite?
- Saknas en enkel nyttofunktion som skulle göra just din upplevelse perfekt?
- Har ni grundläggande kunskaper i HTML, JavaScript samt CSS?
- Känns Firefox som en OK webbläsare?

Är svaret på ovan frågor ja? Då finns lösningen nära tillhands!

Genom att installera ett plugin kan ni ta kontrollen över de siter ni besöker. Trött på den blaffiga loggan som alltid är i vägen? Inga problem! Plocka snabbt och enkelt bort DOM-elementet med lite JavaScript magi.

Pluginet heter Greasemonkey och det tillåter dig injicera godtycklig JavaScript (samt CSS) på vilken site som helst. Pluginet har funnits tillgängligt i flera år och är vida spritt.

Låt oss göra det hela konkret med ett grundläggande exempel.

Uppgiften: Plocka bort störande reklam på www.aftonbladet.se

  1. Se till att Greasemonkey är installerad
  2. Klicka på pilen vid Greasemonkey-loggan och välj ”Nytt användarskript…”
  3. Ange namn: reklam-begone
  4. Ange namnrymd: www.exertusit.se
  5. Ange beskrivning: Enkelt uppstädningsexempel
  6. Ange inkluderar: http://www.aftonbladet.se/*
  7. Klicka på ’OK’
  8. Ni får nu upp en enkel redigeringsvy. Klistra in nedan enkla JavaScript-kod längst ner (på raden efter // ===/UserScript==):
    var adElement = document.getElementById("abPanoramaTop");
    adElement.parentNode.removeChild(adElement);
    adElement = document.getElementById("abOutsider");
    adElement.parentNode.removeChild(adElement);
  9. Klicka på ’Arkiv’ följt av ’Spara’
  10. Klicka på ’Arkiv’ följt av  ’Stäng’
  11. Surfa till http://www.aftonbladet.se
  12. Nu borde ni slippa både toppbannern och sidobannern.

Läs mer om Greasemonkey på deras officiella hemsida.

RSS (Öppnar nytt fönster)
Visar 1-5 av 46 resultat.
av 10