<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adrian Kuper</title>
    <description>The latest articles on DEV Community by Adrian Kuper (@kuperadrian).</description>
    <link>https://dev.to/kuperadrian</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F123088%2F3bed5310-ca39-45f7-8ea9-d2187456e7fa.jpg</url>
      <title>DEV Community: Adrian Kuper</title>
      <link>https://dev.to/kuperadrian</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuperadrian"/>
    <language>en</language>
    <item>
      <title>Communication is key (to good software)</title>
      <dc:creator>Adrian Kuper</dc:creator>
      <pubDate>Mon, 11 May 2020 19:31:33 +0000</pubDate>
      <link>https://dev.to/kuperadrian/communication-is-key-to-good-software-537e</link>
      <guid>https://dev.to/kuperadrian/communication-is-key-to-good-software-537e</guid>
      <description>&lt;p&gt;What I came to realize lately is this: &lt;em&gt;Communication&lt;/em&gt; is the single key factor of creating high quality software. Why? Let me try to explain.&lt;/p&gt;

&lt;h1&gt;
  
  
  Stages of software development
&lt;/h1&gt;

&lt;p&gt;The very first thing to acknowledge when building a software is: Software Development is not just about writing code (maybe for many this was obvious, but it wasn't for me as a coder). If your team is solely build from "Software Developers" (as in people who write code) the project is bound to fail. Instead each software project must go through the following stages, which each require a unique set of skills, that are pretty much impossible for a single person to aquire. These stages in their most basic form are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Planning&lt;/li&gt;
&lt;li&gt;Building&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Deploying&lt;/li&gt;
&lt;li&gt;Monitoring/Maintenance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a corporate environment it is pretty much inevitable for any software to avoid any of these stages, which all build upon and are interdependent on each other. You can't build software without planning and you can't test, deploy or monitor software that has not been build. At the same time any built software that is not tested, deployed or maintained is obviously very unlikely to generate any long-term value.&lt;/p&gt;

&lt;p&gt;To emphasize the importance and the vast set of skills necessary for each stage I would like to explain each in greater detail:&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning
&lt;/h2&gt;

&lt;p&gt;The planning stage sets the foundation for the software project and is therefore from utmost importance. Just like any house built on a loose foundation will sooner or later collapse, any software project is destined to fail without proper planning. No code should be written before planning.&lt;/p&gt;

&lt;p&gt;The planning stage requires specifying the software project from a user perspective. There must be a clear vision for the software, which is understood by all team members and that - when executed through code - solves a clear problem and generates value to the company.&lt;/p&gt;

&lt;p&gt;This stage requires strong communicational skills in order to understand the needs of the customer, how those needs can be served by the software product and communicate that to the other team members. Basic technical skills from each other stage can be hugely beneficial in order to balance the needs of the customer and the rest of the development team, while team leading skills are needed in order to gain the trust and backing of the other team members when making decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building
&lt;/h2&gt;

&lt;p&gt;With a proper plan, and clear vision of the product, it's time to start writing code. This is done most of the time by people called "software developers", although (as should be clear by now) much more people and roles with different skill sets are involved during the development of software.&lt;/p&gt;

&lt;p&gt;Writing code is a complex, lengthy and expensive task. In order to create a code-base that is maintainable, scalable, perfomant, testable and adaptable many skills must be aquired, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programming language(s) for front-end, back-end&lt;/li&gt;
&lt;li&gt;Database design and query languages&lt;/li&gt;
&lt;li&gt;Software design and patterns&lt;/li&gt;
&lt;li&gt;IDEs and version control (Visual Studio, IntelliJ, Git, etc.)&lt;/li&gt;
&lt;li&gt;A multitude of ever-changing frameworks&lt;/li&gt;
&lt;li&gt;Understanding of computers work in general&lt;/li&gt;
&lt;li&gt;Clean code principles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since technology and frameworks keep evolving and changing, the art of writing code is a life long task of learning, adapting, trying and refactoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;To prevent bugs, potentially business-critical failures, slow performance and ensure a high quality in general, each change to the product must be tested thoroughly. With a growing code base, this can only be done by automating as much of the test suite as possible. The software must be planned and designed for making automated tests easy, which requires knowledge in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Software design&lt;/li&gt;
&lt;li&gt;Unit, integration and system testing&lt;/li&gt;
&lt;li&gt;Test automation frameworks (like JUnit, Selenium, etc.)&lt;/li&gt;
&lt;li&gt;TDD, BDD and many others &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since a lot of time can be spend on testing, it's doubly important to have clear specifications for the software and features in development that are as likely as possible to deliver value to the customer. Otherwise a lot of time can be wasted on creating a automated test suite for software features that are unneccesary, unwanted or even contraproductive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying
&lt;/h2&gt;

&lt;p&gt;No software can generate any value to the company, if isn't deployed aka rolled out to the customer. Sadly this stage is more often than not overlooked during planning and therefore unnecessarily causes stress, frustration, overtime for the employees and downtime for the customers.&lt;/p&gt;

&lt;p&gt;In order to prevent this, the software must be planned and developed with an easy and automated deployment in mind. This requires knowledge of technologies such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server administration (Linux, or if you're crazy: Windows Server)&lt;/li&gt;
&lt;li&gt;Shell scripting (bash, Powershell, etc.)&lt;/li&gt;
&lt;li&gt;Computer Networks &lt;/li&gt;
&lt;li&gt;Containers and Orchestrators (Docker, Kubernetes, etc.)&lt;/li&gt;
&lt;li&gt;Virtual Machines&lt;/li&gt;
&lt;li&gt;Configuration Managment Tools (Ansible, Chef, Puppet, etc.)&lt;/li&gt;
&lt;li&gt;Cloud Services (AWS, Azure, Google Cloud, etc.)&lt;/li&gt;
&lt;li&gt;Build Server (Jenkins, Azure Pipelines, Gitlab CI, etc.)&lt;/li&gt;
&lt;li&gt;Continuous Integration/Delivery/Deployment in general&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Monitoring and Maintenance
&lt;/h2&gt;

&lt;p&gt;To keep software running, identify problems with the product early and keep in touch of the needs of the customer, every deployed piece of software must maintained and monitored. Otherwise performance problems might cause downtimes with a growing customer base, or customer might become increasingly unhappy with the product and stop using it.&lt;/p&gt;

&lt;p&gt;Mechanism for effective monitoring and maintance must planned and developed during the early stages. In addition - as with any other stage - there a lot of technologies to learn and skills to aquire:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server administration&lt;/li&gt;
&lt;li&gt;Software monitoring tools&lt;/li&gt;
&lt;li&gt;General customer support and relations&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The key to build a successful product
&lt;/h1&gt;

&lt;p&gt;With all stages of software development in mind, what is the key to build a successful product? In my mind the answer can be clearly summed up in a single word: &lt;em&gt;communication&lt;/em&gt;. In order to build a successful product, you must build a orgisational structure that makes it as easy as possbile for information to flow between each and every the stage of developemnt &lt;strong&gt;at all times&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The reason is simple: All stages depend and influence each other, while requiring unique sets of skills, talents and knowledge, which are impossible to be obtained by a single person. So learnings and information from each stage must be &lt;em&gt;continuously&lt;/em&gt; be synched between the team members through communication.&lt;/p&gt;

&lt;p&gt;It is important to keep the communication as immediate as possible. Each detour from where the information is actually needed, bears the risk of losing or altering that information, simply because we - as humans - are imperfect. Each form of communication between humans is error-prone and always carries the possibilty of misunderstandings, delays or just forgotten memories.&lt;/p&gt;

&lt;p&gt;One easy thing to do to improve existing teams is to track how information is transported between the stages and where that information is actually transformed into actual work. Track the source of the information, count the amount of people involved in transporting the information and the amount of time needed.&lt;/p&gt;

&lt;p&gt;In typical, siloed work environments its not untypical for information to make its way through 2 or 3 delegates (often in the form of managers) over the course of many days or even weeks ("let's wait for next Jour Fix!"), before it can make its way to the person, that actually transforms that information (or a very likely altered version of it) into work. Often the work is also delayed further, since each silo plans work independently and is very unlikely to have time for the problem at hand.&lt;/p&gt;

&lt;p&gt;If you can cut down on the time and persons necessary to transform the information into actual work, the software product will improve.&lt;/p&gt;

&lt;p&gt;Sadly, more often than not, organizational structure discourages frecent communication by building building barriers, which make it very hard and frustrating to build software. In my experience bad communication tends to be the main cause for frustrated employees and the single root cause for bad products.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bad communication as a system: The waterfall model
&lt;/h1&gt;

&lt;p&gt;A very infamous process framework, that systematized bad communication, is the waterfall model (as beautifully drawn by me):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--opEl6kwd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://loglevel-blog.com/content/images/2020/05/waterfall.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--opEl6kwd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://loglevel-blog.com/content/images/2020/05/waterfall.jpeg" alt="waterfall"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first glance it doesn't look too bad since every stage is accounted for, right? But the big problem is how communication is supposed to flow by the system: One-directional from one stage to another. Communicating "back up" or from any stage that is not next to each other is not encouraged. This means that each error made in the previous stage gets worse by the following, while its made artifically hard for information about problems and their solutions to make their way back in order to be prevented in the first place, e.g. during planing or code writing.&lt;/p&gt;

&lt;p&gt;A little bit better is the &lt;em&gt;extended&lt;/em&gt; waterfall model, which allows information to flow back between the stages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sBl1XLmL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://loglevel-blog.com/content/images/2020/05/waterfall-extended.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sBl1XLmL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://loglevel-blog.com/content/images/2020/05/waterfall-extended.jpeg" alt="waterfall-extended"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Still, any problem encountered during deployment or monitoring has to make its way back through each and every preceeding stage. This is bad because the information has to make "detours" and does not get where its needed immediatly. The ("Chinese whispers")[&lt;a href="https://en.wikipedia.org/wiki/Chinese_whispers"&gt;https://en.wikipedia.org/wiki/Chinese_whispers&lt;/a&gt;] effect takes place and with each detour through a unneeded stage the information is delayed, falsified or might even be lost due to shifting priorities.&lt;/p&gt;

&lt;h1&gt;
  
  
  What to do instead
&lt;/h1&gt;

&lt;p&gt;That the waterfall model is flawed isn't a new insight by any means. And people have been starting to adopt more "agile" frameworks like Kanban or Scrum, which - when implemented properly - can eaze the communication by making work visible (through kanban boards for example) and suggesting teams consisting of specialists for different stages (Product Owners for the planning stage and Software Developers for writing code).&lt;/p&gt;

&lt;p&gt;But instead of blindly following "Scrum" or "DevOps" while spending many dollars for expensive courses, you could simply ask yourself the following question when building your software development team:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What can I do to improve the continuous communication between all stages of development?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Things to do include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a autonomous and interdisciplinary team - starting with the planning stage - that covers all stages and is focussed on a single, well-defined project&lt;/li&gt;
&lt;li&gt;Have daily team meetings to keep everyone in sync. The meeting ensures that every new piece of information or problem will be discussed no later than the following day&lt;/li&gt;
&lt;li&gt;Cut meetings that serve no clear purpose or consist of disfunctional groups (meaning that not all people needed to solve a problem are present)&lt;/li&gt;
&lt;li&gt;Keep at least 50% idle time (as in time without meetings) for everyone involved in general. It must be possible for everyone to ask questions and get feedback from any stage. Busy calendars discourage questions - even though there might be some idle time here and there.&lt;/li&gt;
&lt;li&gt;Keep work visible by using a single kanban board for all team members&lt;/li&gt;
&lt;li&gt;Have a single set of tools used by all team members. Have at least readable access for everyone in the team.&lt;/li&gt;
&lt;li&gt;Release frequently for a tight feedback loop (which is communication) from the customer. Keep in mind that stages repeat.&lt;/li&gt;
&lt;li&gt;Automate as much as possible, e.g. tests, deployments. This will allow you to release often and reduces the amount of handoffs and communication needed. Communicate how the automatic release process works instead.&lt;/li&gt;
&lt;li&gt;Plan with technical debt and learning in mind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I feel that, as long improvements are focused on communication, the software quality and value will improve. It is not necessary to do X just because a process framework told you so. But Kanban, Scrum, DevOps, the agile movement in general all try to formulate frameworks, that make communication easier and more immediate on an organizational level. And that makes them a good starting point, although improvement is not achieved by following any named methodology, but by focussing on communication instead.&lt;/p&gt;

</description>
      <category>agile</category>
      <category>devops</category>
      <category>softwaredevelopment</category>
      <category>softwaremanagment</category>
    </item>
    <item>
      <title>How-To: Setup a unit-testable Jenkins shared pipeline library</title>
      <dc:creator>Adrian Kuper</dc:creator>
      <pubDate>Sat, 05 Jan 2019 15:17:58 +0000</pubDate>
      <link>https://dev.to/kuperadrian/how-to-setup-a-unit-testable-jenkins-shared-pipeline-library-2e62</link>
      <guid>https://dev.to/kuperadrian/how-to-setup-a-unit-testable-jenkins-shared-pipeline-library-2e62</guid>
      <description>&lt;p&gt;(Originally posted on &lt;a href="https://loglevel-blog.com/how-to-setup-and-develop-jenkins-shared-pipeline-library/" rel="noopener noreferrer"&gt;loglevel-blog.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In this blog post I will try to explain how to setup and develop a shared pipeline library for Jenkins, that is easy to work on and can be unit tested with JUnit and Mockito.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This blog post is kinda long and touches many topics without explaining them in full detail. If you don't feel like following along a lengthy tutorial you can have a look at the complete example library on &lt;a href="https://github.com/kuper-adrian/jenkins-shared-library-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Also if you have questions, or really any kind of feedback on what I could improve, please leave a comment and I will come back to you asap ;) Additionally, if you are completely unfamiliar with Jenkins Shared Libraries, you should probably read about them first in the &lt;a href="https://jenkins.io/doc/book/pipeline/shared-libraries/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Let's get going!&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Development Setup
&lt;/h2&gt;

&lt;p&gt;First, let's create a new IntelliJ IDEA project. I suggest using the IntelliJ IDEA for Jenkins shared pipeline development, because it is the only IDE I know of, that properly supports Java and Groovy and has Gradle support. So, if you don't have it installed it yet, you can download it &lt;a href="https://www.jetbrains.com/idea/download/#section=mac" rel="noopener noreferrer"&gt;here&lt;/a&gt; for Windows, Linux or MacOS. Also make sure to have the Java Development Kit installed, which is available &lt;a href="https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When everything is ready, start IntelliJ, create a new project, select Gradle and make sure to set the checkbox on Groovy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froynxon804ekpv0uap5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froynxon804ekpv0uap5g.png" alt="project-creation-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up, enter a GroupId and an ArtifactId.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jti6cmuqfcxgbzyeegy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jti6cmuqfcxgbzyeegy.png" alt="project-creation-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ignore the next window (the defaults are fine), click "Next", enter a project name and click "Finish".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqp7liqq6ug0haydzwnji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqp7liqq6ug0haydzwnji.png" alt="project-creation-3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IntelliJ should boot up with your new project. The folder structure in your project should be something like the following.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlt6dl4ehsukj10brr0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlt6dl4ehsukj10brr0a.png" alt="default-project-structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is cool for usual Java/Groovy projects, but for our purpose we have to change things up a bit since Jenkins demands a project structure like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt;
+- src                     &lt;span class="c"&gt;# Groovy source files&lt;/span&gt;
|   +- org
|       +- somecompany
|           +- Bar.groovy  &lt;span class="c"&gt;# for org.foo.Bar class&lt;/span&gt;
+- vars
|   +- foo.groovy          &lt;span class="c"&gt;# for global 'foo' variable&lt;/span&gt;
|   +- foo.txt             &lt;span class="c"&gt;# help for 'foo' variable&lt;/span&gt;
+- resources               &lt;span class="c"&gt;# resource files (external libraries only)&lt;/span&gt;
|   +- org
|       +- somecompany
|           +- bar.json    &lt;span class="c"&gt;# static helper data for org.foo.Bar&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;add a &lt;code&gt;vars&lt;/code&gt; folder to your project root folder&lt;/li&gt;
&lt;li&gt;add a &lt;code&gt;resource&lt;/code&gt; folder to your project root folder&lt;/li&gt;
&lt;li&gt;delete all files/folders inside &lt;code&gt;src&lt;/code&gt; and add a new package like &lt;code&gt;org.somecompany&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;edit the &lt;code&gt;build.gradle&lt;/code&gt; file:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;somecompany&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0-SNAPSHOT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;apply&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;groovy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;apply&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;java&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;sourceCompatibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.8&lt;/span&gt;

&lt;span class="nx"&gt;repositories&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;mavenCentral&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;compile&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;org.codehaus.groovy:groovy-all:2.3.11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;testCompile&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;junit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;junit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4.12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;testCompile&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;org.mockito:mockito-core:2.+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;sourceSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;java&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;srcDirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;groovy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// all code files will be in either of the folders&lt;/span&gt;
            &lt;span class="nx"&gt;srcDirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vars&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;java&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;srcDir&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After saving, import your changes to the Gradle project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mz57sjvh5ajhkh376qs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mz57sjvh5ajhkh376qs.png" alt="import-changes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point our project has the right structure to be used as a shared library by Jenkins. But, as you might have seen in the code snippet above, we also added a source directory for unit tests called &lt;code&gt;test&lt;/code&gt;. Now is the time to create this folder at the root level of the project and add a package &lt;code&gt;org.somecompany&lt;/code&gt; like we did with &lt;code&gt;src&lt;/code&gt;. The final structure should look like the following.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyguj7uzpwos71ebpszy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyguj7uzpwos71ebpszy.png" alt="final-project-structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool, it's time to implement our shared library!&lt;/p&gt;

&lt;h2&gt;
  
  
  The General Approach
&lt;/h2&gt;

&lt;p&gt;First a quick run-down on how we build our library and on why we do it that way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will keep the "custom" steps inside &lt;code&gt;var&lt;/code&gt; as simple as possible and without any real logic. Instead we create classes (inside &lt;code&gt;src&lt;/code&gt;) that do all the work.&lt;/li&gt;
&lt;li&gt;We create an interface, which declares methods for all required Jenkins steps (&lt;code&gt;sh&lt;/code&gt;, &lt;code&gt;bat&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, etc.). The classes call steps only through this interface.&lt;/li&gt;
&lt;li&gt;We write unit tests for your classes like you normally would using JUnit and Mockito.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way we are able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compile and execute our library/unit tests without Jenkins&lt;/li&gt;
&lt;li&gt;Test that our classes work as intended&lt;/li&gt;
&lt;li&gt;Test that Jenkins steps are called with the right parameters&lt;/li&gt;
&lt;li&gt;Test the behaviour of our code when a Jenkins step fails&lt;/li&gt;
&lt;li&gt;Build, test, run metrics and deploy your Jenkins Pipeline Library through Jenkins itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's get really going.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interface For Step Access
&lt;/h2&gt;

&lt;p&gt;First, we will create the interface inside &lt;code&gt;org.somecompany&lt;/code&gt; that will be used by &lt;strong&gt;all&lt;/strong&gt; classes to access the regular Jenkins steps like &lt;code&gt;sh&lt;/code&gt; or &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IStepExecutor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// add more methods for respective steps if needed&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This interface is neat, because it can be mocked inside our unit tests. That way our classes become independent to Jenkins itself. For now, let's add an implementation that will be used in our &lt;code&gt;vars&lt;/code&gt; Groovy scripts:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StepExecutor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;IStepExecutor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this will be provided by the vars script and &lt;/span&gt;
    &lt;span class="c1"&gt;// let's us access Jenkins steps&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;_steps&lt;/span&gt; 

    &lt;span class="nf"&gt;StepExecutor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sh&lt;/span&gt; &lt;span class="nl"&gt;returnStatus:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;script:&lt;/span&gt; &lt;span class="s2"&gt;"${command}"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Adding Basic Dependency Injection
&lt;/h2&gt;

&lt;p&gt;Because we don't want to use the above implementation in our unit tests, we will setup some basic dependency injection in order to swap the above implementation with a mock during unit tests. If you are not familiar with dependency injection, you should probably read up about it, since explaining it here would be out-of-scope, but you might be fine with just copy-pasting the code in this chapter and follow along.&lt;/p&gt;

&lt;p&gt;So, first we create the &lt;code&gt;org.somecompany.ioc&lt;/code&gt; package and add an &lt;code&gt;IContext&lt;/code&gt; interface:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.ioc&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.IStepExecutor&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IContext&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IStepExecutor&lt;/span&gt; &lt;span class="nf"&gt;getStepExecutor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Again, this interface will be mocked for our unit tests. But for regular execution of our library we still need an default implementation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.ioc&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.IStepExecutor&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.StepExecutor&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DefaultContext&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;IContext&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serializable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the same as in the StepExecutor class&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;_steps&lt;/span&gt;

    &lt;span class="nf"&gt;DefaultContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="n"&gt;IStepExecutor&lt;/span&gt; &lt;span class="nf"&gt;getStepExecutor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StepExecutor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To finish up our basic dependency injection setup, let's add a "context registry" that is used to store the current context (&lt;code&gt;DefaultContext&lt;/code&gt; during normal execution and a Mockito mock of &lt;code&gt;IContext&lt;/code&gt; during unit tests):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.ioc&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContextRegistry&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;Serializable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IContext&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;registerContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;registerDefaultContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DefaultContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IContext&lt;/span&gt; &lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's it! Now we are free to code testable Jenkins steps inside &lt;code&gt;vars&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding A Custom Jenkins Step
&lt;/h2&gt;

&lt;p&gt;Let's imagine for our example here, that we want to add a step to our library that  calls the .NET build tool "MSBuild" in order to build .NET projects. To do this we first add a groovy script &lt;code&gt;ex_msbuild.groovy&lt;/code&gt; to the &lt;code&gt;vars&lt;/code&gt; folder that is called like our custom step we want to implement. Since our script is called &lt;code&gt;ex_msbuild.groovy&lt;/code&gt; our step will later be callable with &lt;code&gt;ex_mbsbuild&lt;/code&gt; in our Jenkinsfile. Add the following content to the script for now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;solutionPath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;According to our general idea we want to keep our &lt;code&gt;ex_msbuild&lt;/code&gt; script as simple as possible and do all the work inside a unit-testable class. So let's create a new class &lt;code&gt;MsBuild&lt;/code&gt; in a new package &lt;code&gt;org.somecompany.build&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.build&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.IStepExecutor&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.ioc.ContextRegistry&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MsBuild&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;Serializable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;_solutionPath&lt;/span&gt;

    &lt;span class="nf"&gt;MsBuild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;solutionPath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_solutionPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solutionPath&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IStepExecutor&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContextRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getStepExecutor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;returnStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"echo \"building ${this._solutionPath}...\""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;returnStatus&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Some error"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, we use both the &lt;code&gt;sh&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; steps in our class, but instead of using them directly, we use the &lt;code&gt;ContextRegistry&lt;/code&gt; to get an instance of &lt;code&gt;IStepExecutor&lt;/code&gt; to call Jenkins steps with that. This way, we can swap out the context when we want to unit test the &lt;code&gt;build()&lt;/code&gt; method later.&lt;/p&gt;

&lt;p&gt;Now we can finish our &lt;code&gt;ex_msbuild&lt;/code&gt; script:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.build.MsBuild&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.ioc.ContextRegistry&lt;/span&gt;

&lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;solutionPath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ContextRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerDefaultContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;msbuild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MsBuild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solutionPath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msbuild&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First, we set the context with the context registry. Since we are not in a unit test, we use the default context. The &lt;code&gt;this&lt;/code&gt; we pass into &lt;code&gt;registerDefaultContext()&lt;/code&gt; will be saved by the &lt;code&gt;DefaultContext&lt;/code&gt; inside its private &lt;code&gt;_steps&lt;/code&gt; variable and is used to access Jenkins steps. After registering the context, we are free to instantiate our &lt;code&gt;MsBuild&lt;/code&gt; class and call the &lt;code&gt;build()&lt;/code&gt; method doing all the work. &lt;/p&gt;

&lt;p&gt;Nice, our &lt;code&gt;vars&lt;/code&gt; script is finished. Now we only have to write some unit tests for our &lt;code&gt;MsBuild&lt;/code&gt; class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Unit Tests
&lt;/h2&gt;

&lt;p&gt;At this point writing unit tests should be business as usual. We create a new test class &lt;code&gt;MsBuildTest&lt;/code&gt; inside the test folder with package &lt;code&gt;org.somecompany.build&lt;/code&gt;. Before every test, we use Mockito to mock the &lt;code&gt;IContext&lt;/code&gt; and &lt;code&gt;IStepExecutor&lt;/code&gt; interfaces and register the mocked context. Then we can simply create a new &lt;code&gt;MsBuild&lt;/code&gt; instance in our test and verify the behaviour of our &lt;code&gt;build()&lt;/code&gt; method. The full test class with two example test:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.build&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.IStepExecutor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.ioc.ContextRegistry&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.somecompany.ioc.IContext&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.Before&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mockito&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Mockito&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Example test class
 */&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MsBuildTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IContext&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IStepExecutor&lt;/span&gt; &lt;span class="n"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Before&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IStepExecutor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStepExecutor&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;ContextRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;build_callsShStep&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// prepare&lt;/span&gt;
        &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;solutionPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"some/path/to.sln"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;MsBuild&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MsBuild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solutionPath&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// execute&lt;/span&gt;
        &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// verify&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anyString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;build_shStepReturnsStatusNotEqualsZero_callsErrorStep&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// prepare&lt;/span&gt;
        &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;solutionPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"some/path/to.sln"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;MsBuild&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MsBuild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solutionPath&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anyString&lt;/span&gt;&lt;span class="o"&gt;())).&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// execute&lt;/span&gt;
        &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// verify&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_steps&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anyString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can use the green play buttons on left of the IntelliJ code editor to run the tests, which hopefully turn green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Things Up
&lt;/h2&gt;

&lt;p&gt;That's basically it. Now it's time to setup your library with Jenkins, create a new job and run a Jenkinsfile to test your new custom &lt;code&gt;ex_msbuild&lt;/code&gt; step. A simple test Jenkinsfile could look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// add the following line and replace necessary values if you are not loading the library implicitly&lt;/span&gt;
&lt;span class="c1"&gt;// @Library('my-library@master') _&lt;/span&gt;

&lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;
    &lt;span class="nx"&gt;stages&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;ex_msbuild&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some/path/to.sln&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Obviously there is still a lot more I could have talked about (things like unit tests, dependency injection, Gradle, Jenkins configuration, build and testing the library with Jenkins itself etc.), but I wanted to keep this already very long blog post somewhat concise. I do hope however, that the general idea and approach became clear and helps you in creating a unit-testable shared library, that is more robust and easier to work on than it normally would be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One last piece of advice&lt;/strong&gt;: The unit tests and Gradle setup are pretty nice and help a ton in easing the development of robust shared pipelines, but unfortunately there is still quite a bit that can go wrong inside your pipelines even though the library tests are green. Things like the following, that mostly happen because of Jenkins' Groovy and sandbox weirdness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a class that does not implement &lt;code&gt;Serializable&lt;/code&gt; which is necessary, because "pipelines must survive Jenkins restarts"&lt;/li&gt;
&lt;li&gt;using classes like &lt;code&gt;java.io.File&lt;/code&gt; inside your library, which is &lt;a href="https://support.cloudbees.com/hc/en-us/articles/230922128-Pipeline-Using-java-io-File-in-a-Pipeline-description" rel="noopener noreferrer"&gt;prohibited&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Syntax and spelling errors in your Jenkinsfile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore it might be a good idea to have Jenkins instance solely for integration testing, where new and modified &lt;code&gt;vars&lt;/code&gt; scripts can be tested before going "live". &lt;/p&gt;

&lt;p&gt;Again, feel free to write any kind of questions or feedback in the comments and take a look at the completed, working example library on &lt;a href="https://github.com/kuper-adrian/jenkins-shared-library-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jenkins</category>
      <category>tutorial</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>CSS grid intro for WPF/XAML developers</title>
      <dc:creator>Adrian Kuper</dc:creator>
      <pubDate>Fri, 21 Dec 2018 14:51:39 +0000</pubDate>
      <link>https://dev.to/kuperadrian/css-grid-intro-for-wpfxaml-developers-2foo</link>
      <guid>https://dev.to/kuperadrian/css-grid-intro-for-wpfxaml-developers-2foo</guid>
      <description>&lt;p&gt;This post tries to explain the CSS grid feature to those, who are already familiar with the grid layout in WPF/XAML. The XAML grid layout is pretty cool, because it makes defining complex UIs pretty easy and straight-forward. With CSS on the other hand, I always felt the need the use CSS frameworks in order to somewhat feel confident in making properly looking UIs. But no more! CSS grids brought the awesomeness of the XAML grid layout to the web almost one-to-one, which makes building complex layouts with HTML + CSS manageable at last. So let's get started.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example mock-up
&lt;/h1&gt;

&lt;p&gt;Let's take a look at a simple mock-up for a comment entry form, which we will use as an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2fd2t70aqqoq6t1fyug8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2fd2t70aqqoq6t1fyug8.jpeg" title="UI Mockup" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First we will see how to implement this using the XAML grid layout. Then we will build the same UI with HTML + CSS grids, in order to easily compare both methods.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using XAML
&lt;/h1&gt;

&lt;p&gt;Whenever I am presented a mock-up for a user interface I have to implement with XAML, I first try to figure out "the grid" of the interface and draw it into the mock-up. For our example, the grid could look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo8708r2eflb8czabsh55.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo8708r2eflb8czabsh55.jpeg" title="UI Mock-up with grid" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this point on translating this picture using the XAML grid layout is straightforward and should be understandable even for those, who don't really know about XAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Grid&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- First, we define our grid --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Grid.RowDefinitions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;RowDefinition&lt;/span&gt; &lt;span class="na"&gt;Height=&lt;/span&gt;&lt;span class="s"&gt;"Auto"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;RowDefinition&lt;/span&gt; &lt;span class="na"&gt;Height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;RowDefinition&lt;/span&gt; &lt;span class="na"&gt;Height=&lt;/span&gt;&lt;span class="s"&gt;"Auto"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;RowDefinition&lt;/span&gt; &lt;span class="na"&gt;Height=&lt;/span&gt;&lt;span class="s"&gt;"Auto"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;RowDefinition&lt;/span&gt; &lt;span class="na"&gt;Height=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Grid.RowDefinitions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Grid.ColumnDefinitions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Grid.ColumnDefinitions&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- then we add all ui items at their respective position --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Content=&lt;/span&gt;&lt;span class="s"&gt;"Name:"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Content=&lt;/span&gt;&lt;span class="s"&gt;"Email:"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextBox&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TextBox&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;Content=&lt;/span&gt;&lt;span class="s"&gt;"Comment:"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- something like an text area --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TextBox&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; 
             &lt;span class="na"&gt;Grid.ColumnSpan=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; 
             &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;
             &lt;span class="na"&gt;Height=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;
             &lt;span class="na"&gt;TextWrapping=&lt;/span&gt;&lt;span class="s"&gt;"Wrap"&lt;/span&gt;
             &lt;span class="na"&gt;AcceptsReturn=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="na"&gt;Content=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Grid&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flaaxq7mt4bfyq90w2k0m.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flaaxq7mt4bfyq90w2k0m.PNG" title="UI  with XAML" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  And now with CSS grids
&lt;/h1&gt;

&lt;p&gt;The cool thing about CSS grids is that they can be used remarkably similar to the XAML grid layout, which means if you are good with XAML you will be comfortable with CSS grids in no time.&lt;/p&gt;

&lt;p&gt;First, let's take a look at the HTML that is necessary to replicate the UI we want to create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name:&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email:&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name-txtbox"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-txtbox"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"comment-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Comment:&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"comment-txtarea"&lt;/span&gt; &lt;span class="na"&gt;rows=&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"submit-btn"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doesn't look complicated, but unlike XAML there is nothing about grids in the markup. Well, it's not called &lt;em&gt;CSS&lt;/em&gt; grid for nothing. The rows and columns of the grid, and the position of the UI items inside the grid are all defined inside the &lt;code&gt;css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* "#" selects an item inside the html by their id */&lt;/span&gt;
&lt;span class="nf"&gt;#container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* with this we declare our container as a grid */&lt;/span&gt;

  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* two columns, equal width */&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* 5 rows */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#name-label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#email-label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#name-txtbox&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#name-txtbox&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#comment-label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#comment-txtarea&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-column-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-column-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* span both columns */&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#submit-btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we select our "container"-div element, declare it as a grid and set the rows and columns almost identically to XAML (&lt;code&gt;1fr&lt;/code&gt; pretty much equals &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;auto&lt;/code&gt; equals &lt;code&gt;Auto&lt;/code&gt; and &lt;code&gt;30px&lt;/code&gt; and &lt;code&gt;60px&lt;/code&gt; are self-explanatory). After that we only have to select our UI items inside the grid and give them their place inside the grid using &lt;code&gt;grid-column&lt;/code&gt; and &lt;code&gt;grid-row&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Take a look at this &lt;a href="https://codepen.io/kuper-adrian/pen/yGVdZN/" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt; I created to see the result.&lt;/p&gt;

&lt;p&gt;To summarize, the following table comprehensively shows how certain elements known from the XAML grid layout "translate" into CSS grids:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;XAML&lt;/th&gt;
&lt;th&gt;CSS grid&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;Grid&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;div id="foo"&amp;gt;&lt;/code&gt; + &lt;code&gt;#foo { display: grid; }&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;*, 2*&lt;/td&gt;
&lt;td&gt;1fr, 2fr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;auto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;RowDefinitions&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;grid-template-rows:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;ColumnDefinitions&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;grid-template-columns:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grid.Row&lt;/td&gt;
&lt;td&gt;grid-row:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grid.RowSpan&lt;/td&gt;
&lt;td&gt;grid-row-start: + grid-row-end:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grid.Column&lt;/td&gt;
&lt;td&gt;grid-column:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grid.ColumnSpan&lt;/td&gt;
&lt;td&gt;grid-column-start: + grid-column-end:&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pretty easy, right? CSS grids allowed me to reuse the knowledge and experience I had with XAML grid layouts and finally made me feel comfortable in building complex UI layouts using HTML + CSS without the need for CSS frameworks like Bootstrap. Plus, they have some neat extra features that XAML grid layouts lack, e.g. named areas, which &lt;a href="https://alligator.io/css/css-grid-layout-grid-areas/" rel="noopener noreferrer"&gt;you should definitely check out&lt;/a&gt;. Once you're convinced by CSS grids and want to know all about them, take a look at &lt;a href="https://css-tricks.com/snippets/css/complete-guide-grid/" rel="noopener noreferrer"&gt;this awesome and complete guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>css</category>
      <category>xaml</category>
      <category>wpf</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
