Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 9 Min.

Mit Googles Werkzeugkiste

Mit Puppeteer Sharp, .NET, JavaScript und einem Chromium-Browser Webseiten testen.
© dotnetpro
In Mike Cohns Testpyramide (Bild 1) stehen UI-Tests ganz oben und machen nur einen kleinen Anteil an den gesamten Tests aus [1]. Bei Entwicklern stehen diese Tests in der ­Beliebtheit oft an letzter Stelle. UI-Tests (hier als End-to-End-Tests verstanden und synonym dazu verwendet) sind aufwendig in der Entwicklung, zeitintensiv im Ablauf und werden relativ oft geändert. Da ist es verlockend, diese Tests manuell auszuführen, was am Ende aber leicht dazu führt, dass sie nur ab und zu eingesetzt und Seiteneffektfehler übersehen werden.
Die Testpyramidenach Mike Cohn(Bild 1) © Autor
Für Webseiten-Projekte bestehen manuelle Tests darin, einzelne Webseiten aufzurufen und typische Nutzeraktionen auszuführen, wie zum Beispiel den Kauf eines Produkts oder die Anmeldung für einen Newsletter. Gesucht ist also eine Lösung, die diese manuellen Aufgaben automatisiert und am besten so ausführt, wie der Anwender es machen würde, sodass auch CSS-Probleme auffallen, wenn beispielsweise eine Schaltfläche nicht sichtbar ist.„Für solche Tests gibt es doch Selenium, das ist Open Source, kostenlos und unterstützt alle gängigen Browser“, werden erfahrene Testexperten jetzt sagen. Das stimmt und es gibt auch eine Vielzahl weiterer Tools, die Ähnliches von sich behaupten können. Die browserübergreifende Unterstützung dieser Tools führt aber zu einer weiteren Abstraktionsschicht. Um es einfach zu halten und weil es für Puppeteer Sharp noch weitere spannende Anwendungsfälle gibt (mehr dazu am Ende), ist Puppeteer Sharp jedoch einen näheren Blick wert [2].

Einfacher Einstieg für .NET-Entwickler

Für den einfachen Einstieg in das Thema automatisierter Web-UI-Tests hat der Autor Puppeteer Sharp schätzen gelernt. Dabei handelt es sich um die C#-Portierung von Googles Node.js-Projekt Puppeteer (dt. Puppenspieler). Mit dieser Bibliothek lassen sich Chromium-basierte Browser automatisiert steuern. Besonders hilfreich ist dabei der sogenannte Headless-Modus, bei dem das Browserfenster nicht angezeigt wird. Dadurch kann der UI-Test auch auf einem Server erfolgen.Die Entwicklung erfolgt dank Puppeteer Sharp überwiegend in C# und in etwas JavaScript. Als Browser kommt jeder auf Chromium basierende Browser infrage, die namhaftesten sind der-
zeit Google Chrome, Microsofts Edge und Opera mit zusammen etwa 65 Prozent Marktanteil in Deutschland [3]. Puppeteer selbst unterstützt zusätzlich noch Firefox (21 Prozent Marktanteil), Puppeteer Sharp ist in dieser Hinsicht noch nicht so weit.

Selektor kurz erklärt

Der Selektor document.querySelectorAll(<em>'#messages li'</em>) funk­tio­niert wie folgt:

Hello World

In Listing 1 ist das denkbar einfachste Programm für Puppeteer Sharp umgesetzt. Dazu ist eine neue Konsolenanwendung zu erzeugen, das NuGet-Paket Puppeteer Sharp zum Projekt hinzuzufügen und die Main()-Methode durch diejenige aus dem Listing zu ersetzen. Dabei muss gegebenenfalls der Wert von ExecutablePath noch auf das eigene System angepasst werden.
Listing 1: Puppeteer Sharps Version von „Hello World“
&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; Task &lt;span class="hljs-title"&gt;Main&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;span class="hljs-keyword"&gt;string&lt;/span&gt;[] args&lt;/span&gt;) &lt;/span&gt;&lt;br/&gt;{ &lt;br/&gt;  &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; browser = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; Puppeteer.LaunchAsync(&lt;br/&gt;    &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; LaunchOptions &lt;br/&gt;  { &lt;br/&gt;    Headless = &lt;span class="hljs-literal"&gt;true&lt;/span&gt;, &lt;br/&gt;    ExecutablePath = &lt;span class="hljs-string"&gt;@"C:\Program Files (x86)\&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;      Microsoft\Edge\Application\msedge.exe"&lt;/span&gt; &lt;br/&gt;  }); &lt;br/&gt;  &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; page = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; browser.NewPageAsync(); &lt;br/&gt;  &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; page.GoToAsync(&lt;br/&gt;    &lt;span class="hljs-string"&gt;"https://www.dotnetpro.de"&lt;/span&gt;); &lt;br/&gt;  &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; page.ScreenshotAsync(&lt;br/&gt;    &lt;span class="hljs-string"&gt;@"c:\temp\dotnetpro.jpg"&lt;/span&gt;); &lt;br/&gt;} 
Das Programm startet Edge, öffnet eine neue Registerkarte und lädt darin die Homepage der dotnetpro. Anschließend erstellt es davon einen Screenshot. Wird der Parameter Headless = false gesetzt, erscheint das Browserfenster, versehen mit dem Warnhinweis „Microsoft Edge wird durch automatisierte Testsoftware gesteuert“ (Bild 2).
Edge zeigtüber dem Inhalt einen Hinweistext, dass die Registerkarte automatischgesteuert wird(Bild 2) © Autor
Die Möglichkeit, Puppeteer Sharp bei der Arbeit zuzusehen, ist beim Entwickeln von End-to-End-Tests hilfreich, wenn etwas nicht wie gewünscht funktioniert. (Außerdem gibt es kaum ein schöneres Gefühl als die Gewissheit, künftig weniger monotone manuelle Tests ausführen zu müssen.)

Technischer Hintergrund

Bevor es an den ersten UI-Test geht, ist es sinnvoll, das Puppeteer-Objektmodell zu verstehen [4], das auch für Puppeteer Sharp gilt (Bild 3). Puppeteer kommuniziert über das Chrome-DevTools-Protokoll [5] mit dem Browser. Dabei sind folgende Ebenen von Interesse:
Das vereinfachtePuppeteer-Objektmodell(Bild 3) © Autor
  • BrowserContext: Definiert eine Browser-Session, in der Daten geteilt werden. Ein Browserfenster im privaten Modus besitzt einen eigenen Kontext im gleichen Browserprozess.
  • Page: Eine Registerkarte im Browser. Jedes Page-Objekt hat mindestens einen Frame, den MainFrame. Durch iframe- oder frame-Tags können weitere Frames erzeugt werden.
  • Frame: Jeder Frame hat mindestens den Standard-Ausführungskontext (execution context). In ihm wird das Java­Script des Frames ausgeführt. Weitere Ausführungskontexte kann es für Browser-Extensions geben.
Diese Struktur zu kennen ist insbesondere für Szenarien mit Warenkörben oder Log-ins wichtig, da es hier sinnvoll sein kann, Tests in unterschiedlichen Browserkontexten auszuführen. Das Hauptobjekt für die ersten Tests ist – sofern die zu testende Webanwendung nicht mehrere Frames hat – das Page-Objekt.

Das getestete Projekt

Als zu testendes System wird Microsofts IntegrationTestSample-Projekt für Razor Pages verwendet [6]. Es arbeitet mit einer In-Memory-Datenbank und stellt ein rudimentäres Message Board zur Verfügung (Bild 4). Dort werden Nachrichten angezeigt, es können Nachrichten ergänzt oder gelöscht werden und der Server kann die durchschnittliche Wortlänge aller Nachrichten berechnen. In der Praxis sollte keine In-Memory-Datenbank, sondern eine eigene Testdatenbank verwendet werden, die dem Live-System möglichst gleicht.
Microsofts Razor Pagesfür das Beispielprojekt zu den Integra­tionstests dient als zu testendes System(Bild 4) © Autor

Ein erster Webtest

Mit diesem Wissen ausgestattet, lässt sich mithilfe von N­Unit [7] ein erster einfacher Webtest entwickeln. Dieser folgt dem Arrange-Act-Assert(AAA)-Muster [8] und soll lediglich die Startseite aufrufen und die Anzahl der vorhandenen Nachrichten (drei) zählen. Das Beispiel beginnt mit dem Start des Webservers, dazu gibt es die Hilfsklasse aus Listing 2. In der Praxis könnten hier auch die Datenbank zurückgesetzt oder mehrere parallele Instanzen des Servers gestartet werden; da es hier um Puppeteer Sharp geht, genügt die einfache Variante, in welcher der Server aus dem bin\Debug-Folder aufgerufen wird.
Listing 2: Die Hilfsklasse zum Webserver-Start
public class SimpleServerStarter &lt;br/&gt;{ &lt;br/&gt;  private Process process; &lt;br/&gt;&lt;br/&gt;  public void StartServer() &lt;br/&gt;  { &lt;br/&gt;    RootUri = "https://localhost:5001";&lt;br/&gt;      //könnte auch ausgelesen werden &lt;br/&gt;    process = new Process() &lt;br/&gt;    { &lt;br/&gt;      StartInfo = new ProcessStartInfo &lt;br/&gt;      { &lt;br/&gt;        FileName = @"..\..\..\..\" +&lt;br/&gt;          "RazorPagesProject\bin\Debug\" +&lt;br/&gt;          "netcoreapp3.1\" +&lt;br/&gt;          "RazorPagesProject.exe", &lt;br/&gt;        UseShellExecute = true, &lt;br/&gt;        CreateNoWindow = false&lt;br/&gt;          //zeigt das Server-Log, wenn false &lt;br/&gt;      } &lt;br/&gt;    }; &lt;br/&gt;    process.Start(); &lt;br/&gt;    Task.Delay(2000).Wait();&lt;br/&gt;      //Serverstart abwarten &lt;br/&gt;  } &lt;br/&gt;&lt;br/&gt;  public string RootUri { get; set; } &lt;br/&gt;  public void StopServer() =&amp;gt;&lt;br/&gt;    process.CloseMainWindow(); &lt;br/&gt;}  
Die Testklasse ist in Listing 3 zu sehen. Im Konstruktor werden Webserver und Chromium-Browser gestartet, die nach Ausführung aller Tests mit Disposed() „abgeräumt“ werden. Der Ausdruck IgnoreHTTPSErrors = true ist nötig, da im Beispiel nur ein selbst erzeugtes Zertifikat verwendet wird und es sonst zu Fehlermeldungen im Browser kommt.
Listing 3: Der erste Webtest
[TestFixture] &lt;br/&gt;public class ErsterTest : IDisposable &lt;br/&gt;{ &lt;br/&gt;  private SimpleServerStarter server; &lt;br/&gt;  private Browser browser; &lt;br/&gt;&lt;br/&gt;  public ErsterTest() &lt;br/&gt;  { &lt;br/&gt;    server = new SimpleServerStarter(); &lt;br/&gt;    server.StartServer(); &lt;br/&gt;    StartUp().Wait(); &lt;br/&gt;  } &lt;br/&gt;&lt;br/&gt;  private async Task StartUp() &lt;br/&gt;  { &lt;br/&gt;    browser = await Puppeteer.LaunchAsync(&lt;br/&gt;      new LaunchOptions &lt;br/&gt;    { &lt;br/&gt;      Headless = false, &lt;br/&gt;      ExecutablePath = @"C:\Program Files " +&lt;br/&gt;        "(x86)\Microsoft\Edge\Application\" +&lt;br/&gt;        "msedge.exe", &lt;br/&gt;      IgnoreHTTPSErrors = true,&lt;br/&gt;        // SSL-Zertifikatsfehler ignorieren &lt;br/&gt;    }); &lt;br/&gt;  } &lt;br/&gt;&lt;br/&gt;  [Test] &lt;br/&gt;  public async Task Test1_ZaehleNachrichten() &lt;br/&gt;  { &lt;br/&gt;    var page = await browser.NewPageAsync(); &lt;br/&gt;    await page.GoToAsync($"{_server.RootUri}"); &lt;br/&gt;&lt;br/&gt;    var rows = await page.EvaluateExpressionAsync&lt;br/&gt;      &amp;lt;int?&amp;gt;("document.querySelectorAll(" +&lt;br/&gt;      "'#messages li').length"); &lt;br/&gt;    Assert.That(rows, Is.EqualTo(3)); &lt;br/&gt;  } &lt;br/&gt;&lt;br/&gt;  public void Dispose() &lt;br/&gt;  { &lt;br/&gt;    _server.StopServer(); &lt;br/&gt;    _browser.Dispose(); &lt;br/&gt;  } &lt;br/&gt;} 
Der Test selbst erzeugt zuerst eine neue Browser-Registerkarte und ruft die Website auf. Die Page-Methode Evaluate­ExpressionAsync<T>(string) führt den übergebenen String als JavaScript-Code aus und versucht, das Ergebnis als T (in diesem Fall int?) zu parsen. Dem AAA-Muster folgend wird der so erlangte Wert mit dem erwarteten Wert 3 verglichen.Was genau passiert nun in EvaluateExpressionAsync()? Auch für .NET-Entwickler mit wenig Erfahrung/Wissen über HTML und CSS lassen sich die meisten JavaScript-Zeilen für Puppeteer relativ leicht entwickeln. Nützlich ist dazu die Konsole von Chromium, die per [Strg]+[Umschalt]+[I] geöffnet werden kann. In Bild 5 ist oben die Website, in der Mitte der HTML-Code des Messages-Blocks und unten die Entwicklungskonsole zu sehen. In ihr lassen sich JavaScript-Befehle eingeben, die direkt darunter ausgewertet werden. Im Bild wurde document.querySelectorAll(’#messages li’) eingegeben und das Ergebnis ist die Anzeige einer NodeList, die li-Objekte enthält. Wird dieses JavaScript um .length ergänzt, ergibt sich 3 als Resultat. Eine Einführung in HTML/CSS-Selektoren würde den Rahmen des Artikels sprengen, eine Erklärung dieses Selektors finden Sie im Kasten Selektor kurz erklärt.
Die Chromium-Dev-Toolshelfen beim Entwickeln von Selektoren(Bild 5) © Autor

Warten, warten, warten

Eine häufige Fehlerquelle bei der Verwendung von Puppeteer (Sharp) sind fehlende Wartezeiten, in denen der Server antworten oder Chromium-Webseiten aufbauen kann. Um die Navigation abzuwarten, stehen die Werte der Enumeration WaitUntilNavigation zur Verfügung, die zum Beispiel als zweiter Parameter an Page.GotoAsync() übergeben werden können. Die Werte definieren Events, bis zu deren Eintreten gewartet werden soll. Die einzelnen Werte und ihre Bedeutung in der Reihenfolge des Auftretens sind:
  • DOMContentLoaded: Feuert, sobald das initiale HTML-Dokument geladen und geparst wurde (ohne Stylesheets, Bilder oder Sub-Frames).
  • Load: Feuert, wenn alle abhängigen Dokumente geladen wurden (der Default-Wert, wenn der Parameter nicht angegeben wird).
  • Networkidle2: Feuert, wenn 500 Millisekunden mit zwei oder weniger Netzwerkverbindungen vergangen sind.
  • Networkidle0: Feuert, wenn 500 Millisekunden ohne Netzwerkverbindung vergangen sind.
Pauschal lässt sich nicht sagen, welcher Wert hier richtig ist. Das hängt ab von der Art der Website, der Anzahl der Tests und der Zeit, die man bereit ist, in jeden Test und damit in die Gesamttestdauer zu investieren. Networkidle0 sorgt für stabile, aber sehr langsame Tests. Je nach Aufgabe kann es aber auch genügen, wenn das DOMContentLoaded-Event erreicht wurde. Eine Diskussion, wann welcher Wert sinnvoll ist, bietet das Blog Cloudlayer.io [9].Eine weitere Möglichkeit besteht darin, auf die Existenz bestimmter Objekte im DOM zu warten. Dies wird im nächsten Abschnitt vorgestellt.

Ein End-to-End-Test

Nach dem Navigieren auf Seiten und dem Auslesen von Werten fehlt noch das Interagieren mit den Inhalten der Web­seite, um einen End-To-End-Test schreiben zu können. Im letzten Testszenario sollen zwei Einträge zum Message Board hinzugefügt und geprüft werden, ob die Anzahl der Nachrichten anschließend 5 ist. Listing 4 zeigt den dafür nö­tigen Code. Im Test selbst wird zweimal die Methode ErgaenzeEintrag() aufgerufen, der Rest gleicht dem ersten Test aus Listing 3 – mit dem Unterschied, dass der erwartete Wert nun 5 ist.
Listing 4: Ein erster End-To-End-Test
[Test] &lt;br/&gt;public async Task ErgaenzgeZweiNachrichten() &lt;br/&gt;{ &lt;br/&gt;  var page = await _browser.NewPageAsync(); &lt;br/&gt;  await page.GoToAsync(_server.RootUri); &lt;br/&gt;  await ErgaenzeEintrag(page, "Erster neuer Eintrag"); &lt;br/&gt;  await ErgaenzeEintrag(page,&lt;br/&gt;    "Zweiter neuer Eintrag"); &lt;br/&gt;  var rows = await page.EvaluateExpressionAsync&amp;lt;int?&amp;gt;&lt;br/&gt;    ("document.querySelectorAll('#messages li')." +&lt;br/&gt;    "length"); &lt;br/&gt;&lt;br/&gt;  Assert.That(rows, Is.EqualTo(5)); &lt;br/&gt;} &lt;br/&gt;&lt;br/&gt;private async Task ErgaenzeEintrag(Page page,&lt;br/&gt;  string message) &lt;br/&gt;{ &lt;br/&gt;  var options = new WaitForSelectorOptions {Hidden =&lt;br/&gt;    false, Timeout = 5000, Visible = true}; &lt;br/&gt;  var textBoxHandle = await page.WaitForSelectorAsync(&lt;br/&gt;    "[name='Message.Text']", options); &lt;br/&gt;&lt;br/&gt;  await textBoxHandle.TypeAsync(message); &lt;br/&gt;&lt;br/&gt;  var navigationOptions = new NavigationOptions {&lt;br/&gt;    WaitUntil = new[] { WaitUntilNavigation.Load }&lt;br/&gt;    }; &lt;br/&gt;  var navigationTask = page.WaitForNavigationAsync(&lt;br/&gt;    navigationOptions); }); &lt;br/&gt;&lt;br/&gt;  var buttonHandle = await page.QuerySelectorAsync(&lt;br/&gt;    "#addMessageBtn"); &lt;br/&gt;  await buttonHandle.ClickAsync(); &lt;br/&gt;  await navigationTask; &lt;br/&gt;} 
In ErgaenzeEintrag() wird mittels WaitForSelectorAsync() darauf gewartet, dass ein HTML-Objekt geladen werden kann, das ein Attribut name mit dem Wert Message.Text enthält. Das Objekt muss sichtbar sein und falls es innerhalb von fünf Sekunden nicht geladen wird, schlägt der Test fehl. Die Methode WaitForSelectorAsync() gibt ein ElementHandle-Objekt zurück. Über dieses kann aus C# heraus mit HTML-­Objekten gearbeitet werden. Dies passiert in der nachfolgenden Zeile, indem der Wert aus der Variable message in die Box getippt wird. Das ist fast wörtlich zu nehmen; der Wert wird nicht durch eine einfache DOM-Manipulation gesetzt, sondern virtuell eingetippt. Dadurch ist es möglich, auch Fehler wie eine TextBox im Read-only-Modus zu finden.Die beiden folgenden Zeilen dienen dem Warten auf die fertig geladene Webseite. navigationTask muss zuerst gestartet werden und meldet Vollzug, wenn die Seite das nächste Mal geladen wird und das Load-Ereignis feuert. So lässt sich in diesem Beispiel herausfinden, dass die Webseite fertig geladen wurde und bereit ist für weitere Interaktionen.buttonHandle istauch ein ElementHandle-Objekt. Hier kommt jedoch QuerySelectorAsync() ins Spiel. Die Methode wartet nicht auf ein Objekt, sondern geht davon aus, dass das Objekt vorhanden ist; andernfalls erzeugt sie einen Fehler (Sie ahnen es: WaitForSelectorAsync() ruft letztlich QuerySelectorAsync() auf, sofern das Objekt gefunden wird). Der Button wird mittels ClickAsync() aktiviert.Wichtig ist, dass die Reihenfolge hier exakt eingehalten wird, da ClickAsync() nur zurückmeldet, dass der Klick erfolgt ist, und nichts über den aktuellen Zustand der Webseite aussagt. Um eine Race Conditon zu vermeiden, muss navigationTask unbedingt vorher gestartet und nach ClickAsync() mit await erwartet werden.

Fazit und Ausblick

Der Artikel hat einen kleinen Einblick in die Möglichkeiten und Fallstricke von Puppeteer (Sharp) gegeben. Bis zum „Master of Puppets“ ist es noch ein weiter Weg, aber der erste Schritt ist getan. Wer sich tiefer in das Thema Oberflächentests mit Puppeteer einarbeiten will, kann auf das Buch „UI Testing with Puppeteer“ von Dario Kondratiuk zurückgreifen [10], der gleichzeitig auch Autor von Puppeteer Sharp ist. Der Schwerpunkt des Buchs liegt auf Node.js und Puppeteer, das meiste lässt sich jedoch relativ einfach auf Puppeteer Sharp übertragen.Wie anfangs erwähnt, ist das Testen von grafischen Oberflächen nicht der einzige Einsatzzweck von Puppeteer. In den nächsten beiden Ausgaben der dotnetpro geht es um PDF-Erzeugung und Web-Crawling.
Projektdateien herunterladen

Fussnoten

  1. Mike Cohn, The Forgotten Layer of the Test Automation Pyramid, http://www.dotnetpro.de/SL2107PuppeteerSharp1
  2. Puppeteer Sharp, http://www.dotnetpro.de/SL2107PuppeteerSharp2
  3. Statista, Marktanteile der führenden Browserfamilien an der Internetnutzung in Deutschland von Januar 2009 bis März 2021, http://www.dotnetpro.de/SL2107PuppeteerSharp3
  4. Puppeteer API v1.11.0, http://www.dotnetpro.de/SL2107PuppeteerSharp4
  5. Chrome DevTools Protocol, http://www.dotnetpro.de/SL2107PuppeteerSharp5
  6. AspNetCore.Docs: AspNetCore.Docs, http://www.dotnetpro.de/SL2107PuppeteerSharp6
  7. NUnit, https://nunit.org
  8. Microsoft Docs, Unit test basisc – Write your tests, http://www.dotnetpro.de/SL2107PuppeteerSharp7
  9. Puppeteer waitUntil Options, http://www.dotnetpro.de/SL2107PuppeteerSharp8
  10. Dario Kondratiuk, UI Testing with Puppeteer, ISBN 978-1-800-20678-6,
  11. Selfhtml, JavaScript/DOM/Document, http://www.dotnetpro.de/SL2107PuppeteerSharp9
  12. Selfhtml, CSS/Tutorials/Selektoren, http://www.dotnetpro.de/SL2107PuppeteerSharp10

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

Bausteine guter Architektur - Entwurf und Entwicklung wartbarer Softwaresysteme, Teil 2
Code sauberer gestalten anhand von wenigen Patterns und Grundhaltungen.
6 Minuten
SOLID versus CUPID – Gegner oder Verbündete? - Softwaredesign
Die SOLID-Prinzipien gelten für Entwicklungsteams als goldene Regeln, um guten Code zu schreiben. Dan North übte 2016 Kritik daran und präsentierte als Gegenentwurf CUPID.
13 Minuten
16. Jun 2025
Mehr Struktur im Begriffschaos - Clean Code und Architektur – Module und Hosts
Clean Code befasst sich, wie der Name verrät, mit Code: Es werden Methoden und Klassen in den Blick genommen. Doch was ist der Fokus von Architektur?
18 Minuten
16. Jun 2025
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige