Varför är testning av GUI/användarinteraktion så svårt?

Jag håller på att utveckla ett kommando på jobbet. Det interagerar med användaren för att ta reda på vad det ska göra. Ungefär som en Wizard i Windows.

Jag brukar inte testa interaktion, bränd av erfarenhet från tidigare försök att testa användarinteraktion med hjälp av TDD.

Det är relativt lätt att förstå att det är svårt att testa Windows.Forms-GUIn; man måste instansiera och arbeta med Forms och Controls på ett sätt som man inte alls brukar göra. Dessutom tillkommer events-triggering som kan vara väldigt svårt att åstadkomma alls.

Men i mitt fall är det mer grundläggande: APIt jag använder är inte så komplicerat. Men det är fortfarande så j-a svårt att testa interaktion!

Därför frågar jag mig: vad är det som gör just interaktion så svårtestad..?

Interaktion består av ett slags dialog: fråga-svara-fråga-svara. Nästkommande fråga beror ofta på svaret som ges i föregående fråga. I mitt fall är det ”kommandot” som frågar ”användaren”, generellt är det alltså användaren som ger svaren och datorn som ställer frågorna.

Förutom att frågor kan bero på tidigare svar, kan sidoeffekter eller miljön som frågorna ställs i ändra på frågorna! T.ex. kan en fråga om ”välj skärprogram” undvikas helt om det bara finns precis ett skärprogram på ritningen. Frågan beror alltså också på miljön den ställs i, inte bara användarens svar.

Det är en massa beroenden här.

Kan det vara härifrån svårigheten kommer i interaktionstestning..? Beroendena? T.ex. beroende på filsystem eller databas vet man ju är komplicerat att testa. Beroenden rent generellt är ett bekymmer för testning. 

Och viktigare: kan vi attackera svårigheten på samma sätt som vi attackerar beroendesvårigheten när det gäller andra ”externals” (filer/db)? Dvs. dependency injection?

Jag gör ett försök:

    public interface IContext
    {
      Point2d PickZeroPoint();
      EntityCutProgram PickCutOrder();
      List<EntityCutProgram> PickECPsToAdd();
      void AddECPToCutOrder(ECP ecp, ECP cutorder);
    }

Det ger en väldigt enkel kod för själva kommandoexekveringen, [icke-komplett] exempel:

</pre>
public class MyCommand : Command {
    public void Execute(IContext context)
    {
      var cutorder = context.PickCutOrder();
      if (cutorder == null)
        context.PickZeroPoint();
      var ecps = context.PickECPsToAdd();
      if (ecps != null)
        foreach (var ecp in ecps)
          context.AddECPToCutOrder(cutorder, ecp);
    }
}

Nu kan vi bygga ett mock-object för IContext. Men innan vi gör det vill ja gå till hur man skriver ett test.

Vad är ett typiskt acceptanstest för en sådant här kommando..?

Ja det allra enklaste scenariot är att användaren startar kommandot, men ångrar sig och trycker Esc. Då ska kommandot avbrytas utan att något ändras i dess miljö.

Skriver först ned den mest naturliga översättningen av detta till kod:

</pre>
[Test]
public void Example1() {
  Given();
  User_runs_command();
  User_presses_Esc();
  Should_abort_command();
  Should_not_modify_context();
}

Hur ska vi realisera dessa metoder? Vi behöver

  • Något sätt att ”simulera” användaren
  • Något sätt att ”simulera” svar från miljön
  • Något sätt att få reda på om dialogen ”fallerar” någonstans (och var!)

Det sista är såklart viktigt för avbuggningstempot.

Infall: det känns som om jag helt enkelt vill skriva ned förväntad dialog och sedan verifiera den. Nytt försök på Exempel ett:

</pre>
[Test]
public void Dialog1() {
  Dialog_begins();
  Command_asks_PickCutOrder_Or_Create_new();
  User_presses_Esc();
  Dialog_ends();
}

Enklare, kortare, lättare att förstå(?). 

Hur realisera?

Det kan vara bra att komma ihåg läxan med att ”man behöver inte testa allt exakt”. Det kan räcka med ”grova drag” för att höja förtroendet för koden. 

Men för att kunna implementera User_presses_Esc() krävs det att jag kan ”rigga” returvärden från metoder. I detta fall räcker det med returvärde för ETT anrop.

Command_asks_PickCutOrder_Or_Create_new() kan realiseras mha anropslista.

</pre>
class ContextMock : IContext {
  List<string> expected;
  List<string> actual;

  public void Expect_PickCutOrder() { expected.Add("PickCutOrder"); }
  public ECP PickCutOrder() { callList.Add("PickCutOrder"); return new ECP(); }
  ...
  public void Verify() {
    CollectionAssert.AreEqual(expected, actual);
  }
}

To be continued…

Taggar: , ,

Kommentera

Fyll i dina uppgifter nedan eller klicka på en ikon för att logga in:

WordPress.com Logo

Du kommenterar med ditt WordPress.com-konto. Logga ut / Ändra )

Twitter-bild

Du kommenterar med ditt Twitter-konto. Logga ut / Ändra )

Facebook-foto

Du kommenterar med ditt Facebook-konto. Logga ut / Ändra )

Google+ photo

Du kommenterar med ditt Google+-konto. Logga ut / Ändra )

Ansluter till %s

%d bloggare gillar detta: