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.

Ny höst, ny Exertus-konferens! Vi har sedan starten åkt iväg på två konferenser per år, en på våren och en på hösten. Nice, Marstrand, Bad Gastein, Engelberg, någon som jag säkert glömt och nu denna gången blev det iväg 5 dagar till Italien och Gardasjön.
    Som vanligt blir det mycket skratt, härlig mat och dryck, roliga aktiviteter och spännande tekniksessioner. Denna resan blev inget undantag; maten och drycken var i världsklass(!), hotellet mycket bra (speciellt tältet vid poolen), och skönt med kortärmat så här års. En rolig aktivitet som vi gjorde var att paddla stående över självaste Gardasjön med maxdjup på 346 meter (wikipedia)!


Innan 3-5 rätters middagarna (ja, ena kvällen antog vi att det kommit in en 6:e rätt - men den komiska händelsen nämner vi inte här just nu) så körde vi igenom riktigt bra teknikpass. Vi började med sessioner där vi pratade om företaget; vad som händer och vad vi alla jobbar med om vardagarna. Sedan blev det några teknikpresentationer och teknik-dojon där det bland annat blev en genomgång av HTML5 och Web Components, en genomgång och parallellt kodande i Greasemonkey, samt en dojo i avancerad styling i Android.
    Den sista tiden, en heldag och lite av dagen innan, lades på "hands-on" där ett team på 5 personer jobbade med vidareutveckling av vår Android-app Liferay Blog Reader som sedan tidigare finns att hämta på Google Play. Alla började med att hämta hem källkoden från vårat GIT-repository, sätta upp utvecklingsverktygen (Eclipse eller Intellij), gick igenom översiktig arkitektur och tidigare releaser, samt definierade och delade ut uppgifter i Trello där vi jobbade med en Kanban-board. Sedan satte vi igång att koda! Den dåvarande releasen på Google Play var version 1.2 och tanken med denna session var att lyckas få fram en version 1.3. Hela arbetsprocessen gick väldigt effektivt framåt, alla arbetade strukturerat och produktivt, men framförallt med ett otroligt stort engagemang (ja, t.o.m under 3-rätters lunchen som vi vanligtvis brukade avnjuta i lugn och ro satt alla med kliandes fingrar för att få komma igång med kodandet igen).
    Det gick så pass bra att, utifrån den funktionalitet vi lyckades få fram under denna korta tid, beslutade vi oss att istället för en version 1.3 skulle detta släppas under version 2.0 (Garda Edition). Trots att några inte hade jobbat i detta projekt så lyckades vi parallellt slutföra alla våra uppgifter och sammanfoga allt till en färdig leverans (som nu finns att hämta på Google Play.
    Vi hade även ett team som jobbade på våran nästkommande Android-app som går under arbetsnamnet "ExPic"; också med integration mot Liferay Portal för uppladdning av foton (spoiler varning).

Not: Bilder från denna konferens kommer att finnas på vår Facebook-sida.

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