Visual Studio 2013

Browserübergreifende Coded UI-Tests mit Visual Studio 2013

Damian Zapart

Webbasierte Lösungen sind in den letzten Jahren immer beliebter geworden, da sie einen einfachen Zugriff für Benutzer auf der ganzen Welt bieten. Auch bei den Benutzern sind sie populär, da sie komfortabel sind. Die Benutzer müssen keine separate Anwendung installieren, sondern verwenden einfach einen Browser, um sich auf beliebigen Geräten mit Internetverbindung mit ihren Konten zu verbinden. Der Aspekt, dass die Benutzer sich für einen beliebigen Webbrowser entscheiden können, stellt allerdings für die Entwickler und Tester von Software ein Problem dar: Die Lösung muss mit mehreren Browsern getestet werden. In diesem Artikel zeige ich Ihnen, wie Sie diese Herausforderung auf einfache Weise meistern, indem Sie nur mithilfe von C# Coded UI-Testfälle erstellen, die für jeden modernen Browser ausgeführt werden.

Das neue Visual Studio

Bei der Veröffentlichung von Visual Studio 2010 vor einigen Jahren war eines der interessantesten Features der Version die Funktion zum Testen der UI von webbasierten Lösungen. Es gab damals allerdings einige Einschränkungen bei der Verwendung dieser Technologie, zum Beispiel wurde als einziger Webbrowser Internet Explorer unterstützt. Zum Testen der UI mussten außerdem die auf der Website ausgeführten Aktionen der Benutzer aufgezeichnet und dann erneut abgespielt werden, um die tatsächlichen Benutzeraktionen zu simulieren. Viele Entwickler fanden das inakzeptabel.

Das neue Visual Studio 2013, verfügbar als Release Candidate (RC), enthält eine große Anzahl von Verbesserungen in vielen unterschiedlichen Bereichen, von neuen IDE-Features bis zu einem erweiterten Testframework. Unter bit.ly/1bBryTZ finden Sie eine umfassende Liste der Änderungen in der RC-Version. Aus meiner Sicht sind zwei neue Features besonders spannend. Erstens können Sie jetzt die UI nicht nur für Internet Explorer (einschließlich Internet Explorer 11), sondern auch für alle anderen modernen Browser wie beispielsweise Google Chrome und Mozilla Firefox testen. Das zweite und aus der Perspektive der Testentwicklung noch wichtigere Feature bezeichnet Microsoft als „konfigurierbare Eigenschaften für Coded UI-Tests im Browser“. Im Wesentlichen definiert diese neue Funktionalität einen Satz von Suchkriterien für UI-Elemente. Auf diese Features gehe ich weiter unten im Artikel ausführlicher ein.

Das getestete System

Mithilfe der zwei neuen Features erstelle ich browserübergreifende Tests der vollständig codierten UI. Um eine öffentliche und allgemein bekannte webbasierte Anwendung als getestetes System (GS) festzulegen, habe ich mich für Facebook entschieden. Ich möchte zwei grundlegende Benutzerszenarien abdecken. Das erste ist ein positiver Testfall, bei dem nach erfolgreicher Anmeldung eine Profilseite anzeigt wird. Das zweite ist ein negativer Testfall, bei dem ich ungültige Benutzeranmeldeinformationen eingebe und versuche, mich anzumelden. In diesem Fall erwarte ich eine Fehlermeldung in der Benutzerantwort.

Dafür sind ein paar Hürden zu nehmen. Erstens muss der richtige Browser gestartet werden (auf Basis der Testkonfiguration), und der Browser muss auf eine bestimmte URL zugreifen können. Zweitens muss ein bestimmtes Steuerelement zur Laufzeit aus dem HTML-Dokument extrahiert werden, um eine Eingabe für den simulierten Benutzer bereitzustellen. Wo erforderlich, müssen Werte für das Steuerelement eingegeben werden, und es müssen Klicks auf die richtigen Schaltflächen erfolgen, um ein HTML-Formular an den Server zu senden. Der Code muss auch eine Antwort vom Server verarbeiten, diese überprüfen und zum Schluss den Browser schließen können, wenn die Testfälle abgeschlossen sind (in der Bereinigungsmethode des Tests).

Vor der Programmierung

Bevor ich den Code schreibe, muss ich die Umgebung vorbereiten, was recht einfach ist. Ich lade zunächst unter bit.ly/137Sg3U Visual Studio 2013 RC herunter. Standardmäßig können Sie mit Visual Studio 2013 RC nur Coded UI-Tests für Internet Explorer erstellen. Meine Absicht ist aber, Tests für alle modernen Browser zu erstellen. Natürlich treten keine Kompilierungsfehler auf, solange ich im Code angebe, dass die Tests für andere Browser als Internet Explorer ausgeführt werden sollen, aber zur Laufzeit wird eine unbehandelte Ausnahme ausgelöst. Ich zeige später, wie auch der Browser geändert wird. Um Probleme bei der Codierung zu vermeiden, muss ich die Visual Studio-Erweiterung „Selenium components for Coded UI Cross-Browser Testing“ herunterladen und installieren (bit.ly/Sd7Pgw). Mithilfe dieser Selenium-Komponenten kann ich Tests für jeden Browser ausführen, der auf meinem Computer installiert ist.

Der Code

Nach Abschluss der Vorbereitung möchte ich Ihnen jetzt zeigen, wie Sie ein neues Coded UI-Testprojekt erstellen. Öffnen Sie Visual Studio 2013, und klicken Sie auf „Datei | Neues Projekt | Vorlagen | Visual C# | Tests | Coded UI-Testprojekt“. Geben Sie den Projektnamen ein, und klicken Sie auf „OK“, um die neue Projektmappe anzuzeigen, wie in Abbildung 1 gezeigt.

Creating a New Coded UI Test ProjectAbbildung 1: Erstellen eines neuen Coded UI-Testprojekts

Die Projektmappe ist im Grunde in drei eng verbundene Gruppen unterteilt, wie in Abbildung 2 gezeigt. Die erste Gruppe umfasst den Pages-Namespace, der die BasePage-Klasse enthält, von der „ProfilePage“ und „LoginPage“ abgeleitet werden. Diese Klassen machen die Eigenschaften und die Logik verfügbar, die auf der aktuell im Browser angezeigten Seite ausgeführt werden. Durch einen Ansatz dieser Art lassen sich die Testfallimplementierungen besser von browserspezifischen Vorgängen trennen, wie z. B. ein Steuerelement anhand der ID zu suchen. Die Testfälle werden direkt auf die Eigenschaften und Funktionen angewendet, die die Page-Klassen verfügbar machen.

Coded UI Test Solution DiagramAbbildung 2: Diagramm für die Coded UI-Testprojektmappe

Der zweiten Gruppe habe ich alle Erweiterungen („UIControlExtensions“), Selektoren („SearchSelector“) und browserbezogene allgemeine Klassen hinzugefügt („BrowserFactory“, „Browser“). Diese Teilmenge von Objekten speichert die Implementierungslogik des Suchmoduls für HTML-Elemente (das ich in Kürze beschreibe). Ich habe auch eigene browserbezogene Objekte hinzugefügt, die dazu beitragen, Testfälle für den richtigen Webbrowser auszuführen. Die letzte Gruppe enthält die Testdatei („FacebookUITests“) mit den Implementierungen der Testfälle. Diese Testfälle werden nie direkt auf den Browser angewendet, sie verwenden stattdessen die Panel-Klassen.

Das Suchmodul für HTML-Steuerelemente ist ein wichtiger Bestandteil des Projekts. Mein erster Schritt ist daher die Erstellung der statischen UIControlExtensions-Klasse, die die Implementierungslogik enthält, um bestimmte Steuerelemente in der aktuell geöffneten Webbrowserseite zu suchen und zu extrahieren. Um die Programmierung einfacher zu machen und den Code außerdem später im Projekt erneut verwenden zu können, möchte ich nicht jedes Mal, wenn diese Klasse erforderlich ist, Instanzen der Klasse initialisieren müssen. Daher implementiere ich die Klasse als eine Erweiterungsmethode, die den integrierten UITestControl-Typ erweitert. Außerdem implementiere ich nur generische Erweiterungsfunktionen. Diese müssen von „HtmlControl“, der Basisklasse für alle UI-Steuerelemente im Coded UI-Testframework, abgeleitet werden und einen Standardkonstruktor ohne Parameter enthalten. Diese Funktion soll generisch sein, da ich nur nach bestimmten Steuerelementtypen suchen möchte (die Liste der verfügbaren HTML-Typen finden Sie unter bit.ly/1aiB5eW).

Ich übergebe die Suchkriterien an die Funktion über den selectorDefinition-Parameter vom Typ „SearchSelector“. Die SearchSelector-Klasse ist ein einfacher Typ, aber dennoch sehr nützlich. Die Klasse macht eine Reihe von Eigenschaften wie beispielsweise „ID“ oder „Class“ verfügbar, die in einer anderen Funktion eingerichtet und später mithilfe von Reflektion in die PropertyExpressionCollection-Klasse transformiert werden können (bit.ly/18lvmnd). Als Nächstes kann diese Eigenschaftenauflistung als Filter genutzt werden, um nur die kleine Untermenge von HTML-Steuerelementen zu extrahieren, die den angegebenen Kriterien entspricht. Die generierte Eigenschaftenauflistung wird später der SearchProperties-Eigenschaft (bit.ly/16C20iS) des generischen Objekts zugeordnet, wodurch sie die Exists-Eigenschaft und die FindMatchingControls-Funktion aufruft. Die Algorithmen des Coded UI-Testframeworks suchen nicht standardmäßig auf der ganzen Seite nach bestimmten Steuerelementen und verarbeiten alle untergeordneten Elemente und die diesen untergeordneten Elemente nur in der erweiterten „UITestControl“. Dadurch verbessert sich die Leistung der Testfälle, denn die Suchkriterien können auf nur eine kleine Untergruppe des HTML-Dokuments angewendet werden, z. B. auf alle untergeordneten Elemente eines DIV-Containers, wie in Abbildung 3 gezeigt.

Abbildung 3: Suche nach HTML-Steuerelementen

private static ReadOnlyCollection<T> 
  FindAll<T>(this UITestControl control, 
  SearchSelector selectorDefinition) where T : HtmlControl, new()
{
  var result = new List<T>();
    T selectorElement = new T { Container = control };
      selectorElement.SearchProperties.AddRange(
        selectorDefinition.ToPropertyCollection());
    if (!selectorElement.Exists)
      {
        Trace.WriteLine(string.Format(
          "Html {0} element doesn't exist for given selector {1}.",
             typeof(T).Name, selectorDefinition),"UI CodedTest");
          return result.AsReadOnly();
      }
    return selectorElement
        .FindMatchingControls()
        .Select(c => (T)c).ToList().AsReadOnly();
      }
}

Ich habe den Kern des Moduls für die Suche nach Steuerelementen implementiert. Aber die FindAll<T>-Funktion erfordert viel Code und umfangreiche Kenntnisse, damit sie ordnungsgemäß funktioniert – Sie müssen Suchparameter angeben, prüfen, ob ein Element vorhanden ist usw. Aus diesem Grund habe ich beschlossen, sie als private Funktion zu definieren und stattdessen zwei andere Funktionen verfügbar zu machen:

public static T FindById<T>(this UITestControl control, 
  string controlId) where T : HtmlControl, new()
public static T FindFirstByCssClass<T>(this UITestControl control, 
  string className, bool contains = true) where T : HtmlControl, new()

Diese generischen Methoden sind viel nützlicher, da sie nur einem Zweck dienen und die verschiedenen Dimensionen der erwarteten Eingabe auf einfache Typen reduzieren. Beide Funktionen rufen intern die FindAll<T>-Funktion auf und werden auf ihr Ergebnis angewendet, aber die Implementierung ist jeweils im Textkörper verborgen.

Verwenden beliebiger Browser

Ich habe bereits einige Arbeit in das Suchen und Abrufen von Steuerelementen investiert. Um aber zu testen, ob meine Funktionen ordnungsgemäß implementiert sind, muss ich einen Webbrowser verwenden, wozu dieser gestartet werden muss. Das Starten eines bestimmten Browsers ist genauso einfach wie jede andere browserbezogene Operation. Wie bereits erwähnt möchte ich alle browserbezogenen Operationen in seitenbezogene Klassen einfügen. Das Starten eines Browsers ist aber kein Bestandteil des Testens, sondern eine Voraussetzung. Motiviert durch bewährte Methoden für die Softwareentwicklung, habe ich beschlossen, die in Abbildung 4 gezeigte BasePage-Klasse zu erstellen, um allgemeine Operationen für alle abgeleiteten Seitenklassen ohne Redundanzen zu speichern (einschließlich das Starten eines Browsers).

Abbildung 4: Die BasePage-Klasse

public abstract class BasePage : UITestControl
{
  protected const string BaseURL = "https://www.facebook.com/";
  /// <summary>
  /// Gets URL address of the current page.
  /// </summary>
  public Uri PageUrl{get; protected set;}
  /// <summary>
  /// Store the root control for the page.
  /// </summary>
  protected UITestControl Body;
  /// <summary>
  /// Gets current browser window.
  /// </summary>
  protected BrowserWindow BrowserWindow { get; set; }
  /// <summary>
  /// Default constructor.
  /// </summary>
  public BasePage()
  {
    this.ConstructUrl();
  }
  /// <summary>
  /// Builds derived page URL based on the BaseURL and specific page URL.
  /// </summary>
  /// <returns>A specific URL for the page.</returns>
  protected abstract Uri ConstructUrl();
  /// <summary>
  /// Verifies derived page is displayed correctly.
  /// </summary>
  /// <returns>True if validation conditions passed.</returns>
  public abstract bool IsValidPageDisplayed();
}

Die statische, generische Launch<T>-Funktion ist auch ein Teil der BasePage-Klasse. Im Funktionstextkörper wird eine neue Instanz des spezifischen Seitentyps (abgeleitet von „BasePage“) initialisiert, die auf dem parameterlosen Standardkonstruktor basiert. Der Zielwebbrowser wird später im Code anhand des Browserparameterwerts festgelegt, z. B. „ie“ für Internet Explorer oder „chrome“ für Google Chrome. Diese Zuweisung gibt den Browser an, für den der aktuelle Test ausgeführt wird. Der nächste Schritt ist die Navigation zu einer URL im ausgewählten Browser. Die Verarbeitung dafür übernimmt „BrowserWindow.Launch(page.ConstructUrl())“, wobei die ConstructUrl-Funktion eine spezifische Funktion für jede abgeleitete Seite ist. Nach dem Start des Browserfensters und der Navigation zur spezifischen URL speichere ich den HTML-Textkörper in der BasePage-Eigenschaft. Optional kann ich das Browserfenster maximieren (empfehlenswert, da die Seitensteuerelemente sich möglicherweise überschneiden und Fehler bei automatisierten UI-Aktionen auftreten könnten). Ich lösche anschließend die Cookies, damit jeder Test unabhängig ist. Zum Schluss prüfe ich in der Launch-Funktion, die in Abbildung 5 gezeigt wird, ob die aktuell angezeigte Seite die richtige ist. Dazu rufe ich die Funktion „IsValidPageDisplayed“ auf, die im Kontext der generischen Seite ausgeführt wird. Diese Funktion sucht alle erforderlichen HTML-Steuerelemente (Anmeldung, Kennwort, Schaltfläche zum Senden), und überprüft, ob sie auf der Seite vorhanden sind.

Abbildung 5: Die Launch-Funktion

public static T Launch<T>(
  Browser browser = Browser.IE,
  bool clearCookies = true,
  bool maximized = true)
  where T : BasePage, new()
{
  T page = new T();
  var url = page.PageUrl;
  if (url == null)
  {
    throw new InvalidOperationException("Unable to find URL for requested page.");
  }
  var pathToBrowserExe = FacebookCodedUITestProject
        .BrowserFactory.GetBrowserExePath(browser);
  // Setting the currect browser for the test.
  BrowserWindow.CurrentBrowser = GetBrowser(browser);
  var window = BrowserWindow.Launch(page.ConstructUrl());
  page.BrowserWindow = window;
  if (window == null)
  {
    var errorMessage = string.Format(
        "Unable to run browser under the path: {0}", pathToBrowserExe);
          throw new InvalidOperationException(errorMessage);
            }
            page.Body = (window.CurrentDocumentWindow.GetChildren()[0] as
                          UITestControl) as HtmlControl;
  if (clearCookies)
  {
  BrowserWindow.ClearCookies();
  }
  window.Maximized = maximized;
  if (!page.IsValidPageDisplayed())
  {
    throw new InvalidOperationException("Invalid page is displayed.");
  }
  return page;
}
 }

Webbrowser werden kontinuierlich weiterentwickelt, und Sie bemerken es möglicherweise nicht sofort. Manchmal sind bestimmte Features in einer neuen Browserversion nicht mehr vorhanden, und dies führt dazu, dass einige Tests fehlschlagen, sogar wenn diese zuvor bestanden wurden. Es ist daher wichtig, die automatischen Browseraktualisierungen zu deaktivieren. Warten Sie, bis die jeweilige neue Version von der Erweiterung „Selenium components for Coded UI Cross-Browser Testing“ unterstützt wird. Andernfalls können zur Laufzeit unerwartete Ausnahmen auftreten, wie in Abbildung 6 gezeigt.

An Exception After a Web Browser UpdateAbbildung 6: Eine Ausnahme nach einer Webbrowseraktualisierung

Testen, Testen, Testen

Zum Schluss schreibe ich einige Logik für meine Tests. Wie zuvor erwähnt, möchte ich zwei grundlegende Benutzerszenarien testen. Das erste Szenario ist ein positiver Anmeldevorgang (ein zweiter, negativer Testfall ist im Projektquellcode verfügbar, den Sie unter msdn.microsoft.com/en-us/magazine/msdnmag1213 finden). Um diesen Test auszuführen, muss ich eine bestimmte Seitenklasse erstellen, die von „BasePage“ abgeleitet wird, wie in Abbildung 7 gezeigt. In der neuen Klasse platziere ich in privaten Feldern die gesamten konstanten Werte (Steuerelemente, IDs und CSS-Klassennamen), und ich erstelle dedizierte Methoden, die mithilfe dieser Konstanten spezifische UI-Elemente aus der aktuellen Seite extrahieren. Ich erstelle außerdem eine Funktion mit Namen „TypeCredentialAndClickLogin(string login, string password)“, die den Anmeldevorgang vollständig einkapselt. Sie findet zur Laufzeit alle notwendigen Steuerelemente, simuliert die Eingabe von Werten, die als Parameter übergeben werden, und drückt dann die Login-Schaltfläche durch Klicken mit der linken Maustaste.

Abbildung 7: Die Anmeldeseite

public class LoginPage : BasePage
  {
    private const string LoginButtonId = "u_0_1";
    private const string LoginTextBoxId = "email";
    private const string PasswordTextBoxId = "pass";
    private const string LoginFormId = "loginform";
    private const string ErrorMessageDivClass = "login_error_box";
    private const string Page = "login.php";
    /// <summary>
    /// Builds URL for the page.
    /// </summary>
    /// <returns>Uri of the specific page.</returns>
    protected override Uri ConstructUrl()
    {
      this.PageUrl = new Uri(string.Format("{0}/{1}", BasePage.BaseURL,
         LoginPage.Page));
      return this.PageUrl;
    }
    /// <summary>
    /// Validate that the correct page is displayed.
    /// </summary>
    public override bool IsValidPageDisplayed()
    {
      return this.Body.FindById<HtmlDiv>(LoginTextBoxId) != null;
    }
    /// <summary>
    /// Gets the login button from the page.
    /// </summary>
    public HtmlInputButton LoginButton
    {
      get
      {
        return this.Body.FindById<HtmlInputButton>(LoginButtonId);
      }
    }
    /// <summary>
    /// Gets the login textbox from the page.
    /// </summary>
    public HtmlEdit LoginTextBox
    {
      get
      {
        return this.Body.FindById<HtmlEdit>(LoginTextBoxId);
      }
    }
    /// <summary>
    /// Gets the password textbox from the page.
    /// </summary>
    public HtmlEdit PasswordTextBox
    {
      get
      {
        return this.Body.FindById<HtmlEdit>(PasswordTextBoxId);
      }
    }
    /// <summary>
    /// Gets the error dialog window - when login failed.
    /// </summary>
    public HtmlControl ErrorDialog
    {
      get
      {
        return this.Body.FindFirstByCssClass<HtmlControl>("*login_error_box ");
      }
    }
    /// <summary>
    /// Types login and password into input fields and clicks the Login button.
    /// </summary>
    public void TypeCredentialAndClickLogin(string login, string password)
    {
      var loginButton = this.LoginButton;
      var emailInput = this.LoginTextBox;
      var passwordInput = this.PasswordTextBox;
      emailInput.TypeText(login);
      passwordInput.TypeText(password);
      Mouse.Click(loginButton, System.Windows.Forms.MouseButtons.Left);
    }
  }

Nachdem ich nun die erforderlichen Komponenten habe, kann ich einen Testfall erstellen. Diese Testfunktion überprüft, ob der Anmeldevorgang erfolgreich abgeschlossen wird. Zu Beginn des Testfalls starte ich die Anmeldeseite mithilfe der statischen Launch<T>-Funktion. Ich übergebe alle erforderlichen Werte in die Eingabefelder für die Anmeldung und das Kennwort, und ich klicke anschließend auf die Login-Schaltfläche. Wenn der Vorgang abgeschlossen ist, überprüfe ich, dass der neu angezeigte Bereich eine Profile-Seite ist:

[TestMethod]
public void FacebookValidLogin()
{
  var loginPage = BasePage.Launch<LoginPage>();
  loginPage.TypeCredentialAndClickLogin(fbLogin, fbPassword);
  var profilePage = loginPage.InitializePage<ProfilePage>();
  Assert.IsTrue(profilePage.IsValidPageDisplayed(),
     "Profile page is not displayed.");
}

Bei der Suche nach einem Steuerelement mit einer spezifischen CSS-Klasse wurde mir klar, dass eine Komplikation im Coded UI-Testframework auftreten könnte. In HTML können Steuerelemente mehr als einen Klassennamen im Klassenattribut haben, und das wirkt sich natürlich auf das von mir verwendete Framework aus. Wenn zum Beispiel meine aktuelle Website ein DIV-Element mit der Attributklasse „A B C“ enthält, und ich die SearchSelector.Class-Eigenschaft verwende, um alle Steuerelemente mit der CSS-Klasse „B“ zu finden, erhalte ich möglicherweise keine Ergebnisse, da „A B C“ nicht gleich „B“ ist. Um dieses Problem zu lösen, führe ich die Notation mit Stern „*“ ein, die die Klassenerwartung von „ist gleich“ in „enthält“ ändert. Damit dieses Beispiel funktioniert, muss ich also die Klasse „B“ in die Klasse „*B“ ändern.

Was wäre wenn ...

Manchmal schlagen Tests fehl, und Sie müssen den Grund ermitteln. In vielen Fällen müssen Sie dazu nur das Testprotokoll lesen – aber nicht immer. Ein neues Feature im Coded UI-Testframework stellt bei Bedarf zusätzliche Informationen zur Verfügung.

Nehmen Sie an, der Test ist fehlgeschlagen, da eine andere als die erwartete Seite angezeigt wurde. In den Protokollen sehe ich, dass ein erforderliches Steuerelement nicht gefunden wurde. Das ist eine wertvolle Information, die aber nicht die vollständige Antwort enthält. Mit dem neuen Feature kann ich den aktuell angezeigten Bildschirm erfassen. Um das Feature zu verwenden muss ich nur die Erfassung und das Verfahren für ihre Speicherung zur Testbereinigungsmethode hinzufügen, wie in Abbildung 8 gezeigt. Ich erhalte nun detaillierte Informationen für jeden fehlgeschlagenen Test.

Abbildung 8: Die Testbereinigungsmethode

[TestCleanup]
public void TestCleanup()
{
  if (this.TestContext.CurrentTestOutcome != null &&
     this.TestContext.CurrentTestOutcome.ToString() == "Failed")
{
    try
    {
      var img = BrowserWindow.Desktop.CaptureImage();
      var pathToSave = System.IO.Path.Combine(
        this.TestContext.TestResultsDirectory,
        string.Format("{0}.jpg", this.TestContext.TestName));
      var bitmap = new Bitmap(img);
      bitmap.Save(pathToSave);
    }
    catch
    {
      this.TestContext.WriteLine("Unable to capture or save screen.");
    }
  }
}

Zusammenfassung

In diesem Artikel habe ich dargelegt, wie schnell und einfach Sie damit beginnen können, das neue Coded UI-Testframework in Visual Studio 2013 RC zu verwenden. Ich habe natürlich nur die grundlegende Verwendung dieser Technologie erläutert, darunter die Verwaltung von verschiedenen Browsern und die Unterstützung einer Vielzahl von Vorgängen, mit denen Sie HTML-Steuerelemente suchen, abrufen und bearbeiten können. Beim Erkunden des Frameworks werden Sie zahlreiche weitere, hervorragende Features entdecken.

Damian Zapart ist .NET-Entwickler mit mehr als sieben Jahren Erfahrung in diesem Bereich. Er beschäftigt sich hauptsächlich mit webbasierten Technologien. Als ausgewiesener Programmierungsexperte ist Zapart an innovativen Technologien, Entwurfsmustern und Tests interessiert. Weitere Informationen über ihn erhalten Sie in seinem Blog unter bit.ly/18BV7Qx.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Bilal A. Durrani und Evren Önem
Bilal A. Durrani (Microsoft) ist Test Engineer bei Microsoft im OTTS Europe-Team. Seine Aufgabe ist die Sicherung der Qualität von cloudbasierten TV-Diensten und HTML5-Clients, die Millionen von Benutzern betreffen.

Evren Onem (Microsoft) ist Entwickler bei Microsoft im OTTS Europe-Team. Er entwirft und erstellt cloudbasierte Live-TV-Dienste für Millionen von Endbenutzern. In letzter Zeit hat er sich vor allem mit JavaScript und der NodeJS-basierten serverseitigen Entwicklung beschäftigt. Zu später Stunde erforscht er außerdem kognitive Ad-hoc-Funknetze.