<?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: Adam Lewis</title>
    <description>The latest articles on DEV Community by Adam Lewis (@dotdashnotdot).</description>
    <link>https://dev.to/dotdashnotdot</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%2F418197%2F58766788-cf42-4187-b3ed-ef28dce70d4e.png</url>
      <title>DEV Community: Adam Lewis</title>
      <link>https://dev.to/dotdashnotdot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dotdashnotdot"/>
    <language>en</language>
    <item>
      <title>Compiling Android in Jenkins In Docker (A bitter victory)</title>
      <dc:creator>Adam Lewis</dc:creator>
      <pubDate>Sun, 04 Feb 2024 16:40:48 +0000</pubDate>
      <link>https://dev.to/dotdashnotdot/compiling-android-in-jenkins-in-docker-a-bitter-victory-1n4e</link>
      <guid>https://dev.to/dotdashnotdot/compiling-android-in-jenkins-in-docker-a-bitter-victory-1n4e</guid>
      <description>&lt;p&gt;Part three of an N part series: Successfully building an Android app inside of a docker container, controlled by Jenkins, running in inside of a docker container, on a NAS - but at a cost... Covers the advanced set up of a Jenkins server on a QNAP NAS &amp;amp; getting a build to run&lt;/p&gt;

&lt;h2&gt;
  
  
  Step One - Accessing docker in Jenkins
&lt;/h2&gt;

&lt;p&gt;To use &lt;a href="https://www.jenkins.io/doc/book/pipeline/docker/"&gt;docker agents in Jenkins&lt;/a&gt; we're going to need to expose docker on our host to the docker runtime in our container, so it can create &amp;amp; control other containers as siblings. In its simplest form, this is quite easy. First we need to SSH into our NAS &amp;amp; write a new Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM jenkins/jenkins:lts-jdk17

USER root
# Ensure we're using latest available packages, install docker &amp;amp; then remove the cache to ensure a lean image
RUN apt-get -yqq update &amp;amp;&amp;amp; \
    apt-get -yqq install docker.io &amp;amp;&amp;amp; \
    apt-get clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then followed by a docker build and a docker run to test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t tmp --progress=plain .
docker run -v /var/run/docker.sock:/var/run/docker.sock -it --rm --entrypoint /bin/bash tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see everything is working as expected! 🎉🎉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@c66bce970068:/# docker image ls
REPOSITORY        TAG         IMAGE ID       CREATED        SIZE
tmp            latest      723acf6a32d6   10 seconds ago   872MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we're done, right? Unfortunately, no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step Two - No root please
&lt;/h2&gt;

&lt;p&gt;While it's great that Jenkins can access our docker socket &amp;amp; control docker in the host - the eagle eyed amongst you may notice we are running as the root user, we forgot to switch back to the default jenkins user in our Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM jenkins/jenkins:lts-jdk17

USER root
# Ensure we're using latest available packages, install docker &amp;amp; then remove the cache to ensure a lean image
RUN apt-get -yqq update &amp;amp;&amp;amp; \
    apt-get -yqq install docker.io &amp;amp;&amp;amp; \
    apt-get clean

# Switch back to default jenkins user so we're not running as root
USER jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now if we try to access docker within our container we get the an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Got permission denied while trying to connect to the Docker daemon socket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happens because when you use a bind mount in docker, it maintains the permissions of the host's file system when injecting it to the container's - as far as the Jenkins container is concerned, the jenkins user isn't permitted to access the socket so we're blocked from using docker! So how can we fix this? Well, first, let's take a look at our current permissions to check out the groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[~] # ls -la /var/run/docker.sock
srw-rw---- 1 admin administrators 0 2024-01-26 00:24 /var/run/docker.sock=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ah. Well, we don't want to give our Jenkins container full rights as part of the administrators group - after all, this is still my home NAS &amp;amp; I don't want to expose a backdoor! 😨 So let's come at this differently: We'll create a new group, give it rights to use the docker socket &amp;amp; then assign that group to our admin user on the host &amp;amp; the jenkins user in the container! First, creating our new group &amp;amp; assigning it to the admin user is simple enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;addgroup admin jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we can modify our permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getent group jenkins # Note the group id
chgrp 1000 /var/run/docker.sock # Use the group id as needed
docker ps # check everything is still working
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, new group created, admin user configured to use it - last step, Jenkins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM jenkins/jenkins:lts-jdk17

USER root
# Ensure we're using latest available packages, install docker &amp;amp; then remove the cache to ensure a lean image
RUN apt-get -yqq update &amp;amp;&amp;amp; \
    apt-get -yqq install docker.io &amp;amp;&amp;amp; \
    apt-get clean

# Force our internal docker group to have the same GID as our external jenkins user
ARG DOCKER_GROUP_ID
RUN groupmod -g $DOCKER_GROUP_ID docker &amp;amp;&amp;amp; gpasswd -a jenkins docker

# Switch back to default jenkins user so we're not running as root
USER jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which we must build with a modified command now, to pass the required group info:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build --build-arg DOCKER_GROUP_ID=`getent group jenkins | cut -d: -f3` -t tmp --progress=plain .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start up a container again, test docker ps &amp;amp; yes! We're in business 🎉🎉🎉🎉&lt;br&gt;
Now let's try out our real thing in Jenkins... We'll start a container using a command instead of the UI to configure everything we want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -v /var/run/docker.sock:/var/run/docker.sock -v jenkins_home:/var/jenkins_home -p 35035:8080 -it --rm tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update our Jenkinsfile to test out the connection inside of the pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
                sh 'docker run alpine:latest echo Goodbye world'
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And isn't it just magic?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45u1jxnjtqxxqaihe8j5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45u1jxnjtqxxqaihe8j5.png" alt="A container running from Jenkins on the host" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step Three - compiling the application
&lt;/h2&gt;

&lt;p&gt;To support docker agents, we're going to first need to install some plugins on our server:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40s2nm3shs0z9zd4kqb8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40s2nm3shs0z9zd4kqb8.png" alt="Installing Docker &amp;amp; Docker Pipeline plugins" width="800" height="308"&gt;&lt;/a&gt;&lt;br&gt;
And we can quickly rewrite our Jenkinsfile to use the new syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
    agent {
        docker { image 'alpine:latest' }
    }
    stages {
        stage('Example') {
            steps {
                sh 'echo Hello world'
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vn63278mdp2owt67sey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vn63278mdp2owt67sey.png" alt="Pipeline running using docker agent syntax" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But obviously, we're going to need something that can compile Android, not a basic Alpine Linux container! For this, I decided to use &lt;a href="https://github.com/MobileDevOps/android-sdk-image"&gt;MobileDevOps android-sdk-image&lt;/a&gt; as it had everything I would need nicely bundled up already for me. No need to reinvent the wheel after all! Before we get started though, our Jenkins server is going to need some more config first - we can only compile our application if we have a keystore to sign it.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11z18whphcxpdl3ddvdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11z18whphcxpdl3ddvdj.png" alt="Uploading a keystore as a secret file in Jenkins" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this, I simply uploaded the debug keystore from my laptop. For Windows users this can usually be found at: C:\Users[username].android\debug.keystore and for Mac/Linux users: ~/.android/debug.keystore.&lt;br&gt;
With the keystore in place, we can update our build.gradle.kts (don't worry about the passwords, they're default debug ones!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signingConfigs {
    create("debug_jenkins") {
        storeFile = file ("${project.rootDir}/keystore.jks")
        keyAlias = "androiddebugkey"
        keyPassword = "android"
        storePassword = "android"
    }
}

buildTypes {
        debug {
            if (System.getenv("IS_JENKINS") != null) {
                signingConfig = signingConfigs.getByName("debug_jenkins")
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally... Our Jenkinsfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
    agent {
        docker { image 'mobiledevops/android-sdk-image:33.0.2' }
    }
    stages {
        stage('Example') {
            environment {
                KEYSTORE_FILE = credentials('android_debug_keystore')
            }
            steps {
                withEnv(["IS_JENKINS=YES"]) {
                    sh "cp -f \"${KEYSTORE_FILE}\" \"keystore.jks\""
                    // Useful for debugging keys
                    // sh './gradlew signingReport'
                    // Do the build!
                    sh './gradlew clean assembleDebug assembleDebugUnitTest assembleDebugAndroidTest'
                }
            }
        }
    }
    post {
        always {
            archiveArtifacts artifacts: 'kotlin_app/app/build/outputs/**/*.apk', fingerprint: true
            sh 'rm -f "kotlin_app/keystore.jks'
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it... Click build in Jenkins, sit back and watch the magic happen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pcksm27gh64uz5dow8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pcksm27gh64uz5dow8c.png" alt="Successful build artifacts" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So where's the sadness/cost I mentioned at the start of this article? We set out to do everything we wanted to &lt;a href="https://dev.to/dotdashnotdot/setting-up-android-cicd-the-intro-insurance-for-a-rainy-day-6mb"&gt;when we started this project&lt;/a&gt; and we've even &lt;a href="https://dev.to/dotdashnotdot/compiling-android-in-jenkins-in-docker-nearly-516"&gt;made it fully flexible&lt;/a&gt;. We have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jenkins running in docker&lt;/li&gt;
&lt;li&gt;Jenkins spinning up a new container to build our application&lt;/li&gt;
&lt;li&gt;And the application compiled
All in a documented &amp;amp; committable format! Well, the catch is do you remember this line from earlier?
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh 'docker run alpine:latest echo Goodbye world'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, our docker set up is not configured to run rootless, which means if I were an evil-me, I could run something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh 'docker run -v /:/host alpine:latest rm -rf /host/*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And uh. That would be bad. Goodbye media collection. Our Jenkins would happily inform our host to mount our root directory, the docker user will happily run as a root user &amp;amp; we will destroy everything.&lt;/p&gt;

&lt;p&gt;But at the end of the day - that's okay - at least for me for now. Why? Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I am only going to spin up this server when I need it&lt;/li&gt;
&lt;li&gt;I am only ever going to point it at private repositories with private build files stored within them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maybe one day I'll write a part four, discovering how to make my QNAP run docker rootless to close this hole. Maybe one day I'll just accept that I should use a cloud based solution instead. Or maybe one day I'll set up a new server that I don't care about it it gets exploited.&lt;/p&gt;

&lt;p&gt;But until that day... I'll call it goal achieved&lt;/p&gt;

</description>
      <category>learning</category>
      <category>android</category>
      <category>docker</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Compiling Android In Jenkins In Docker (nearly!)</title>
      <dc:creator>Adam Lewis</dc:creator>
      <pubDate>Tue, 30 Jan 2024 23:09:46 +0000</pubDate>
      <link>https://dev.to/dotdashnotdot/compiling-android-in-jenkins-in-docker-nearly-516</link>
      <guid>https://dev.to/dotdashnotdot/compiling-android-in-jenkins-in-docker-nearly-516</guid>
      <description>&lt;p&gt;Part two of an N part series: My first steps into trying to compile an Android app via Jenkins in docker. Covers the initial set up of a Jenkins server on a QNAP NAS in docker and the realisation that this project can be so much more...&lt;/p&gt;

&lt;h2&gt;
  
  
  Step One: Creating and connecting the server to GitHub
&lt;/h2&gt;

&lt;p&gt;To begin our journey, I knew that first I would need to head on over to the Docker hub and to find the official Jenkins image: &lt;a href="https://hub.docker.com/r/jenkins/jenkins"&gt;https://hub.docker.com/r/jenkins/jenkins&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This should have everything we need to set up some pipelines on my QNAP NAS in Container Station - and even better! There's a tag with the JDK already installed - this might be perfect for building an Android app no? (Spoiler, it wasn't!)&lt;/p&gt;

&lt;p&gt;So let's get the jenkins/jenkins:lts-jdk17 image pulled locally to our NAS...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn682x14hxezjmg2s4qv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn682x14hxezjmg2s4qv.png" alt="Jenkins downloaded in Container Station" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, so far so good, now let's create a new volume for our Jenkins server's future home directory, so we don't lose any data between container restarts:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmvqibh86kovco5ynrd4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmvqibh86kovco5ynrd4.png" alt="Creating a volume for the home directory" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally, with the basics done, we can move on to the good stuff: Creating a container that will have a running server inside of it! First, let's tell Container Station that we want to use the image we just downloaded:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpufsqsc7jrj0kzf1m6as.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpufsqsc7jrj0kzf1m6as.png" alt="Image Selection Dialog" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then let's configure our container with a name &amp;amp; ensure that port 8080 is exposed (but not 50000 as we're not interested in allowing any Jenkins workers to connect to our main server in this setup)&lt;/p&gt;

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

&lt;p&gt;And thinking of stuff we probably don't need... Full access to our RAM is likely overkill too - this is still a media server too after all - so let's try limiting it to 1GB for now to see how that handles the load:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf7tcnyu0i44cu8e8lua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf7tcnyu0i44cu8e8lua.png" alt="Restricting the memory" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And for the last step, let's configure the home directory to use that volume that we created earlier:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztndx7jrklwd0h4brzm1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztndx7jrklwd0h4brzm1.png" alt="Home directory volume configuration" width="800" height="501"&gt;&lt;/a&gt;&lt;br&gt;
Annnnnnnnd bingo!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyjecriscijdp7moqj14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyjecriscijdp7moqj14.png" alt="Jenkins welcome screen" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the initial setup wizard is taken care of, it's a simple case of creating our first pipeline! Let's just add the SSH key to clone my repo annnnnd oh. Okay, well, problems are normal and to be expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx38r358qddnxztsnmn6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx38r358qddnxztsnmn6k.png" alt="No host key error" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's see, Google, StackExchange - aha! &lt;a href="https://askubuntu.com/a/1431887"&gt;Got you!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A bit of SSH magic later via a terminal inside of our container et voilà!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqatz6xv0wnzv8pmrnb69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqatz6xv0wnzv8pmrnb69.png" alt="Adding GitHub to known hosts" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try running against our script (stored in a Jenkinsfile inside of git of course!)...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
                sh './gradlew clean build'
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we are in business!&lt;/p&gt;

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

&lt;p&gt;But oh. I should've seen this coming... Jenkins may have the JDK but it definitely doesn't have the Android SDK... So the build fails...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fox9bsn0cx0wblb6c2986.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fox9bsn0cx0wblb6c2986.png" alt="Cannot find Android SDK" width="800" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, this could be as simple as &lt;a href="https://github.com/WindSekirun/Jenkins-Android-Docker"&gt;writing a new Dockerfile&lt;/a&gt;... But let's face it - this &lt;a href="https://devjorgecastro.medium.com/how-to-build-a-ci-cd-pipeline-for-android-with-jenkins-part-1-265b62b706e6"&gt;tutorial &lt;/a&gt; is much closer to what would actually be useful.&lt;/p&gt;

&lt;p&gt;I started this project with a goal, but why should I build a one trick pony? A Jenkins server that can only build my Android apps? What if I write some TypeScript? .NET? ? Will I always restart my Jenkins server/make one big, complex, Dockerfile/image?&lt;/p&gt;

&lt;p&gt;No. We can do better than that and should do better than that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jenkins server running in a container - Done already ✔&lt;/li&gt;
&lt;li&gt;Builds running in ephemeral Jenkins container agents defined by Dockerfiles/images - I've done this before ✔&lt;/li&gt;
&lt;li&gt;Container with docker installed managing docker on the host - I've done this before too ✔&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result of this should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single Dockerfile that defines how to create my Jenkins server, that can be stored in git!&lt;/li&gt;
&lt;li&gt;A Dockerfile/Image reference per project, that can contain all of the tooling I need, stored in git!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which when I consider the &lt;a href="https://dev.to/dotdashnotdot/setting-up-android-cicd-the-intro-insurance-for-a-rainy-day-6mb"&gt;origins of this little project&lt;/a&gt; I think I'm sold! My own build system, almost entirely defined as &amp;amp; reproducible as code&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/dotdashnotdot/compiling-android-in-jenkins-in-docker-a-bitter-victory-1n4e"&gt;Until part three&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExOHFiejBrdWh0dmxwd3VqN3pwbTMzbno0MXc4aHYxdWFudm1xZHZociZlcD12MV9naWZzX3NlYXJjaCZjdD1n/Uun3HIZM8KSz69jhJt/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExOHFiejBrdWh0dmxwd3VqN3pwbTMzbno0MXc4aHYxdWFudm1xZHZociZlcD12MV9naWZzX3NlYXJjaCZjdD1n/Uun3HIZM8KSz69jhJt/giphy.gif" alt="Live and Learn" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>learning</category>
      <category>softwaredevelopment</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Setting up Android CI/CD, the intro - Insurance for a rainy day</title>
      <dc:creator>Adam Lewis</dc:creator>
      <pubDate>Sat, 27 Jan 2024 23:08:56 +0000</pubDate>
      <link>https://dev.to/dotdashnotdot/setting-up-android-cicd-the-intro-insurance-for-a-rainy-day-6mb</link>
      <guid>https://dev.to/dotdashnotdot/setting-up-android-cicd-the-intro-insurance-for-a-rainy-day-6mb</guid>
      <description>&lt;p&gt;Part one of an N part series: The motivation behind trying to build my own CI system &amp;amp; why now. &lt;a href="https://dev.to/dotdashnotdot/compiling-android-in-jenkins-in-docker-nearly-516"&gt;(Part two)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three and a half years have gone by since I wrote my first post here on dev.to: &lt;a href="https://dev.to/dotdashnotdot/why-i-wrote-a-password-manager-and-what-i-ve-learned-1g7n"&gt;Why I wrote a Password Manager and what I've learned&lt;/a&gt; and a lot has changed since then. I've learned new skills, met new people and even moved to a new country! So I can even say that I have learned not only new programming languages but a new spoken one too!&lt;/p&gt;

&lt;p&gt;Yet during all of this time and through all of these changes, one thing has remained consistent and stayed the same: my laptop. Nearly 8.5 years old now, my machine is definitely starting to feel the effects of Moore's Law however...&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f0vei2ctv81j6p2epmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f0vei2ctv81j6p2epmj.png" alt="My PC rated as 21%" width="800" height="338"&gt;&lt;/a&gt;&lt;br&gt;
But still this machine just keeps on going. It keeps processing and compiling and taking on whatever little software projects I throw at it all in stride without so much as a single complaint (Ignoring one dead &amp;amp; replaced SSD!)&lt;/p&gt;

&lt;p&gt;That was until recently however when the power jack stopped working and I thought is this it, the end?.. Will I at last have to replace you and say farewell?&lt;/p&gt;

&lt;p&gt;Well I can happily write, on the afflicted laptop, that thanks to a bit of luck &amp;amp; some DIY repairs, we're going to stay together for a little while longer!&lt;/p&gt;

&lt;p&gt;That is at least until Microsoft ends support for Windows 10 in 2025... By which point I should accept that our time together is over &amp;amp; that my trusty machine will probably become a file server/backup machine or something else...&lt;/p&gt;

&lt;p&gt;Which got me thinking: what am I going to do when the day eventually does come that we say farewell to one another? When I finally buy myself a shiny new machine to catch up with the latest tech?&lt;/p&gt;

&lt;p&gt;One thing I know for certain is that I don't want to create more pain than is &lt;em&gt;absolutely&lt;/em&gt; necessary recreating my environment... Maintaining/deciphering READMEs &amp;amp; the toolchains they describe sucks, and these last few years I've tried to combat this in both my professional &amp;amp; personal work by leaning heavily into the concept of "dev-env as code" thanks to the ability to run Visual Studio code connected to a docker container/stack of containers: &lt;a href="https://github.com/dotdashnotdotsoftware/QRGoPass-IO"&gt;example...&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And honestly? I've found this approach very useful... I can destroy/swap between projects easily - so why not try to learn and push ourselves a little further?&lt;/p&gt;

&lt;p&gt;I have an old Android app &amp;amp; an old laptop both from that original article 3.5 years ago, but today I have stronger docker &amp;amp; jenkins knowledge + a NAS server that can run containers...&lt;/p&gt;

&lt;p&gt;Let's build ourselves a dockerised CI/CD system&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N-GyEXiR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://media2.giphy.com/media/mVJojMQvDwixG/giphy.gif%3Fcid%3Decf05e47fu4ldhrah8xxy8adcftzri6i3pzzjcpri25ng7d3%26ep%3Dv1_gifs_related%26rid%3Dgiphy.gif%26ct%3Dg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N-GyEXiR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://media2.giphy.com/media/mVJojMQvDwixG/giphy.gif%3Fcid%3Decf05e47fu4ldhrah8xxy8adcftzri6i3pzzjcpri25ng7d3%26ep%3Dv1_gifs_related%26rid%3Dgiphy.gif%26ct%3Dg" alt="Bring it on" width="500" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Part two coming soon...&lt;/p&gt;

</description>
      <category>android</category>
      <category>cicd</category>
      <category>docker</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Dr. Strangelove or: How I Learned to Stop Worrying and Love the Tests</title>
      <dc:creator>Adam Lewis</dc:creator>
      <pubDate>Fri, 27 Aug 2021 22:38:27 +0000</pubDate>
      <link>https://dev.to/dotdashnotdot/dr-strangelove-or-how-i-learned-to-stop-worrying-and-love-the-tests-464p</link>
      <guid>https://dev.to/dotdashnotdot/dr-strangelove-or-how-i-learned-to-stop-worrying-and-love-the-tests-464p</guid>
      <description>&lt;p&gt;Recently I've been spending a lot of time thinking about automated tests.&lt;/p&gt;

&lt;p&gt;Why do we write them? Well the benefits are obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They prove your new code works&lt;/li&gt;
&lt;li&gt;They &lt;em&gt;continue&lt;/em&gt; to prove your new code is still working, even as it ages and drifts into obsoletion...&lt;/li&gt;
&lt;li&gt;They act as documentation that &lt;em&gt;must&lt;/em&gt; be updated, providing valuable insights to the next developer who needs to maintain your code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So then why do so many companies struggle to establish coherent strategies given these benefits? Why are there so many instances of "we tried it, but it didn't really work, so we're trying this instead"?&lt;/p&gt;

&lt;p&gt;Well, is it any surprise really?&lt;br&gt;
How can you talk with both your fellow devs and non-devs about the different approaches without getting into a quagmire of confusion?&lt;/p&gt;

&lt;p&gt;Unit tests, Integration tests, Regression Tests, Feature Tests, White Box, Black Box and more...&lt;br&gt;
Given, When, Then. Arrange, Act, Assert.&lt;/p&gt;

&lt;p&gt;And the always famous "How big is a unit in a unit test?"&lt;/p&gt;

&lt;p&gt;This post is an attempt of trying to put into words the things I've learned and the perspective I've gained to cut through the mess and put all of the confusion into a single framework.&lt;br&gt;
All in the format of a conversation with myself from the past if you will permit... ;)&lt;/p&gt;

&lt;h1&gt;
  
  
  1) Drop the unit tests and integration tests
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;What?&lt;/em&gt;&lt;br&gt;
I know, I know. Advising you drop tests - crazy right? But hold on!&lt;/p&gt;

&lt;p&gt;My advice is not to stop writing the tests you already are, but to drop the terms and baggage that comes with their connotations.&lt;br&gt;
Stop thinking about unit tests as things that test your classes.&lt;br&gt;
Stop thinking of integration tests as things that test your services.&lt;/p&gt;

&lt;p&gt;Instead, think about identifying what it is you want to test and talk in those terms. Don't write unit tests and integration tests, write:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code level tests&lt;/li&gt;
&lt;li&gt;Build level tests&lt;/li&gt;
&lt;li&gt;Subsystem level tests&lt;/li&gt;
&lt;li&gt;System level tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then treat this test entity as a black box. What this means is for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A class you do not write tests that rely on private state/asserting certain paths of logic are followed&lt;/li&gt;
&lt;li&gt;The build of your service you do not write tests that will assert the flow through certain classes&lt;/li&gt;
&lt;li&gt;A subsystem you do not test the interactions of/state of the individual services that make the sub-system&lt;/li&gt;
&lt;li&gt;The whole system you do not test the interactions of/state of subsystems/services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Phrased another way, we only want to write our tests based on four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What we input into the test entity&lt;/li&gt;
&lt;li&gt;What the test entity returns&lt;/li&gt;
&lt;li&gt;What the test entity outputs into its dependencies&lt;/li&gt;
&lt;li&gt;What the test entity can receive as an input from its dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By doing this, you'll be able to drop the shackles of unit tests and integration tests and start talking about these test levels as gates to the next stage in your CI/CD train!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code level tests pass? We can create the build!&lt;/li&gt;
&lt;li&gt;Build level tests pass in an emulated deployment environment? We can deploy it into an environment!&lt;/li&gt;
&lt;li&gt;Subsystem/System level tests pass in the deployed environment? Our environment is healthy - we can promote it to the next environment!&lt;/li&gt;
&lt;li&gt;Subsystem/System level tests pass in the deployed environment? Our environment is healthy - we can promote it to the next environment!&lt;/li&gt;
&lt;li&gt;Subsystem/System level tests pass in the deployed environment? Our environment is healthy - we can promote it to the next environment!&lt;/li&gt;
&lt;li&gt;To however many you want...&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  2) Embrace that you can test four things
&lt;/h1&gt;

&lt;p&gt;No matter the size of your test entity, you can always test four things related to that entity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quality (An aspect of the entity-under-test that isn't directly tested but can be analysed by metrics)&lt;/li&gt;
&lt;li&gt;Features (Something the entity-under-test does entirely within itself)&lt;/li&gt;
&lt;li&gt;Integrations (Something that the entity-under-test does by interacting with other similarly sized entities)&lt;/li&gt;
&lt;li&gt;End-to-end (Testing that complex multi-step use cases generate the result that is expected, &lt;em&gt;but&lt;/em&gt; strictly only when the state is contained within the entity-under-test)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Okay? What does this mean? Can you give examples?&lt;/em&gt;&lt;br&gt;
Sure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Code level tests, such as testing a class/react component&lt;br&gt;
-- Quality - this is stuff like running a code linter to check the code is in line with standards&lt;br&gt;
-- Features - this is the equivalent of testing a sorting algorithm, where all logic is contained in the class&lt;br&gt;
-- Integrations - this is the equivalent of testing that your class interacts with other classes correctly&lt;br&gt;
-- E2E - if you're writing this kind of test here then your code is probably badly written to be honest. But there are some valid use cases, such as testing an object that implements the Builder pattern so it still applies!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build level tests, such as testing a NodeJS docker container&lt;br&gt;
-- Quality - this is stuff like checking the image does not contain your dev dependencies&lt;br&gt;
-- Features - could be something like testing that a particular element is visible on a webpage&lt;br&gt;
-- Integrations - could be something like testing that given a POST, a row is created in a database&lt;br&gt;
-- E2E - could be something like uploading a png and then downloading a watermarked version from your service, where the image is kept in the container's local file system&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Subsystem level tests, such as testing your new NodeJs image deployed with the database it uses&lt;br&gt;
-- Quality - this is stuff like how is the performance? What are the error rates like?&lt;br&gt;
-- Features - could be something like testing that "get product info" returns JSON (but not testing that it used certain tables in your database! It's a black box!)&lt;br&gt;
-- Integrations - could be something like testing that your subsystem fetches the stock of a product from another subsystem&lt;br&gt;
-- E2E - testing things like we can create product followed by get product&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;System level tests, such as testing a social media site&lt;br&gt;
-- Quality - similar to subsystem level tests, this is stuff like how is the performance? What are the error rates like?&lt;br&gt;
-- Features - could be something like testing that the search feature has auto-suggest&lt;br&gt;
-- Integrations - I lied earlier. You can't actually test this one! More on this soon...&lt;br&gt;
-- E2E - testing things like upload picture, rename picture &amp;amp; tag friends&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Alright, but why no integration tests at the system level?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Wait. First recognise that you can now talk about the different aspects of an entity-under-test with a fellow developer. That you have strong clear definitions to back them up. Awesome right?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fine, I can see that, but why no...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Okay okay! I get it already. I understand. Why no system level integration tests... Well it's simple really (but can require a certain amount of perspective shifting to see it) - the system does not interact with anything of a similar size.&lt;br&gt;
It is made up of subsystems that interact with each other, including any third party ones you might use!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Eh?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By integrating your own sub-system with another third party one, that third party system has become a part of your system.&lt;br&gt;
Without it, you don't function.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm not sure?..&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alright, try thinking of it this way.&lt;br&gt;
Let's say you wrote a system that upon ordering food, it posts to DevTo.&lt;br&gt;
You write some Integration tests at the build or subsystem level to check that it interacts with a mock DevTo service as expected - great!&lt;br&gt;
We still have to use the real DevTo though and check that things continue to work when we're not using the mock.&lt;br&gt;
Well, you can define your food sub-system &amp;amp; DevTo as a sub-system itself! That is, DevTo can be thought of as part of the internal state in your entity-under-test. It's no longer two entities talking to each other but rather two entities thought of as one.&lt;/p&gt;

&lt;p&gt;By this absorbing aspect, anything that's used by your system becomes part of your system.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;So that's it! Automated tests can be described in generic terms. Provided you pass all of the tests you've written under the four catagories, you can confidently move your entity-under-test to the next stage. You can move from a class in the source to a build. From a build in a per-build environment to a per sub-system environment. Per sub-system to the whole system. Rinse and repeat as many times as you need to move to the production environment!&lt;/p&gt;

&lt;p&gt;Now comes the hard part. Establishing common understanding with my peers based on this framework...&lt;/p&gt;

</description>
      <category>testing</category>
      <category>test</category>
      <category>testdev</category>
    </item>
    <item>
      <title>Given When Then</title>
      <dc:creator>Adam Lewis</dc:creator>
      <pubDate>Sun, 20 Dec 2020 17:31:42 +0000</pubDate>
      <link>https://dev.to/dotdashnotdot/given-when-then-3p95</link>
      <guid>https://dev.to/dotdashnotdot/given-when-then-3p95</guid>
      <description>&lt;p&gt;Today I'm happy to announce that I have released my first public Nuget package today! And in true Christmas fashion, I'd like to share it with you dear reader as a potential way to improve the quality of your testing code :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.nuget.org/packages/GivenWhenThen/"&gt;nuget&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/dotdashnotdot/GivenWhenThen"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Writing tests under the "Given When Then" approach is something I've come to appreciate these last few years for its ability to produce clear and easy to follow test cases. This style of test writing is covered extensively elsewhere, but speaking generally the base concept is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Given an initial state&lt;/li&gt;
&lt;li&gt;When something happens&lt;/li&gt;
&lt;li&gt;Then the result is/these things happen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where I work there is a heavy focus on making test names as close to plain English as possible. The benefits of this are two fold:&lt;br&gt;
1) Code is easier to maintain as a developer&lt;br&gt;
2) Code is easier for less technical testers to evaluate whether there is sufficient coverage&lt;/p&gt;

&lt;p&gt;But when writing tests however I noticed that I was starting to feel minor annoyances with the quality of code being produced. Naming could quickly get out of hand when trying to follow the given-when-then pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void Given_the_database_is_empty_when_searching_for_open_orders_then_it_returns_no_orders_found()
{
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spurred on by this problem, I did what any good Software Engineer should - apply some creative thinking. GivenWhenThen is the result and it aims to make writing tests in this style as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TestCaseSource(nameof(TestCases))]
public void TestThat(
    GivenWhenThenTestCase toRun)
{
    toRun.RunTestCase();
}

public static IEnumerable&amp;lt;GivenWhenThenTestCaseData&amp;gt; TestCases
{
    get
    {
        yield return GivenWhenThen.Create(
            Given.UsernameValidationFails,
            When.FredTriesToLogOn,
            Then.LogOnFailsBecauseTheUserIsNotValid);
        yield return GivenWhenThen.Create(
            Given.NoUsersExist,
            When.FredTriesToLogOn,
            Then.LogOnFailsBecauseTheUserDidNotExist);
        yield return GivenWhenThen.Create(
            Given.FredExists,
            When.FredTriesToLogOn,
            Then.LogOnSucceeds);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which to my eyes reads beautifully and renders wonderfully in a test runner too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F83th6a25mhbyno2jph33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F83th6a25mhbyno2jph33.png" alt="Runner Example" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thoughts?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dotdashnotdot/GivenWhenThen"&gt;More detail&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>csharp</category>
    </item>
    <item>
      <title>A True Single Page Application</title>
      <dc:creator>Adam Lewis</dc:creator>
      <pubDate>Wed, 07 Oct 2020 22:36:35 +0000</pubDate>
      <link>https://dev.to/dotdashnotdot/a-true-single-page-application-280b</link>
      <guid>https://dev.to/dotdashnotdot/a-true-single-page-application-280b</guid>
      <description>&lt;p&gt;Today I wrote a Single Page application in Vanilla Javascript that can literally be printed onto a single page of paper:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F87t0vtbcpcebjzmtpzik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F87t0vtbcpcebjzmtpzik.png" alt="Alt Text" width="548" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to the magic of IttyBitty and JS/CSS minifiers I proudly present the most single-page single-page app that I'm aware of: &lt;a href="https://itty.bitty.site/#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"&gt;Aster Plotter&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Based on &lt;a href="http://bl.ocks.org/bbest/2de0e25d4840c68f2db1"&gt;source&lt;/a&gt; this tool lets you easily represent exactly how far you are in total, with differently weighted components added together to give a true idea of progress!&lt;/p&gt;

&lt;p&gt;Personally I find this very useful for presenting Sprint progress ;)&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I wrote a Password Manager and what I've learned</title>
      <dc:creator>Adam Lewis</dc:creator>
      <pubDate>Sat, 27 Jun 2020 16:19:30 +0000</pubDate>
      <link>https://dev.to/dotdashnotdot/why-i-wrote-a-password-manager-and-what-i-ve-learned-1g7n</link>
      <guid>https://dev.to/dotdashnotdot/why-i-wrote-a-password-manager-and-what-i-ve-learned-1g7n</guid>
      <description>&lt;p&gt;Longtime reader, first time writer :)&lt;/p&gt;

&lt;p&gt;Some years ago as a recent graduate of university, I was working for Citrix in my first serious position of employment, updating the installers for their XenClient XT product (now OpenXT). Smart phones were beginning to truly reach their stride in how useful/powerful they could be. Advertisers were toying with interactive ads via the use of NFC &amp;amp; QR codes. And a recent move from academic pursuits into working on a security based project got me thinking...&lt;/p&gt;

&lt;p&gt;"I wonder what it would take to log in with a QR code?"&lt;/p&gt;

&lt;p&gt;I scratched down some ideas, looked into it a little from a feasability perspective and then quickly forgot about it. I had a job, other things to do/not much time and a lack of confidence in my ability to build what I'd need. Aside from sitting in my list of "ideas I once had" and occasionally reminding me of that time I thought "It would be cool if..." the project was on ice for a few years.&lt;/p&gt;

&lt;p&gt;Then came a period of unexpected unemployment. For the first time in a long time I had a large window of time with no plans on how to use it. I found myself once again looking at my list, but this time with the idea of shortening it instead of lengthening it.&lt;/p&gt;

&lt;p&gt;Out of all of the terrible/good ideas in the list the QR logon app caught my imagination again. How many times had I been in a hostel over the last few years using one of their computers lamenting the fact I had no good password management solution?&lt;/p&gt;

&lt;p&gt;By the time I had my next professional project lined up, I had used the experience gained over the previous years to build a working prototype - an idea made reality, ready to use whenever I wanted!&lt;/p&gt;

&lt;p&gt;The next goal was to refine until I felt ready to share my idea with the world. This part was probably the most difficult! Finding a balance between wanting your release to be perfect &amp;amp; knowing which problems actually don't need to be fixed is no easy task. Nor is balancing professional and personal projects time wise!&lt;/p&gt;

&lt;p&gt;But today as I write this, &lt;a href="https://www.qrgopass.com/"&gt;QRGoPass&lt;/a&gt; is live. 7 people in the world are using my app and I couldn't be happier with this little success.&lt;/p&gt;

&lt;p&gt;TL;DR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep a list of your ideas - it's great for nostalgia and you never know when you might find yourself with the time and skill improvements needed to implement one!&lt;/li&gt;
&lt;li&gt;Be persistent - small progress is still progress, give it time and you'll eventually succeed&lt;/li&gt;
&lt;li&gt;Target a minimal viable release - learn how to say "good is good enough"&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>webdev</category>
      <category>security</category>
      <category>yearinreview</category>
    </item>
  </channel>
</rss>
