<?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>Forem: Jordan Polaniec</title>
    <description>The latest articles on Forem by Jordan Polaniec (@franndotexe).</description>
    <link>https://forem.com/franndotexe</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%2F30444%2Fddf31cbd-0fb6-42ef-8e63-a60902591727.jpg</url>
      <title>Forem: Jordan Polaniec</title>
      <link>https://forem.com/franndotexe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/franndotexe"/>
    <language>en</language>
    <item>
      <title>Useful sites and apps I've come across while studying web frontend development and web server configuration</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Wed, 16 Oct 2019 23:27:41 +0000</pubDate>
      <link>https://forem.com/franndotexe/useful-sites-and-apps-i-ve-come-across-while-studying-web-frontend-development-and-web-server-configuration-19jb</link>
      <guid>https://forem.com/franndotexe/useful-sites-and-apps-i-ve-come-across-while-studying-web-frontend-development-and-web-server-configuration-19jb</guid>
      <description>&lt;p&gt;Web development is hard. There are seemingly infinite ways to make things work, many of them outdated and no longer useful to the developer because of the incredible speed with which the web development ecosystem pushes forward.&lt;/p&gt;

&lt;p&gt;This speed has its pros and cons, and one of the drawbacks I've found while learning web development is that sometimes finding up-to-date resources for learning is difficult.&lt;/p&gt;

&lt;p&gt;What follows is a smattering of resources I've found incredibly useful while beginning to study web development - both front- and back-end.  &lt;/p&gt;

&lt;p&gt;I've stayed away from learning frameworks, focusing instead on learning mostly fundamentals. For me personally, just learning a framework like Angular or React without having a solid understanding of the basics means long, frustrating debugging sessions while attempting to interpret what was messed up in the framework. Often times, this issue is exacerbated by the fact that the framework assumes I know the basics of whatever the context is and glosses over certain details when reporting error information.&lt;/p&gt;

&lt;p&gt;Please note: I am in no way affiliated with any of the following resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend basics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://internetingishard.com/"&gt;Interneting is Hard&lt;/a&gt; - This site offers a beautifully made tutorial about using HTML and CSS to build modern websites. It starts from the perspective of someone who knows nothing about HTML or CSS. If you're looking for a rock solid place to start this is it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web"&gt;Mozilla Developer Network&lt;/a&gt;&lt;br&gt;
This is most likely the best reference for general web development knowledge and has replaced the W3 Schools in my digital library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flukeout.github.io/"&gt;CSS Diner&lt;/a&gt;&lt;br&gt;
Practice CSS selectors while selecting fruits and veggies! CSS selectors are very important to building clean, maintainable CSS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser feature compatibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://caniuse.com/"&gt;CanIUse&lt;/a&gt;&lt;br&gt;
I quickly learned not all browsers are created equal.  CanIUse shows just how different they are, feature by feature. Example queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=referrer-policy"&gt;Referrer Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=contentsecuritypolicy"&gt;Content Security Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=video"&gt;Video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=client-hints-dpr-width-viewport"&gt;Client Hints: DPR, Width, Viewport-Width&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=srcset"&gt;Srcset and sizes attributes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=http2"&gt;Http/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=css-grid"&gt;CSS Grid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=imports"&gt;Imports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#search=css-image-set"&gt;CSS image-set&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=calc"&gt;calc()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/#feat=ttf"&gt;TrueType and OpenType&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CSS Grid&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridbyexample.com/learn/"&gt;Grid By Example&lt;/a&gt;&lt;br&gt;
While I spent a large amount of time learning Flexbox, CSS Grid is just as cool if not a bit more easy to use (in my novice opinion) from a responsive design perspective.  This &lt;a href="https://gridbyexample.com/video/grid-template-areas/"&gt;feature&lt;/a&gt; in particular is very cool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Templating engines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I do not consider these to be frameworks akin to Angular or React, but they helped with some of the pain points of writing plain HTML, such as writing list items. I do not have a strong opinion on either yet so I've listed the two I've spent some time learning and using in small projects.&lt;/p&gt;

&lt;p&gt;The idea behind these types of tools is helping componentize blocks of HTML by 'templating' out logical groups (like a 'card' of data) and allowing for data to be inserted into specific areas of the template at runtime. This way, a single logical grouping of HTML can be used over and over again while the actual markup only needs to exist once (while avoiding the overhead of larger frameworks that also do this).  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pugjs.org/api/getting-started.html"&gt;Pug - formerly Jade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://handlebarsjs.com/"&gt;Handlebars&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's also worth noting that &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt; has &lt;a href="https://expressjs.com/en/guide/using-template-engines.html"&gt;support&lt;/a&gt; for some template engines, although that is outside of the scope of this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser usage stats&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://gs.statcounter.com/"&gt;gs.statcounter&lt;/a&gt;&lt;br&gt;
Straightforward, no-nonsense data about browser usage, common screen resolutions and device usage. If you're looking to build a web app for a more specific audience this site could help you decide what to focus on first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I am not very artistic. The resources here mostly helped me learn some ways web design can be done. One crucial concept I learned is that there is nothing wrong with having someone dedicated to designing your site for you as the web developer to then build. This might mean acquiring a pre-built theme and then modifying it as needed or working with a full-time web designer.  I found that I intertwined web development and web design as a single profession more than they need to be. Web design is a full-time job and people study it just as hard as programmers study programming. If you want something that looks professional and don't want to attempt to manage learning both good design and web programming yourself, these artists will do the design right, thus freeing your mind to focus on building the vision. That being said, understanding some of their processes and tooling will help you communicate with them which can be a major benefit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://paletton.com"&gt;Paletton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.figma.com"&gt;Figma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reliablepsd.com/ultimate-google-font-pairings/"&gt;Ultimate Google Font Pairings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.typewolf.com/"&gt;Typewolf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-1-559d4e805cda"&gt;7 Rules for Creating Gorgeous UI - Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96"&gt;7 Rules for Creating Gorgeous UI - Part 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Provisioning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's one (very important) thing to know how to build the frontend of a website, but hosting it was something rather foreign to me as well. There are a smorgasboard of tools available to do this, many of them offering more or less options based on how much stuff in the server you want to manage yourself.&lt;/p&gt;

&lt;p&gt;Here are some options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/"&gt;Microsoft Azure&lt;/a&gt;&lt;br&gt;
Coming from a .NET background I thought I'd be more confortable learning to host sites on Azure. I've hosted a few very small APIs on Azure in the past, but never any actual web sites.&lt;/p&gt;

&lt;p&gt;To be honest, the amount of options in Azure was overwhelming to me as a novice. More power to you if you want to go this route or the Amazon Web Services route, but I found the complexity more than I wanted to take on at this stage of learning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dashboard.heroku.com/"&gt;Heroku&lt;/a&gt;&lt;br&gt;
At no point in time did I need to remote into a machine and run terminal commands to configure a site while using Heroku. Their web UI is more than enough to get a basic site hosted and they offer some really nice tooling around configuring Continuous Integration among some other great scaling options, and there is a free tier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/"&gt;Digital Ocean&lt;/a&gt; &lt;br&gt;
In constrast to Heroku, Digital Ocean requirements for configuration are much more technical, however, that is not a bad thing. In fact, coming at setting up a web server with almost no knowledge of how to do it led me to using Digital Ocean for several sites I've done so far specifically because I wanted to learn more about how this is done.  &lt;/p&gt;

&lt;p&gt;Aside from 'empty' VPS's (Virtual Private Servers - they call them Droplets) they have these &lt;a href="https://www.digitalocean.com/products/one-click-apps/"&gt;One-click apps&lt;/a&gt; which are VPS's with a bunch of software pre-installed and configured on them. These are amazing, especially for a beginner like me. This reduces the overhead for getting a server up and running drasitically, allowing me to focus on the specifics of my server configuration (like setting up sites in Nginx). I've used the NodeJS and Ghost One-click apps so far and might check out the GitLab one in the future.  &lt;/p&gt;

&lt;p&gt;In addition to fantastic infrastructure provisioning, DigitalOcean has some of the best tutorials and documentation on web server configurations I've come across.  Some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-dns-terminology-components-and-concepts"&gt;An Introduction to DNS Terminology, Components, and Concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04"&gt;How to Secure Nginx with Let's Encrypt on Ubuntu 16.04&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-point-to-digitalocean-nameservers-from-common-domain-registrars"&gt;How to Point to DigitalOcean Nameservers From Common Domain Registrars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/nginx-essentials-installation-and-configuration-troubleshooting"&gt;Nginx Essentials: Installation and Configuration Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04"&gt;How to Install Nginx on Ubuntu 16.04&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-protect-an-nginx-server-with-fail2ban-on-ubuntu-14-04"&gt;How To Protect an Nginx Server with Fail2Ban on Ubuntu 14.04&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-fail2ban-works-to-protect-services-on-a-linux-server"&gt;How Fail2Ban Works to Protect Services on a Linux Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dnsmap.io/"&gt;DNSMap&lt;/a&gt;&lt;br&gt;
One thing that burned me several times was that making DNS changes is not instant.  There were a few times I was repointing a domain to different name servers while adjusting the site config (in Nginx). This was confusing because browsers would show the site was misconfigured (especially if SSL certs were involved) even though in reality DNS propagation of my changes had not yet completed. There are a bunch of variations on the above tool, but it helps to know when these types of changes have 'completed'. These tools aren't perfect, but I haven't found another way to be notified when propagation has been completed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Ffrannsoft.com%2F&amp;amp;tab=mobile"&gt;Google's PageSpeed Insights&lt;/a&gt;&lt;br&gt;
You may have already heard of PageSpeed Insights since 'page speed' is now &lt;a href="https://webmasters.googleblog.com/2018/01/using-page-speed-in-mobile-search.html"&gt;factored into Google search rankings for mobile&lt;/a&gt;. I found this tool useful after a first pass on a site as it helps identify issues that I found fixes easy to learn and implement. It also seperates mobile and desktop scores which can be very helpful if you're trying to tailor your site to a specific audience. Additionally, if you're looking for something more intensive, take a look at &lt;a href="https://developers.google.com/web/tools/lighthouse/"&gt;Lighthouse&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nginx&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my brief time learning about how to configure web servers Nginx has been fairly straightforward to use. There are docs and articles galore about how to do very specific things. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://nginx.org/en/docs/beginners_guide.html"&gt;Nginx - Start with Igor's Beginner's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nginx.com/resources/wiki/start/"&gt;Wiki home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.nginx.com/products/nginx-amplify/"&gt;Nginx Amplify&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I got some sites up and running I looked around for some services to monitor them. I didn't want to be writing my own tooling or parsing log files and landed on Nginx Amplify. You can connect a few Nginx instances to Amplify for free and the monitoring tools are great. Included in the free 'tier' is the ability to send Slack messages and emails when metrics go over thresholds you configure. Thanks to this tool I was notified of a bot making thousands of requests per second to one of my sites just a few minutes after the bot started doing this. A quick update to the firewall stopped the issue. Not only does this show monitoring metrics, but will use static analysis on your nginx.conf files to find common issues and show info about SSL configs and security advisories for the running version of Nginx. Again, tools like this are super useful to newbies like me as it helped give an awareness of the system I might not have been able to grasp otherwise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nginxconfig.io"&gt;NginxConfig.io&lt;/a&gt; - This tool helps kickstart writing nginx config files and server blocks&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web server configuration tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/laboratory-by-mozilla/"&gt;Laboratory (Content Security Policy / CSP Toolkit)&lt;/a&gt;&lt;br&gt;
While learning about secure server configurations I came across this Firefox addon.  It will build your &lt;a href="https://developers.google.com/web/fundamentals/security/csp/"&gt;Content Security Policy&lt;/a&gt; while recording you using your site. This, of course, assumes your site is stable and serving expected data. It should also be noted this is an experimental addon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ssllabs.com/ssltest/"&gt;Test your server's SSL implementation&lt;/a&gt;&lt;br&gt;
Again, being a newbie I wanted to find tools that could tell me what I was messing up while learning. This site focuses on grading your SSL implementation in detail and is a good jumping point for seeing what makes up a secure SSL implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image optimization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tinypng.com/"&gt;TinyPng&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudinary.com/documentation/image_optimization"&gt;Cloudinary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.responsivebreakpoints.com/"&gt;Responsive Image Breakpoints Generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of all the frontend concepts I'm learning I find this the most challenging. I didn't discover Cloudinary until sinking a bunch of time into learning how to shape or serve variations of my images depending on the client parameters. In fact, using newer features like &lt;a href="https://internetingishard.com/html-and-css/responsive-images/"&gt;sizes and srcset&lt;/a&gt; require knowing how all the parameters to configure them work, which is a decent hill to climb by itself.  &lt;/p&gt;

&lt;p&gt;However, once I started to understand how &lt;code&gt;sizes&lt;/code&gt; and &lt;code&gt;srcset&lt;/code&gt; work in tandem with media queries I loved Cloudinary all the more. The work it does on the fly for the developer is incredible.&lt;/p&gt;

&lt;p&gt;TinyPng is listed because it's important to understand how responsive image sizing (by itself) is different from compression-based image optimization. TinyPng (which supports more than PNGs) focuses on optimization of an existing image rather than only resizing it. While Cloudinary does offer this service as well, it's probably overkill for smaller sites. TinyPng will optimize your images for free up to a certain &lt;a href="https://tinypng.com/developers"&gt;quota&lt;/a&gt;. I found their .NET api super easy to use to send up a folder of images for processing.&lt;/p&gt;

&lt;p&gt;That's my set of sites and tools I've come across so far while learning web development. I hope some of them are useful to you! &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Featured Image by &lt;a href="https://unsplash.com/photos/nCvi-gS5r88"&gt;nihon graphy&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>css</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Part 7 - Deploying a simple web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 21:00:32 +0000</pubDate>
      <link>https://forem.com/franndotexe/part-7-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-1fib</link>
      <guid>https://forem.com/franndotexe/part-7-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-1fib</guid>
      <description>&lt;p&gt;Thanks for reading this tutorial and I hope you enjoyed it! You now have a website, the ability to analyze server logs, hosted storage and analysis of client error logs and are receiving alerts on noteworthy system usage. What's next? Here's a few tools and posts you may want to check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nginxconfig.io/"&gt;NginxConfig.io&lt;/a&gt; - This tool provides a UI for building Nginx &lt;code&gt;conf&lt;/code&gt; files. It has a dizzying amount of options available with many common presets. If you're looking to increase the capability of your Nginx configuration this might be a good place to start, even if just to see new terms to learn about.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Server hardening
&lt;/h5&gt;

&lt;p&gt;There are a multitude of additional steps that can be taken to secure your server. We have done some of them, but there are always more. It's up to you how much time you want to put into this based on the website(s) you are serving.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.owasp.org/index.php/SCG_WS_nginx"&gt;OWASP on Nginx&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-on-ubuntu-14-04"&gt;Digital Ocean - How to secure Nginx on Ubuntu 14.04&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-protect-an-nginx-server-with-fail2ban-on-ubuntu-14-04"&gt;Blocking malicious requests&lt;/a&gt; - In the tutorial we used &lt;code&gt;ufw&lt;/code&gt;, which allows us to &lt;a href="https://www.digitalocean.com/community/tutorials/ufw-essentials-common-firewall-rules-and-commands#block-an-ip-address"&gt;block specific IP addresses&lt;/a&gt; via command line entries. These can be found while analyzing server logs, but a more proactive approach could be using tools like &lt;a href="http://www.fail2ban.org/wiki/index.php/Main_Page"&gt;fail2ban&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.nginx.com/products/nginx-amplify/"&gt;Nginx Monitoring&lt;/a&gt; - In the tutorial, we configuring monitoring for our Droplet's resources, but there tools to monitor Nginx itself. I've used &lt;a href="https://www.nginx.com/products/nginx-amplify/"&gt;Nginx Amplify&lt;/a&gt; before and it works well. There is a free tier. It involves installing an agent on your Droplet which then sends information back to the Nginx Amplify server. You can then view metrics and set alerts from the Nginx Amplify dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-configure-logging-and-log-rotation-in-nginx-on-an-ubuntu-vps#log-rotation"&gt;Nginx Log Rotation&lt;/a&gt; - It's important to keep your server logs tidy. &lt;code&gt;logrotate&lt;/code&gt; is already installed and configured on our Droplet, but if you want to make changes you can do so in the &lt;code&gt;/etc/logrotate.d/nginx&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully this tutorial was useful for learning one of the many ways to setup your own website.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nginx</category>
      <category>serverhardening</category>
    </item>
    <item>
      <title>Part 6 - Deploying a simple web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 21:00:25 +0000</pubDate>
      <link>https://forem.com/franndotexe/part-6-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-12ed</link>
      <guid>https://forem.com/franndotexe/part-6-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-12ed</guid>
      <description>&lt;p&gt;In the previous post we went over using &lt;a href="https://goaccess.io/"&gt;GoAccess&lt;/a&gt; to view server logs. In this part, I'll go over using &lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt; to automatically report errors on the client-side.&lt;/p&gt;

&lt;p&gt;Sentry has a &lt;a href="https://sentry.io/pricing/"&gt;Developer tier&lt;/a&gt; that should meet our needs. As of time of writing this, the Developer tier supports Real-time error tracking, which is the main feature we want. Configuring this for our website will enable the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client-side errors will be logged and stored&lt;/li&gt;
&lt;li&gt;We will be able to view errors and the details surrounding them (such as browser type) &lt;/li&gt;
&lt;li&gt;We will be notified via email when new errors are encountered by users&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Do I need a user consent banner to use this?
&lt;/h3&gt;

&lt;p&gt;Sentry gathers some client-side info to use in tracking errors. If you decide to incorporate Sentry into your website I recommend adding a user consent banner with a Privacy Policy detailing what is done with the user's data. Sentry will help you meet &lt;a href="https://sentry.io/security/"&gt;GDPR compliance&lt;/a&gt;, but you should still notify your users with a Privacy Policy. There are a lot of way to handle this and I am not a lawyer so I will leave it up to you, the reader, to determine what's best for your website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing and configuring Sentry
&lt;/h3&gt;

&lt;p&gt;First, you will need to make an account with Sentry. In this tutorial, we will be using the hosted version of Sentry, but they also have an &lt;a href="https://docs.sentry.io/server/"&gt;on-premise version&lt;/a&gt;. After making an account, during Sentry's 'Onboarding' step, they should ask you to Select a Platform for your application. If you are using the website provided for this tutorial, choose &lt;code&gt;React&lt;/code&gt;. Otherwise, choose the one that best fits your needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H4X92lvs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719312/x6klbrqn854ojg7qgvub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H4X92lvs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719312/x6klbrqn854ojg7qgvub.png" alt="text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, Sentry should display a short guide to installing and connecting the SDK to Sentry in your project. It boils down to this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the package (e.g., &lt;code&gt;npm i @sentry/browser&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Connect the SDK (&lt;code&gt;Sentry.init({dsn:'[your unique url]'}))&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may notice Sentry is 'waiting' for a first event on this 'Onboarding' page. We'll set that up next.&lt;/p&gt;

&lt;p&gt;Go ahead and copy that &lt;code&gt;Sentry.init({dsn:'[your unique url]'})&lt;/code&gt; line above the &lt;code&gt;ReactDOM.render&lt;/code&gt; method in our example project's &lt;code&gt;src/index.js&lt;/code&gt; file. Don't forget to import Sentry &lt;code&gt;import * as Sentry from '@sentry/browser';&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's it! Sentry will now handle any uncaught errors in our website. Let's test it out.&lt;/p&gt;

&lt;p&gt;In our example website, throw a new Error in the &lt;code&gt;src/App.js&lt;/code&gt; file in the first line of &lt;code&gt;componentDidMount()&lt;/code&gt;. Example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;throw new Error('Hi Sentry!')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now reload your website locally. You should see the error on the browser page. If you still have the Sentry 'Onboarding' page open notice how that 'waiting for first event' control has updated and our error event was received. &lt;/p&gt;

&lt;p&gt;You should even see a button &lt;code&gt;Take me to my event&lt;/code&gt; on the Sentry Onboarding page. Go ahead and click that. Take some time to check out the page showing this error. There's lots of great info!&lt;/p&gt;

&lt;h3&gt;
  
  
  Disabling storing of IP Addresses
&lt;/h3&gt;

&lt;p&gt;Ultimately, this is up to you, but minimizing the amount of data we collect is usually a good idea. Unless you have a specific need to collect IP addresses we can turn this off. Click the 'Projects' item in the sidebar and then click the title of the Project you created for this tutorial. Scrolling down, you should see a &lt;code&gt;Data Privacy&lt;/code&gt; section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9ijfhC6B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719324/qhqzgi5cksdwpfnbydui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9ijfhC6B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719324/qhqzgi5cksdwpfnbydui.png" alt="text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turn the &lt;code&gt;Prevent Storing of IP Addresses&lt;/code&gt; option on. Going forwards, IP Addresses will no longer be stored for events. Unless you really need this data, it's a good idea to turn this setting on. If you want to read more about these settings Sentry has &lt;a href="https://docs.sentry.io/data-management/sensitive-data/"&gt;documentation for that&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Email Alerts for new Issues
&lt;/h3&gt;

&lt;p&gt;Sentry is now receiving events from our website, but it would be nice if we were emailed the details of an event the first time it happens. Support for this is built-in to Sentry. To configure it, click the &lt;code&gt;Settings&lt;/code&gt; item in the sidebar. In the &lt;code&gt;Organization&lt;/code&gt; sidebar you now see, click the &lt;code&gt;Projects&lt;/code&gt; item. Now click your Project in the list of Projects. You should be taken to a &lt;code&gt;Project Settings&lt;/code&gt; pane. &lt;/p&gt;

&lt;p&gt;From here, click the &lt;code&gt;Alerts&lt;/code&gt; item in the &lt;code&gt;General Settings&lt;/code&gt; section of the &lt;code&gt;Project&lt;/code&gt; sidebar. You should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V3PTlMs3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719334/b8tdrxtx7hbkfri1cbs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V3PTlMs3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719334/b8tdrxtx7hbkfri1cbs8.png" alt="text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Edit Rule&lt;/code&gt; button in the upper right. In the &lt;code&gt;Edit Alert Rule&lt;/code&gt; page look for the &lt;code&gt;Take these actions&lt;/code&gt; section. In the &lt;code&gt;Select&lt;/code&gt; dropdown, select the &lt;code&gt;Send a notification via {service}&lt;/code&gt; option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DrNNaevD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719342/ypqxfxsfjtvegrbusgvm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DrNNaevD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719342/ypqxfxsfjtvegrbusgvm.png" alt="text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see a new action added, using &lt;code&gt;Mail&lt;/code&gt; as the default, which is what we want in this case. Now click &lt;code&gt;Save Rule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's go back to our website code and modify our &lt;code&gt;Error&lt;/code&gt; so Sentry receives it as a new event. Change the &lt;code&gt;Hi Sentry!&lt;/code&gt; string to some other text (e.g., &lt;code&gt;Mail time!&lt;/code&gt;) and move it to inside &lt;code&gt;render()&lt;/code&gt; in &lt;code&gt;src/App.js&lt;/code&gt; so Sentry does not think this event can be grouped with the previous one. Now reload the page locally to cause the error to happen again. You should receive an email with a summary of the error!&lt;/p&gt;

&lt;p&gt;It's important to note Sentry supports a variety of services to use for alerts rather than just mail. For example, &lt;a href="https://sentry.io/integrations/slack/"&gt;Slack&lt;/a&gt;, if you prefer it to email.&lt;/p&gt;

&lt;p&gt;Great! We have covered the absolute basics of adding Sentry to our website to automatically report errors. This gives us a powerful tool to see issues on our site without requiring users to log issues for us so we are aware of them. Between, Sentry and GoAccess, we have some very useful infrastructure with regards to logging and error analysis.&lt;/p&gt;

&lt;p&gt;Thanks for reading! In the next and final part, we will go over some next steps for you to take.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>sentry</category>
      <category>logging</category>
    </item>
    <item>
      <title>Part 5 - Deploying a simple web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 21:00:16 +0000</pubDate>
      <link>https://forem.com/franndotexe/part-5-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-528l</link>
      <guid>https://forem.com/franndotexe/part-5-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-528l</guid>
      <description>&lt;p&gt;In the previous post, we covered how to configure SSL with Nginx and our site. With that post, general configuration of the site is complete. The remainder of the posts in this tutorial focus on logging and analytics. Specifically server-side logging and error logging. Even if your website is designed to just be a small hobby site it is extremely useful to have the ability to look at access logs and error logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewing Nginx logs
&lt;/h3&gt;

&lt;p&gt;First, we'll focus on viewing server-side access logs. On our Droplet, Nginx is actually already creating these at a default location for us. On the Droplet, if you open up the &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; you should see something around line 38 like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;##
# Logging Settings
##

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Open up the &lt;code&gt;access.log&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nano /var/log/nginx/access.log&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should see timestamps and details of requests made. To view the default configuration I recommend checking out the &lt;a href="http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format"&gt;ngx http log_module docs&lt;/a&gt;. By default the &lt;code&gt;combined&lt;/code&gt; log format is used (you can see this in the module docs linked above). You can also create your own custom format if desired, but for this tutorial we'll stick with the default configuration.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;error.log&lt;/code&gt; won't get much use in our simple single-page application for our tutorial, but errors encountered on the server will be logged here. It's worth noting there are &lt;a href="http://nginx.org/en/docs/ngx_core_module.html#error_log"&gt;several severity levels&lt;/a&gt; and a default is used so at least some information is gathered by default.&lt;/p&gt;

&lt;p&gt;Looking at these logs individually can be useful, but it's also very granular. In many cases, being able to group and organize this information will give us valuable insight into how our site and web server are being used. &lt;code&gt;GoAccess&lt;/code&gt; can provide this type of view with minimal overhead. In fact, we can get a very clean view of this data right in the console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using GoAccess
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://goaccess.io/"&gt;GoAccess&lt;/a&gt; is an &lt;a href="https://github.com/allinurl/goaccess"&gt;open-source&lt;/a&gt; log analyzer and viewer. We'll focus on two features for now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzing logs in the terminal&lt;/li&gt;
&lt;li&gt;Creating real-time HTML report of logs to view in a browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, we need to pull down &lt;code&gt;GoAccess&lt;/code&gt; to our Droplet. According to the &lt;a href="https://goaccess.io/download#distro"&gt;GoAccess docs&lt;/a&gt; since we're using Ubuntu we can run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt-get install goaccess&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, let's try to get a real-time view of our logs in the terminal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo goaccess /var/log/nginx/access.log -c --log-format=COMBINED&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should see something like this appear in the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LzLfTxnI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719158/jxwvequwjh0dypjno7ao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LzLfTxnI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570719158/jxwvequwjh0dypjno7ao.png" alt="text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see &lt;code&gt;goaccess&lt;/code&gt; has populated a Log, Date and Time format since we specified the &lt;code&gt;COMBINED&lt;/code&gt; log format as a command argument. You can hit &lt;code&gt;ENTER&lt;/code&gt; to proceed and should now see a terminal dashboard with lots of great information.&lt;/p&gt;

&lt;p&gt;Note the &lt;code&gt;Log Source&lt;/code&gt; at the top. This is the file we're currently viewing. Based on our Nginx config, this is the active log file. You can load past log files by simply specifying the old log file you want to see on the command line when using &lt;code&gt;goaccess&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also at the top of the terminal you can see info like Total Requests, Bandwidth, Unique Visitors, etc. If you try hitting the site in your local browser again you should see some of these values change. Scrolling down in the terminal you can see a wealth of detail such as Request Files in order of most popular, 404s, Geo Location and Browser as well as other information. It's wonderful that &lt;code&gt;goaccess&lt;/code&gt; offers all this as baked-in functionality that requires such a small amount of setup.&lt;/p&gt;

&lt;p&gt;If you would rather have this info rendered in a web browser, &lt;code&gt;goaccess&lt;/code&gt; can generate an &lt;code&gt;HTML&lt;/code&gt; report of the same data we just saw in the terminal. This can be handy for having a web page that's easily accessible outside of a terminal session. &lt;code&gt;goaccess&lt;/code&gt; can generate a real-time report in &lt;code&gt;HTML&lt;/code&gt; as well, but since we're using &lt;code&gt;SSL&lt;/code&gt; on our site the setup is more involved that I believe is in the scope of this tutorial. If you want to configure that I recommend reading the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://goaccess.io/get-started"&gt;GoAccess Getting Started page&lt;/a&gt; - Check out the 'Real-Time HTML Output' section&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://goaccess.io/faq#html-report"&gt;GoAccess HTML Report FAQ&lt;/a&gt; - Shows more detail on how to configure this with SSL support&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/allinurl/goaccess/issues/683"&gt;This GoAccess GitHub issue&lt;/a&gt; - Has additional info on setting up SSL support&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/"&gt;Restricting Access with HTTP Basic Authentication on Nginx&lt;/a&gt; - If you decide to make this accessible remotely it should probably not be available to everyone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I find that using the terminal support is enough for my purposes, but it's great that this &lt;code&gt;HTML&lt;/code&gt; output is available as an option. As a way to see this we can generate a snapshot of the current log file as an HTML page with the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo goaccess access.log -o report.html --log-format=COMBINED&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will print out a report of the current contents of the &lt;code&gt;access.log&lt;/code&gt; file. This file can then be transferred over to our local machine using your favorite SFTP tool and opened up to see the data in the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE: Be sure to delete this report html file from your Droplet when done with it just to keep things tidy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;goaccess&lt;/code&gt; is a super accessible way to analyze logs from our website. There a multitude of other features &lt;code&gt;goaccess&lt;/code&gt; has that we didn't touch! To find out more about it check out the &lt;a href="https://goaccess.io/features"&gt;GoAccess Features page&lt;/a&gt;. (I'm in no way affiliated with the tool)&lt;/p&gt;

&lt;p&gt;Great! We now have the ability to easily analyze basic server logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Securely handling your logs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;NOTE: Our server logs will be rotated (archived) with the configured &lt;code&gt;rotate&lt;/code&gt; value in &lt;code&gt;/etc/logrotate.d/nginx&lt;/code&gt;. You can change this to your desired value if necessary. This default log rotation configuration will not automatically clean up your logs until 14 days have been logged. This is set with the &lt;code&gt;rotate 14&lt;/code&gt; line in the &lt;code&gt;etc/logrotate.d/nginx&lt;/code&gt; file. With this setting, the oldest log file will be removed to create room for the newest one after 14 have been created (that matching our file name scheme of 'access.log.[number]'&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You should monitor these logs for your website and make sure no sensitive information is exposed. &lt;a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation"&gt;GDPR&lt;/a&gt; has brought some changes to the how certain data is stored and processed that are &lt;a href="https://www.nginx.com/blog/data-masking-user-privacy-nginscript/"&gt;worth&lt;/a&gt; &lt;a href="https://www.ctrl.blog/entry/gdpr-web-server-logs.html"&gt;reading&lt;/a&gt; about. I will leave these as an exercise for the reader as I am not a lawyer and the site you are deploying may have unique requirements.&lt;/p&gt;

&lt;p&gt;You may have thought we would be working with &lt;a href="https://analytics.google.com/analytics/web/"&gt;Google Analytics&lt;/a&gt; or other client-side analytics tools like &lt;a href="https://usefathom.com/"&gt;Fathom&lt;/a&gt; or &lt;a href="https://matomo.org/"&gt;Matomo&lt;/a&gt;. There are definitely great uses for client-side analytics. This tutorial focuses on the basics and I believe basic stats include metrics such as top requests, geo location, browser usage, server response errors and the ability to view access logs for potential malicious behavior. All of this data can be acquired via server logs. You can, of course, supplement these types of logs on the server-side as well as the client-side.&lt;/p&gt;

&lt;p&gt;In part 6 of the tutorial we will see how to add automated error logging to our website with &lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt;. Hope to see you there!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>goaccess</category>
      <category>nginx</category>
    </item>
    <item>
      <title>Part 4 - Deploying a simple, modern web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 21:00:08 +0000</pubDate>
      <link>https://forem.com/franndotexe/part-4-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-2and</link>
      <guid>https://forem.com/franndotexe/part-4-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-2and</guid>
      <description>&lt;p&gt;In the previous post, we configured Nginx and deployed our website. We're now live! However, it's a good idea to add support for accessing our website via &lt;code&gt;HTTPS&lt;/code&gt;. In this post, we'll configure an SSL certificate for our website and setup automatic renewal of the certificate using &lt;code&gt;Certbot&lt;/code&gt; and Let's Encrypt. After that, we'll adjust some more settings in our general Nginx configuration to make things a bit more secure. At the end we'll submit our website to an SSL test at &lt;a href="https://www.ssllabs.com/ssltest/index.html"&gt;SSL Labs&lt;/a&gt; and see how it performs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Let's Encrypt and Certbot
&lt;/h3&gt;

&lt;p&gt;We will need to add the Ubuntu software repository for &lt;code&gt;Certbot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo add-apt-repository ppa:certbot/certbot&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After that, we can install it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt install python-certbot-nginx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;More details about install can be found on the &lt;a href="https://certbot.eff.org/lets-encrypt/ubuntuxenial-nginx.html"&gt;certbot site&lt;/a&gt;;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Certbot&lt;/code&gt; uses the &lt;code&gt;server_name&lt;/code&gt; in all the loaded &lt;code&gt;server&lt;/code&gt; blocks in your Nginx configuration files to find the domain you've specified when generating a certificate. This is because &lt;code&gt;Certbot&lt;/code&gt; will actually automatically insert the necessary lines into our &lt;code&gt;server&lt;/code&gt; block once it finds our domain. To create a certificate for our website:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo certbot --nginx -d yourdomain&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The domain you use must match the &lt;code&gt;server_name&lt;/code&gt; you put in the Nginx &lt;code&gt;conf&lt;/code&gt; file we created earlier. When running &lt;code&gt;certbot&lt;/code&gt; it might prompt you to enter an email address and agree to Terms of Service. Adding your email address can be handy as it enables &lt;code&gt;certbot&lt;/code&gt; to send you emails when your certificate is coming up for renewal. More information can be found on the &lt;a href="https://letsencrypt.org/docs/expiration-emails/"&gt;Let's Encrypt Expiration Emails page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, you should see &lt;code&gt;certbot&lt;/code&gt; ask you if you want to redirect &lt;code&gt;HTTP&lt;/code&gt; traffic to &lt;code&gt;HTTPS&lt;/code&gt;. We want this feature, so option number 2 can be selected. &lt;code&gt;certbot&lt;/code&gt; will go ahead and add the necessary configuration lines to our Nginx &lt;code&gt;conf&lt;/code&gt; file for our website.&lt;/p&gt;

&lt;p&gt;Upon success, &lt;code&gt;certbot&lt;/code&gt; will now show you information about where your certificate and chain exist on our Droplet as well as their expiry date. Our certificate should be loaded in our website configuration so go ahead and hit your website from your local machine again, this time using &lt;code&gt;https://&lt;/code&gt;. You should see the same website as before, this time with &lt;code&gt;https&lt;/code&gt;! Additionally, you can try hitting your site with &lt;code&gt;http&lt;/code&gt; and verify you are redirected to &lt;code&gt;https&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you open up your Nginx &lt;code&gt;conf&lt;/code&gt; file you'll see several lines have been added regarding our choices to redirect &lt;code&gt;http&lt;/code&gt; to &lt;code&gt;https&lt;/code&gt;, our &lt;code&gt;ssl_certificate&lt;/code&gt; entries, a listen directive for port 443 among other entries. &lt;/p&gt;

&lt;p&gt;You'll also notice a line &lt;code&gt;include /etc/letsencrypt/options-ssl-nginx.conf&lt;/code&gt;. We'll take a look at that in just a moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing out certificate renewal
&lt;/h3&gt;

&lt;p&gt;It's very important our certificate not expire and &lt;code&gt;certbot&lt;/code&gt; has configured automatic renewal for us. We can test renewal out with the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo certbot renew --dry-run&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will run the process for renewing a certificate, but not actually renew our current production certificate. If any failures happen during the renewal process they will be listed in the output of this command.&lt;/p&gt;

&lt;p&gt;If want to see more information about the automated renewal you can run a command to print out info about it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo systemctl status certbot.timer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Will show details about the &lt;code&gt;certbot.timer&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo systemctl list-timers&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Will show information about scheduled services. You should see &lt;code&gt;certbot.timer&lt;/code&gt; listed under the &lt;code&gt;UNIT&lt;/code&gt; column on the far right of the printed output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analyzing our setup via SSL Labs
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.ssllabs.com/ssltest/"&gt;SSL Labs&lt;/a&gt; is a useful site for getting an over-arching grade on the security of your site. Here, you can enter your site url (e.g., &lt;a href="https://yourdomain"&gt;https://yourdomain&lt;/a&gt;) and it will give you a grade. Running your domain against our site now should produce a &lt;code&gt;A&lt;/code&gt;. There are a few small tweaks we can make to our Nginx configuration to get that up to an &lt;code&gt;A+&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, in our Droplet terminal open up the file &lt;code&gt;/etc/letsencrypt/options-ssl-nginx.conf&lt;/code&gt; that I mentioned earlier. You should see the following line around line 10:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssl_protocols TLSv1 TLSv1.1 TLSv1.2;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TLSv1&lt;/code&gt; section means our Nginx config supports &lt;code&gt;TLSv1&lt;/code&gt;. If you take a look at your SSL Labs report you can see several of the yellow-orange lines are due to supporting TLSv1. Let's remove that setting. So this line should like this now:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssl_protocols TLSv1.1 TLSv1.2;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we're going to add a line right below the &lt;code&gt;ssl_prefer_server_ciphers&lt;/code&gt; line. Add the following line:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This enables &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security"&gt;HSTS&lt;/a&gt; which, according to the MDN docs "lets a web site tell browsers that it should only be accessed using HTTPS, instead of using HTTP." More info can be found in the link.&lt;/p&gt;

&lt;p&gt;Now that we've adjusted these two settings, save this file.&lt;/p&gt;

&lt;p&gt;Test your Nginx configuration to ensure there are not any errors:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Reload Nginx:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo systemctl nginx restart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once reloaded, go back to your SSL Labs report. Scroll to the top of the page and click the &lt;code&gt;Clear cache&lt;/code&gt; link. This will cause SSL Labs to restart testing your site fresh. Otherwise it'll return the previous results since we just had our site analyzed a moment ago.&lt;/p&gt;

&lt;p&gt;After it's done testing, you should see an &lt;code&gt;A+&lt;/code&gt;. Notice the &lt;code&gt;TLSv1&lt;/code&gt; warnings are gone and &lt;code&gt;TLSv1&lt;/code&gt; is now marked &lt;code&gt;No&lt;/code&gt; in the &lt;code&gt;Configuration&lt;/code&gt; -&amp;gt; &lt;code&gt;Protocols&lt;/code&gt; section of the report. Also, near the bottom you can see that &lt;code&gt;Strict Transport Security (HSTS)&lt;/code&gt; is green and marked as enabled. Hooray!&lt;/p&gt;

&lt;p&gt;There is definitely more you can do regarding securing your site configuration, which I'll touch on at the end of this tutorial.&lt;/p&gt;

&lt;p&gt;Our site is now accessible via &lt;code&gt;HTTPS&lt;/code&gt; and our SSL certificate is automatically renewed for us!&lt;/p&gt;

&lt;p&gt;Next, we'll see how we can access and view server logs for our site on our Droplet using &lt;code&gt;GoAccess&lt;/code&gt; in Part 5. &lt;/p&gt;

&lt;p&gt;Thanks for reading so far!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nginx</category>
      <category>certbot</category>
    </item>
    <item>
      <title>Part 0 - Deploying a simple web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 20:59:03 +0000</pubDate>
      <link>https://forem.com/franndotexe/deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-54kf</link>
      <guid>https://forem.com/franndotexe/deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-54kf</guid>
      <description>&lt;p&gt;Over the past year or so I find myself learning more and more of frontend development and ops/deployment of web sites. There are so many different ways to deploy a web site nowadays it can be overwhelming. &lt;/p&gt;

&lt;p&gt;Many examples assume prior knowledge of DevOps tooling of some sort. Docker and/or containers are examples that I commonly found. Sure, there are services that abstract large portions of this away, but what if I just wanted learn about how a basic website might be deployed? Reading many of these examples, I would need to first read up on Docker and containers before actually getting to learn how to deploy and maintain a website. My website doesn't need to be a globally scaling production monster and doing this without the use of Docker and/or containers is still viable. I found it rewarding to be able to configure the whole infrastructure, not that there's anything wrong with using any of the amazing services that help with this.&lt;/p&gt;

&lt;p&gt;In this tutorial I want to focus on taking a very simple web app from a dev machine to a publically accessible domain on a web server. The tutorial will not use Docker or containers directly to serve or ship the website itself, as I want to focus on the basics of setting a web server up. I do understand there are non-performance benefits related to using containers when setting a web server up, but this tutorial is aimed at someone just dipping their feet into setting up a website who also is interested in configuring it themselves instead of using a service such as Zeit Now, Netlify, etc.&lt;/p&gt;

&lt;p&gt;Below is what this tutorial will cover. The idea is to break these out into reusable pieces that you can hopefully take with you to other projects that may be more complex.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/franndotexe/part-1-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-34il"&gt;Part 1&lt;/a&gt;
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Using a React app that has been created via &lt;code&gt;create-react-app&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;Feel free to sync the &lt;a href="https://github.com/frannsoft/pokecard-example"&gt;repository&lt;/a&gt; or bring your own website! You do not have to use this tool to make your web site. The tutorial will focus on running a tiny Single-Page Application, but the website you use in this tutorial does not need to be a Single-Page Application. If you're comfy using your favorite framework or no framework, by all means go right ahead. Just know the process of getting it running on our web server might be slightly different.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/franndotexe/part-2-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-32p"&gt;Part 2&lt;/a&gt;
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Creating a \$5 &lt;a href="https://www.digitalocean.com/"&gt;Digital Ocean&lt;/a&gt; Droplet
&lt;/h5&gt;

&lt;p&gt;We'll be configuring our web server on a $5/month Digital Ocean Droplet. These are extremely handy and straightforward to configure and I'll go through the process of doing so. We will be using Ubuntu on our Droplet. Required knowledge of Linux is minimal. If you would like, Digital Ocean offers a referral program. Use &lt;a href="https://m.do.co/c/660558d956e2"&gt;this referral link&lt;/a&gt; to create your account and Digital Ocean will give you get $50 in credit over 30 days. More details on their referral program can be found &lt;a href="https://www.digitalocean.com/referral-program/"&gt;on their site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, we will configure Digital Ocean Droplet monitoring to enable alerts when our Droplet's resources are over a threshold for a certain period of time. We want to monitor the Droplet's resource availability itself and Digital Ocean offers tooling to do so with minimal configuration necessary. In this section, we'll setup email alerts based on memory and CPU usage of our Droplet.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/franndotexe/part-3-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-24p"&gt;Part 3&lt;/a&gt;
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Configuring a domain to point to Digital Ocean nameservers and deploying our website
&lt;/h5&gt;

&lt;p&gt;In order to serve up a memorable website you'll need a domain name. This is necessary to route users to our Droplet when querying for our website on the interwebs.&lt;/p&gt;

&lt;p&gt;We will also configure &lt;a href="https://www.nginx.com/"&gt;Nginx&lt;/a&gt; on our Droplet. Nginx is an popular, &lt;a href="https://github.com/nginx/nginx"&gt;open-source&lt;/a&gt; application that has many uses. We will be using it as our web server. This will allow us to route users to our site and serve our content when they hit the Droplet as well as support SSL for our site using a generated certificate.&lt;/p&gt;

&lt;p&gt;Finally we will push our built website to the Droplet. We need to actually put the files from our dev machine on our Droplet. Here's where we can first test if our site is externally accessible from our domain!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/franndotexe/part-4-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-2and"&gt;Part 4&lt;/a&gt;
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Generating an SSL certificate so our website is served over https
&lt;/h5&gt;

&lt;p&gt;Let's Encrypt and Certbot have made it very easy to so in a reliable way. We will also run our site through Qualys &lt;a href="https://www.ssllabs.com/ssltest/"&gt;SSL Labs&lt;/a&gt; analysis tool to verify our configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/franndotexe/part-5-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-528l"&gt;Part 5&lt;/a&gt;
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Install GoAccess and view real-time or historical server logs
&lt;/h5&gt;

&lt;p&gt;I use this to browse Nginx logs on my servers. It's lightweight, free, and console-based. A wonderful tool. It's also not limited to viewing just Nginx logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/franndotexe/part-6-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-12ed"&gt;Part 6&lt;/a&gt;
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Setup Sentry for error logging
&lt;/h5&gt;

&lt;p&gt;Logging errors automatically is very important for debugging and feedback purposes. Sentry is an incredible tool with a useful free tier that we can use. We'll go over adding it to our website and generate some test errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/franndotexe/part-7-deploying-a-simple-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-1fib"&gt;Part 7&lt;/a&gt;
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Conclude with a few potential next steps now that your website is up and running. Server hardening, Nginx config tools, GDPR, Nginx amplify, banning IP addresses.
&lt;/h5&gt;

&lt;p&gt;Whew! There's a lot to deploying a website. It's no wonder there are more and more services dedicated to making it easier for developers. However, I hope you'll find that going from local hobby website to deployed hobby website is not that bad. Additionally, you should be able to take this knowledge with you for other projects that may not be limited to just a basic website.&lt;/p&gt;

&lt;p&gt;Will your server be a highly-optimized, globally scaling beast after following this tutorial? No. However, you will be able to go quite far with this setup and get a lot of great insight from the various logging and monitoring services we'll be configuring.&lt;/p&gt;

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

</description>
      <category>webdev</category>
      <category>digitalocean</category>
      <category>webserver</category>
    </item>
    <item>
      <title>Part 3 - Deploying a simple web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 20:47:17 +0000</pubDate>
      <link>https://forem.com/franndotexe/part-3-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-24p</link>
      <guid>https://forem.com/franndotexe/part-3-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-24p</guid>
      <description>&lt;p&gt;In the previous post, we configured our Droplet on Digital Ocean. Now that we have our Droplet up and running we need to configure DNS for our domain and install software that will help us serve our website to users under a recognizable name (our domain name).&lt;/p&gt;

&lt;p&gt;In this post, we will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuring DNS for our domain so visitors are routed to our Droplet&lt;/li&gt;
&lt;li&gt;Installing Nginx&lt;/li&gt;
&lt;li&gt;Adjusting our Firewall to handle website traffic&lt;/li&gt;
&lt;li&gt;Creating an Nginx configuration file for our website&lt;/li&gt;
&lt;li&gt;Deploying a production build of our website to the Droplet&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuring DNS
&lt;/h3&gt;

&lt;p&gt;In order to configure DNS for our domain, you will need a domain name. If you already have one to use for this tutorial, great! If not, you can acquire one from your favorite domain registrar. I tend to use &lt;a href="https://domains.google.com"&gt;domains.google&lt;/a&gt;, but have also used &lt;a href="https://www.namecheap.com/"&gt;namecheap&lt;/a&gt; in the past. Digital Ocean is not a domain name registrar, but they do offer a DNS hosting service that we will be using.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE: Keep in mind, there's more than one way to complete this part of setting up a domain. We'll be leveraging Digital Ocean as much as we can in this tutorial, but there are other ways of setting up DNS. Use whichever you prefer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After acquiring your domain we want to add a Domain to our Digital Ocean account. This will allow us to manage records for the domain within Digital Ocean (e.g., A, AAAA, CNAME records). In the &lt;code&gt;Manage&lt;/code&gt; sidebar section on the Digital Ocean page, click &lt;code&gt;Networking&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;You should now see a page that allows you to &lt;code&gt;Add a domain&lt;/code&gt;. Add the domain you acquired earlier. For example, if the domain you acquired was frannsoft.com you would enter 'frannsoft.com', no quotes. Next, click &lt;code&gt;Add Domain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your Domain entry will be created and you should be taken to a page showcasing records for this domain. By default, three NS records will be automatically created, pointing to Digital Ocean's nameservers. You should also see a &lt;code&gt;Create new Record&lt;/code&gt; section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MdiujnLS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/do_createrecord.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MdiujnLS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/do_createrecord.png" alt=""&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Let's add one more record for now. We will add an &lt;a href="https://support.dnsimple.com/articles/a-record/"&gt;&lt;code&gt;A&lt;/code&gt; record&lt;/a&gt; which will provide Digital Ocean's Nameservers with an IP Address for our domain. In the &lt;code&gt;Hostname&lt;/code&gt; input field, enter &lt;code&gt;@&lt;/code&gt;. This means your root domain name will be used (e.g., example.com). &lt;/p&gt;

&lt;p&gt;Next, click in the &lt;code&gt;Will Direct To&lt;/code&gt; input field. In the list of Droplets that appears, select the one created for this tutorial. This is how we specify where the request should go. The &lt;code&gt;TTL&lt;/code&gt; should be defaulted to &lt;code&gt;3600&lt;/code&gt;, which is fine. Click the &lt;code&gt;Create Record&lt;/code&gt; button and you should see a new &lt;code&gt;A&lt;/code&gt; entry in the &lt;code&gt;DNS records&lt;/code&gt; section of the page. Your root domain should be listed with the IP address of your Droplet.&lt;/p&gt;

&lt;p&gt;Now that we have configured our Domain on Digital Ocean we need to make sure our DNS is correct on the domain registrar for our domain. Digital Ocean has a &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-point-to-digitalocean-nameservers-from-common-domain-registrars"&gt;guide&lt;/a&gt; that offers walkthroughs on pointing to Digital Ocean Nameservers from some of the more popular Domain Registrars. If you're using &lt;a href="https://domains.google.com"&gt;domains.google&lt;/a&gt; you simply need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the &lt;code&gt;DNS&lt;/code&gt; sidebar option for your domain in &lt;a href="https://domains.google.com"&gt;domains.google&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click the &lt;code&gt;Use custom name servers&lt;/code&gt; option in the &lt;code&gt;Name servers&lt;/code&gt; pane&lt;/li&gt;
&lt;li&gt;Enter Digital Ocean's nameservers in the input fields. These can be found in the &lt;a href="https://www.digitalocean.com/docs/networking/dns/overview/#features"&gt;Digital Ocean networking overview&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And DNS configuration is complete. One important thing to keep in mind is that DNS changes can take a while to propogate. This can be a bit confusing as you might have made these changes and moved forward with the rest of your site configuration only to hit DNS resolution issues down the line. You can track DNS change propogation through sites like &lt;a href="https://dnschecker.org/"&gt;dnschecker.org&lt;/a&gt; (I am not affiliated with them). There are definitely other sites that perform the same checks. I would recommend waiting until your DNS changes have been propogated in your region before moving forward, just to help avoid any issues once we're ready to test our website later on in the tutorial.&lt;/p&gt;

&lt;p&gt;That being said, we're done configuring DNS!&lt;/p&gt;

&lt;h3&gt;
  
  
  Opening ports for web traffic
&lt;/h3&gt;

&lt;p&gt;Next, we'll be installing and configuring &lt;a href="https://www.nginx.com/"&gt;Nginx&lt;/a&gt; so our site is actually served to users upon request.&lt;/p&gt;

&lt;p&gt;Jump back to our Droplet and switch to the user &lt;code&gt;webadmin&lt;/code&gt; we created earlier:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;su webadmin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To install Nginx we'll run the following commands:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt update&lt;/code&gt;&lt;br&gt;
&lt;code&gt;sudo apt install nginx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the first time we're using &lt;code&gt;apt&lt;/code&gt; so updating our packages to have the most recent listings is a good idea. The second command will install Nginx on our Droplet. After installing Nginx we need to update our firewalls so that common web traffic ports are allowed. Nginx has a few profiles that are compatible with &lt;code&gt;ufw&lt;/code&gt; for doing this. To see the profile run the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo ufw app list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Four application profiles should be listed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx Full&lt;/li&gt;
&lt;li&gt;Nginx HTTP&lt;/li&gt;
&lt;li&gt;Nginx HTTPS&lt;/li&gt;
&lt;li&gt;OpenSSH&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have already added the &lt;code&gt;OpenSSH&lt;/code&gt; profile. Now we want to add the &lt;code&gt;Nginx Full&lt;/code&gt; profile via the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo ufw allow 'Nginx Full'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This opens ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt;. To confirm the expected ports were opened you can run the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo ufw status&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bkSZKOxB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/do_ufw_nginx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bkSZKOxB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/do_ufw_nginx.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we check to see if Nginx is running:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo systemctl status nginx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should see a response with &lt;code&gt;nginx.service&lt;/code&gt; and and &lt;code&gt;Active&lt;/code&gt; property that is set to &lt;code&gt;active (running)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another way we can test if Nginx is working is to hit our Droplet's IP address from our browser on our local machine. Example: &lt;code&gt;http://[droplet IP address]&lt;/code&gt;. You should see a page stating:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Welcome to nginx!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If so, nginx is now running! Hit &lt;code&gt;CTRL+C&lt;/code&gt; to exit the output in our terminal session from our previous command.&lt;/p&gt;

&lt;p&gt;We have nginx running, but it has no configuration details about our website yet. We will configure that next.&lt;/p&gt;
&lt;h3&gt;
  
  
  Nginx configuration for our site
&lt;/h3&gt;

&lt;p&gt;Nginx uses configuration files sometimes called &lt;code&gt;conf&lt;/code&gt; files. These files typically house information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server name of the site. In our case this is the domain name&lt;/li&gt;
&lt;li&gt;How to respond to requests for specific file types (e.g., images, javascript files, html files, etc)&lt;/li&gt;
&lt;li&gt;How to respond to requests for specific endpoints (e.g., /admin, /home, etc)&lt;/li&gt;
&lt;li&gt;SSL certificate information&lt;/li&gt;
&lt;li&gt;gzip configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...And more. We'll be setting up a few of these in the &lt;code&gt;conf&lt;/code&gt; file for our website.&lt;/p&gt;

&lt;p&gt;First, navigate to &lt;code&gt;/etc/nginx/sites-available&lt;/code&gt;. Create a new file with a name the same as your domain. For example, if the domain you've been working with so far is &lt;code&gt;example.com&lt;/code&gt;, the &lt;code&gt;conf&lt;/code&gt; file would be named &lt;code&gt;example.com&lt;/code&gt;. If you are using a subdomain the file would be named something like &lt;code&gt;mysubdomain.example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Technically, this naming convention is not required by Nginx, but it helps keep our files informative without needing to open them to look through the contents as much. If you're configuring multiple sites on the same Droplet, the naming of these files gets even more important to help keep things clear. You can also split these files up to avoid repeat configuration blocks when multiple sites use the same configuration. &lt;/p&gt;

&lt;p&gt;To create the file run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nano yourdomain&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will open &lt;code&gt;nano&lt;/code&gt; with your new file. In the file we want add a few basic items for now (we'll add more later).&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.nginx.com/resources/wiki/start/topics/examples/server_blocks/"&gt;server block&lt;/a&gt; is an Nginx term for a group that uses the configured &lt;code&gt;server_name&lt;/code&gt; and listen directives to bind to tcp sockets on the machine. Here's what the file contents should look like for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    # We will add https support in just a bit. 
    # For now, this will support http.
    listen 80; 

    # Use your domain here. 
    # This is what Nginx will use when attempting to find 
    # the configuration for the incoming user's request.
    server_name yourdomain; 

    # Use your domain here. 
    # This is the root directory Nginx will serve data when responding to requests.
    root /var/www/html/yourdomain; 

    # If the client is request just the root domain 'e.g., example.com',
    # serve the index.html page for the site.
    location / {
        try_files $uri $uri/ /index.html; 
    }

    # We do not want to cache the index.html file for our React Single-Page Application.
    # If we do, when we update the site, users might still 
    # see old info and links of they have a stale cache.
    # More info here - https://www.nginx.com/blog/nginx-caching-guide/
    location /index.html {
        add_header Cache-Control 'no-store, no-cache';
    }

    # gzip configuration. We want to compress our responses.
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once you have added that to the file save the contents and exit the file. &lt;/p&gt;

&lt;p&gt;Finally, we'll create a symbolic link from this file to the &lt;code&gt;sites-enabled&lt;/code&gt; directory. The &lt;code&gt;sites-enabled&lt;/code&gt; directory is the directory Nginx uses as a 'live sites' folder which Nginx reads on start. We want to avoid having to duplicate our physical &lt;code&gt;conf&lt;/code&gt; file so a symbolic link is useful here. To create the symbolic link run the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/[your conf file] /etc/nginx/sites-enabled/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can now test that your configuration is correct in Nginx with the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will report any errors with your configuration. If there are no problems, you can now restart Nginx so it will load your changes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo systemctl restart nginx&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the physical directory for our site
&lt;/h3&gt;

&lt;p&gt;Now we will create the folder where our website will be physically deployed. This should match the &lt;code&gt;root&lt;/code&gt; value we specified in our Nginx &lt;code&gt;conf&lt;/code&gt; file above. Change to &lt;code&gt;/var/www/html&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd /var/www/html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a new directory that will contain our website contents using your domain instead of the example one given below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo mkdir yourdomain&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lastly, change the owner of the directory we just created to the &lt;code&gt;webadmin&lt;/code&gt; user:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo chown webadmin:webdamin your-folder-name&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying our website to our Droplet
&lt;/h3&gt;

&lt;p&gt;It's time to push our website to our Droplet! In this section we will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building a production version of our site locally&lt;/li&gt;
&lt;li&gt;Pushing it to our Droplet at the &lt;code&gt;root&lt;/code&gt; location in our &lt;code&gt;conf&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are not using &lt;code&gt;create-react-app&lt;/code&gt; these steps might be slightly different for you. The end result is the same, however. Get the production website files to the &lt;code&gt;root&lt;/code&gt; location in our &lt;code&gt;conf&lt;/code&gt; file for our website on our Droplet.&lt;/p&gt;

&lt;p&gt;To create a production build of our &lt;code&gt;create-react-app&lt;/code&gt; project we'll run the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should now see a &lt;code&gt;build&lt;/code&gt; directory in your local project structure. The contents of this folder are what we want to copy to our Droplet.&lt;/p&gt;

&lt;p&gt;In order to copy files from our local machine to our Droplet we'll use SFTP. I'm on Windows and will be using &lt;a href="https://winscp.net/eng/index.php"&gt;WinSCP&lt;/a&gt; to do this, but you can accomplish it using any SFTP client you like. Digital Ocean also has a guide to do this &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-sftp-to-securely-transfer-files-with-a-remote-server"&gt;entirely within the terminal&lt;/a&gt; if that suits your needs better. If you are using WinSCP they &lt;a href="https://winscp.net/eng/docs/guide_public_key"&gt;have a guide&lt;/a&gt; to configuring SSH key usage.&lt;/p&gt;

&lt;p&gt;Next, we can copy the contents of the &lt;code&gt;build&lt;/code&gt; folder on our local machine to the &lt;code&gt;root&lt;/code&gt; folder we just created on our Droplet. Take everything inside the &lt;code&gt;build&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Now, if we hit our domain again from our local browser we should see our website!&lt;/p&gt;

&lt;p&gt;Our site is now up and running. Let's configure &lt;code&gt;SSL&lt;/code&gt; so that the site can be accessed via &lt;code&gt;HTTPS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll do that in part 4.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nginx</category>
      <category>webserver</category>
    </item>
    <item>
      <title>Part 2 - Deploying a simple web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 20:46:06 +0000</pubDate>
      <link>https://forem.com/franndotexe/part-2-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-32p</link>
      <guid>https://forem.com/franndotexe/part-2-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-32p</guid>
      <description>&lt;p&gt;In the previous post, we setup our website using this &lt;a href="https://github.com/Frannsoft/pokecard-example"&gt;repository&lt;/a&gt;. If you want, you should be able to follow these posts using your own website. Just know the setup on our web server might differ slightly if you do so.&lt;/p&gt;

&lt;p&gt;Now we want to configure our web server. Here is what we want to have configured by the end of this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Digital Ocean \$5 (for a full month's use) Droplet&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;An Nginx configuration file for our website&lt;/li&gt;
&lt;li&gt;The domain for our website pointing to Digital Ocean nameservers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Keep in mind&lt;/strong&gt; This will create a Droplet that will charge your account a maximum of \$5/month. Charges for the Droplet will stop accruing if you Destroy the Droplet. For more info, check the &lt;a href="https://www.digitalocean.com/pricing/#FAQs"&gt;Digital Ocean pricing FAQ&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also Keep in mind&lt;/strong&gt; - If you would like, Digital Ocean offers a referral program. Use my &lt;a href="https://m.do.co/c/660558d956e2"&gt;this referral link&lt;/a&gt; to create your account and Digital Ocean will give you get \$50 in credit over 30 days if you add a valid payment method with your Digital Ocean account (ah yes, the catch). More details on their referral program can be found &lt;a href="https://www.digitalocean.com/referral-program/"&gt;on their site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First off, we'll create a \$5 Digital Ocean Droplet. I like using Digital Ocean because UX for maintaining these Droplets is super easy to use, the prices are good for the specs on what you buy and they have all the features I've wanted for my projects so far. It's personal preference, of course. There are other great platforms that offer a similar featureset.&lt;/p&gt;

&lt;p&gt;Go ahead and create a Digital Ocean account. Once done and logged into your account you may need to a create a &lt;code&gt;Project&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vmePf_DL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676831/ebszkvew2e55d3cl1flr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vmePf_DL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676831/ebszkvew2e55d3cl1flr.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add any Name and purpose and then click &lt;code&gt;Create Project&lt;/code&gt;. You should be taken to the 'home page' for that Project. A &lt;code&gt;Project&lt;/code&gt; is like a grouping of resources in Digital Ocean.&lt;/p&gt;

&lt;p&gt;In the upper right corner of the page you should see a green &lt;code&gt;Create&lt;/code&gt; button. Go ahead and click that, then select &lt;code&gt;Droplets&lt;/code&gt; and you should be taken to a page with a header of &lt;code&gt;Create Droplets&lt;/code&gt;. Here is where we'll configure our machine.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;Choose an Image&lt;/code&gt; section we want to choose an Ubuntu 18.04.3 (LTS) x64 image. At the time of writing, this is the latest available version of Ubuntu. Feel free to take a later version if available:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0QVaJC6c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676844/hthdggiwtptfseqlmmoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0QVaJC6c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676844/hthdggiwtptfseqlmmoa.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our plan will be the &lt;code&gt;Standard&lt;/code&gt; plan. After selecting the &lt;code&gt;Standard&lt;/code&gt; option under the &lt;code&gt;Starter&lt;/code&gt; Plan section, click the arrows to move to the left so you can see the &lt;code&gt;$5/mo&lt;/code&gt; option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ls98kVEZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676859/m0q0ebytmuk7siowhggc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ls98kVEZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676859/m0q0ebytmuk7siowhggc.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;Choose a datacenter region&lt;/code&gt; choose the region that you think the majority of your visitors will reside. I usually just default to either New York or San Francisco since the majority of my projects see visitors in the United States. Choosing a subregion number is just fine-tuning your region selection. For our example app it's fine to leave this at the pre-selected option. For me, this is &lt;code&gt;New York&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next is Additional Options. Check the &lt;code&gt;Monitoring&lt;/code&gt; box. This will come into play later, once our website is up and running. Checking this box will add in a monitoring agent to our Droplet, letting it communicate cpu usage, memory usage, metrics collection and few other system stats back to Digital Ocean, allowing us to easily configure alerts on those stats.&lt;/p&gt;

&lt;h5&gt;
  
  
  Authentication
&lt;/h5&gt;

&lt;p&gt;For the Authentication section, choose SSH and add your Key. Clicking the &lt;code&gt;New Key&lt;/code&gt; button will bring up a helpful dialog to walk you through creating a Key if you're less familiar with SSH. After adding your SSH Key content and clicking the &lt;code&gt;Add SSH Key&lt;/code&gt; in the dialog on the screen you should see your Key added on the Droplet configuration page we've been working with so far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's important to note that by using an SSH key while configuring our Droplet, Digital Ocean will configure our Droplet to &lt;em&gt;not&lt;/em&gt; allow logins via password (only by SSH key).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can see a bit more info on this by hovering of the &lt;code&gt;?&lt;/code&gt; next to the 'Authentication' section header:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A-to-wva--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676869/hbmen5ly0sjpg4dfejvp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A-to-wva--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676869/hbmen5ly0sjpg4dfejvp.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost done!&lt;/p&gt;

&lt;p&gt;Now you'll see a section dedicated to some final steps in our configuration.&lt;/p&gt;

&lt;p&gt;How many Droplets? We just need 1&lt;/p&gt;

&lt;p&gt;Choose a hostname? This is &lt;strong&gt;not&lt;/strong&gt; our domain hostname or our website name. This is the name of the Droplet itself according to Digital Ocean. The default generated name will consist of some our Droplet's current configuration. If this works for you, great! Otherwise, feel free to name it what you find to be most helpful. Digital Ocean will show you your configuration for each Droplet even if you use a name here that doesn't contain that info.&lt;/p&gt;

&lt;p&gt;Add tags: Here you can add tags to help categorize and/or organize your Droplet. We'll add &lt;code&gt;nginx&lt;/code&gt; and &lt;code&gt;web server&lt;/code&gt;, but feel free to add your own.&lt;/p&gt;

&lt;p&gt;Select Project: We'll just assign our Droplet to the Project we created earlier.&lt;/p&gt;

&lt;p&gt;Here's an example of this portion of the Droplet configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AcD08At---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676881/gou725sbnlx2jtsvwoto.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AcD08At---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676881/gou725sbnlx2jtsvwoto.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add backups: Since this is just an example Droplet we won't enable any backups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep in mind&lt;/strong&gt; - This will create a Droplet that will charge your account a maximum of \$5/month. Charges for the Droplet will stop accruing if you Destroy the Droplet. For more info, check the &lt;a href="https://www.digitalocean.com/pricing/#FAQs"&gt;Digital Ocean pricing FAQ&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you can click the green &lt;code&gt;Create Droplet&lt;/code&gt; button and Digital Ocean will get to work! Navigating to the Project we assigned our Droplet to should show our new Droplet in the list of Droplets for the Project. If you click on the Droplet you'll be taken to details about that Droplet including taking manual backups, adjusting configuration details and/or destroying the Droplet.&lt;/p&gt;

&lt;p&gt;Let's SSH into the terminal. If you're newer to SSH, Digital Ocean provides a nice tutorial on &lt;a href="https://www.digitalocean.com/docs/droplets/how-to/connect-with-ssh/openssh/"&gt;How to Connect to your Droplet with OpenSSH&lt;/a&gt;. I'm on Windows and tend to use PuTTY. Digital Ocean provides a guide on &lt;a href="https://www.digitalocean.com/docs/droplets/how-to/connect-with-ssh/putty/"&gt;How to Connect to your Droplet with PuTTY on Windows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After connecting, you can login as &lt;code&gt;root&lt;/code&gt;. We'll now create a new user and perform the rest of our tasks on the Droplet using that user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a User
&lt;/h3&gt;

&lt;p&gt;This can be done with the following command: &lt;code&gt;adduser [your new username]&lt;/code&gt;. Ours will be &lt;code&gt;adduser webadmin&lt;/code&gt;. You can populate the Full Name, Room Number info you want, but we'll just leave it blank.&lt;/p&gt;

&lt;p&gt;Next, we want to grant administrative privileges to this user so they can perform admin tasks when necessary without us needing to switch back and forth to &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This can be done with the following command: &lt;code&gt;usermod -aG sudo webadmin&lt;/code&gt;. Now, we can perform actions with superuser privileges when we enter the password for this user and prefix our commands with &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Switch to the user:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;su webadmin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's create an SSH Key for this user. This will allow us to SSH in as this user so we do not have to login as &lt;code&gt;root&lt;/code&gt;. We can then disable &lt;code&gt;root&lt;/code&gt; access even via SSH. &lt;/p&gt;

&lt;p&gt;For creating and configuring the SSH Key for our &lt;code&gt;webadmin&lt;/code&gt; user I recommend following &lt;code&gt;Step 4 (using 'Option 2')&lt;/code&gt; of &lt;a href="https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-16-04#step-four-%E2%80%94-add-public-key-authentication-(recommended)"&gt;this Digital Ocean tutorial&lt;/a&gt;. If you're on Windows like me and using PuTTY to login to your Droplet they have a &lt;a href="https://www.ssh.com/ssh/putty/windows/puttygen"&gt;guide&lt;/a&gt; to using their SSH key generator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disabling root access
&lt;/h3&gt;

&lt;p&gt;We no longer need to login as &lt;code&gt;root&lt;/code&gt; to be able to get into our Droplet so we can disable the ability to login as &lt;code&gt;root&lt;/code&gt; as a security measure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE: Doing this will disable &lt;code&gt;root&lt;/code&gt; access. If you have misconfigured your access for the &lt;code&gt;webadmin&lt;/code&gt; user we created above you might lock yourself out of your Droplet. It's a good idea to test that you can SSH into the Droplet with your &lt;code&gt;webadmin&lt;/code&gt; user prior to doing this.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To do this, we will modify the &lt;code&gt;sshd_config&lt;/code&gt; file to not allow &lt;code&gt;root&lt;/code&gt; login:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nano /etc/ssh/sshd_config&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Will open this file. Around line 32 there should be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PermitRootLogin yes&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Change this line to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PermitRootLogin no&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lastly, we need to restart the &lt;code&gt;ssh&lt;/code&gt; service in order for our changes to take effect:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo service ssh restart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Great, our server is a bit more secure now with regards to access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firewall configuration
&lt;/h3&gt;

&lt;p&gt;Now let's setup basic Firewall rules. On our Droplet, we will be using &lt;code&gt;ufw&lt;/code&gt; and we want to allow SSH connections.&lt;/p&gt;

&lt;p&gt;First, enable &lt;code&gt;ufw&lt;/code&gt; with the command &lt;code&gt;sudo ufw enable&lt;/code&gt;. You should receive a response stating &lt;code&gt;Firewall is active and enabled on system startup&lt;/code&gt;.&lt;br&gt;
Second, add the &lt;code&gt;OpenSSH&lt;/code&gt; profile. This will add common &lt;code&gt;OpenSSH&lt;/code&gt; settings to our Firewall and make it less tedious for us to set these up ourselves. This is done using the command &lt;code&gt;sudo ufw allow OpenSSH&lt;/code&gt;. You should see a response of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rule added
Rule added (v6)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now enter the command &lt;code&gt;sudo ufw status&lt;/code&gt;. You should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XUnBvwho--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676897/pcdbwhhuf86p2fnzrg3z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XUnBvwho--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676897/pcdbwhhuf86p2fnzrg3z.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do, great! Basic configuration of our server is complete!&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Email Alerts for Droplet Usage
&lt;/h3&gt;

&lt;p&gt;When we checked the &lt;code&gt;Monitoring&lt;/code&gt; box earlier, our Droplet was created with the Digital Ocean monitoring agent already installed and configured. It's important to be notified when your Droplet might soon run out of resources or is running under heavy usage for unexpectedly long amounts of time. Digital Ocean allows us to create Alert Policies and integrate those alert notifications with email or Slack In this tutorial we will choose to use Email.&lt;/p&gt;

&lt;p&gt;On the Digital Ocean page that lists your Droplets, click on the Droplet you are using for this tutorial and in the center you should see blue text stating &lt;code&gt;Create Alert Policy&lt;/code&gt;. Go ahead and click that and you will be taken to a page like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5rDhXdKB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676909/elyzsqr2covzratcayjg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5rDhXdKB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570676909/elyzsqr2covzratcayjg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is where we can configure the specific metrics we want to watch and the threshold we consider worthy of an alert. For now, select &lt;code&gt;Memory Utilization&lt;/code&gt;, &lt;code&gt;is above&lt;/code&gt;, &lt;code&gt;70&lt;/code&gt; (percent) and &lt;code&gt;5 min&lt;/code&gt;. This means we will get an alert if the Memory utilization remains above 70% for 5 consecutive minutes.&lt;/p&gt;

&lt;p&gt;In the next section, select the Droplet you are using for this tutorial.&lt;/p&gt;

&lt;p&gt;Then, in the &lt;code&gt;Send alerts via&lt;/code&gt; section, select the checkbox that displays your email address. This is the email address Digital Ocean will send alerts to when necessary.&lt;/p&gt;

&lt;p&gt;Finally, you can name your alert policy if you would like. After that, click the &lt;code&gt;Create alert policy&lt;/code&gt; button. Done! Alerts are now setup. Feel free to add others if you would like.&lt;/p&gt;

&lt;p&gt;Next, we will be installing and configuring tooling more specific to serving up our website.&lt;/p&gt;

&lt;p&gt;Onward to Part 3!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>digitalocean</category>
      <category>webserver</category>
    </item>
    <item>
      <title>Part 1 - Deploying a simple web app with monitoring and analytics without Docker or containers for beginners</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Thu, 10 Oct 2019 20:45:44 +0000</pubDate>
      <link>https://forem.com/franndotexe/part-1-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-34il</link>
      <guid>https://forem.com/franndotexe/part-1-deploying-a-simple-modern-web-app-with-monitoring-and-analytics-without-docker-or-containers-for-beginners-34il</guid>
      <description>&lt;h3&gt;
  
  
  Our website
&lt;/h3&gt;

&lt;p&gt;We will be deploying a simple website that shows a collection of Pokemon from the &lt;a href="https://pokeapi.co/"&gt;PokeAPI&lt;/a&gt; when the user lands on the home page. Clicking on a specific Pokemon will take the user to a 'card' that shows information about that Pokemon.&lt;/p&gt;

&lt;p&gt;I've pushed the project to &lt;a href="https://github.com/frannsoft/pokecard-example"&gt;GitHub&lt;/a&gt; so you can clone the website if you would like.&lt;/p&gt;

&lt;p&gt;If you are using the website I've created for this tutorial you can run it locally via the following commands:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm i&lt;/code&gt; to install packages locally&lt;/p&gt;

&lt;p&gt;Once all the packages are restored you can then run the site locally:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm start&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once started, the site will be accessible on port &lt;code&gt;3000&lt;/code&gt;. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aQKcy_gD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570664527/d8abtq5iggb5sqegglxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aQKcy_gD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/frannsoft/image/upload/v1570664527/d8abtq5iggb5sqegglxy.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Is it the best looking thing? Nah, but it works for our tutorial!&lt;/p&gt;

&lt;p&gt;In the next part we'll focus on setting up our web server with Digital Ocean.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Parallel test execution within the context of MSTestv2, NUnit3 and VSTest.Console</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Mon, 15 Oct 2018 18:12:31 +0000</pubDate>
      <link>https://forem.com/franndotexe/parallel-test-execution-within-the-context-of-mstestv2-nunit3-and-vstestconsole-3f39</link>
      <guid>https://forem.com/franndotexe/parallel-test-execution-within-the-context-of-mstestv2-nunit3-and-vstestconsole-3f39</guid>
      <description>&lt;p&gt;Keeping automated tests within a reasonable performance range is no trivial task and I'm always looking for ways to reduce the total duration of automated tests.  Recently, I did some reading on the capabilities of a few different tools with regards to parallel execution of tests. These tools included &lt;a href="https://github.com/Microsoft/testfx"&gt;MSTestv2&lt;/a&gt; and &lt;a href="https://github.com/nunit/nunit"&gt;NUnit3&lt;/a&gt; when run with &lt;a href="https://github.com/Microsoft/vstest"&gt;vstest.console&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While building a test case to compare the two I built two projects.  One using MSTestv2 (v1.3.2) &lt;code&gt;[DataTestMethod]&lt;/code&gt;s, designed to take advantage of &lt;a href="https://github.com/Microsoft/testfx-docs/blob/master/RFCs/006-DynamicData-Attribute.md"&gt;DynamicData&lt;/a&gt; and another using NUnit3 (v3.10.1). Below is my MSTestv2 test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.VisualStudio.TestTools.UnitTesting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Workers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExecutionScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodLevel&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="c1"&gt;//0 means use as many workers as possible&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;mstest_parallel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestClass&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnitTest1&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;MyTestData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1000&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;span class="n"&gt;DataTestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DynamicData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyTestData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;DynamicDataSourceType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestMethod1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;testVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testVal&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;Now, MSTestv2 has the &lt;a href="https://blogs.msdn.microsoft.com/devops/2018/01/30/mstest-v2-in-assembly-parallel-test-execution/"&gt;ability to parallelize tests&lt;/a&gt; to some degree. Here are the parallelization options it offers at a high level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Class level&lt;/strong&gt; - each thread executes a &lt;code&gt;[TestClass]&lt;/code&gt; worth of tests. Within the &lt;code&gt;[TestClass]&lt;/code&gt;, the test methods execute serially&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Method level&lt;/strong&gt; - each thread executes a &lt;code&gt;[TestMethod]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom&lt;/strong&gt; - users can provide a plugin implementing the required execution semantics (Not yet supported).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These capabilities can be referred to as &lt;strong&gt;fine-grained parallelization&lt;/strong&gt; features and the number of worker threads can be configured via assembly attribute &lt;code&gt;[assembly: Parallelize(Workers = n, Scope = Execution.ClassLevel)]&lt;/code&gt; or a &lt;code&gt;.runsettings&lt;/code&gt; file. &lt;a href="https://blogs.msdn.microsoft.com/devops/2018/01/30/mstest-v2-in-assembly-parallel-test-execution/"&gt;More details here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind these are different than &lt;a href="https://blogs.msdn.microsoft.com/vstsqualitytools/2009/12/01/executing-unit-tests-in-parallel-on-a-multi-cpucore-machine/"&gt;MSTest v1's parallel options&lt;/a&gt; and &lt;strong&gt;also&lt;/strong&gt; different from vstest.console's parallel options.&lt;/p&gt;

&lt;p&gt;Vstest.console's &lt;a href="https://blogs.msdn.microsoft.com/devops/2016/10/10/parallel-test-execution/"&gt;parallel options&lt;/a&gt; focus on what its authors refer to as &lt;strong&gt;coarse-grained parallelization&lt;/strong&gt;. This means one can parallelize the running of multiple test containers at once. So if you are running vstest.console from the command line and specify several test containers i.e., &lt;code&gt;mytestlib1.dll mytestlib2.dll&lt;/code&gt; vstest.console will run each test container simultaneously up to the maximum possible on the machine or the maximum configured.&lt;/p&gt;

&lt;p&gt;Why do we care? I want to squeeze as much performance out of the chosen test framework and runner as possible - ideally without having to write any threading code myself. &lt;strong&gt;Unfortunately, MSTestv2 (and v1) lacks a very specific and important parallelization option; The ability to run tests in parallel which are configured with &lt;code&gt;[DynamicData]&lt;/code&gt; and/or &lt;code&gt;[DataRow]&lt;/code&gt;&lt;/strong&gt;. Evidence for this not being supported exists &lt;a href="https://github.com/Microsoft/testfx/issues/405"&gt;here&lt;/a&gt;, &lt;a href="https://github.com/Microsoft/testfx/issues/452"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/Microsoft/testfx/issues/142#issuecomment-394283354"&gt;here&lt;/a&gt;. For our situation, this means our test method that utilizes &lt;code&gt;[DynamicData]&lt;/code&gt; to generate unique cases will have all of the test cases per &lt;code&gt;[TestMethod]&lt;/code&gt; run in serial rather than in parallel, thus making the majority of our parallelization efforts moot.&lt;/p&gt;

&lt;p&gt;With parallelization turned on at the method level for the assembly our proof of concept MSTestv2 project showed the following results when using vstest.console to execute:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total tests: 11.  Passed: 11. Failed: 0. Skipped: 0.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Test Run Successful.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Test execution time: 47.4547 Seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not that great. The majority of these tests are being run one by one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: vstest apparently reports the 'base' test method as a test case when running MSTestv2 tests which is why the test count is 11 instead of 10.  My understanding is that this does not actually run, but contains the overall results for all the tests associated with this &lt;code&gt;[TestMethod]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's check out NUnit3. This framework offers the &lt;a href="https://github.com/nunit/nunit/issues/2471#issuecomment-340290377"&gt;feature we want&lt;/a&gt;, which is running parameterized tests that have their cases generated at runtime in parallel.&lt;/p&gt;

&lt;p&gt;The fundamental difference between the frameworks here is that MSTestv2 considers test cases via &lt;code&gt;[DynamicData]&lt;/code&gt; and &lt;code&gt;[DataRow]&lt;/code&gt; to all be &lt;a href="https://github.com/Microsoft/testfx/issues/450#issuecomment-400966410"&gt;under a single [TestMethod]&lt;/a&gt; whereas NUnit3 considers &lt;a href="https://github.com/nunit/docs/wiki/Parameterized-Tests"&gt;each test case its own separate [Test]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's our test method adjusted to be used with NUnit3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NUnit.Framework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;nunit_parallel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Parallelizable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ParallelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Children&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="c1"&gt;//parameterized tests are considered child tests&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnitTest1&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;MyTestData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1000&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;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;TestCaseSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyTestData&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestMethod1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;testVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testVal&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;The attribute specified at the top of the class &lt;a href="https://github.com/nunit/docs/wiki/Framework-Parallel-Test-Execution"&gt;&lt;code&gt;[Parallelizable(ParallelScope.Children)]&lt;/code&gt;&lt;/a&gt; tells NUnit to parallelize every test method in the class and it considers each of our generated test cases a unique test method. Additionally, I am letting NUnit decide my &lt;code&gt;[LevelOfParallism]&lt;/code&gt;.  By &lt;a href="https://github.com/nunit/docs/wiki/LevelOfParallelism-Attribute"&gt;default&lt;/a&gt;, NUnit3 uses the processor count or 2, whichever is greater.&lt;/p&gt;

&lt;p&gt;Now, running with the same tests using vstest.console and same number of tests cases per &lt;code&gt;[Test]&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total tests: 10.  Passed: 10. Failed: 0. Skipped: 0.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Test Run Successful.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Test execution time: 17.5729 Seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Predictably, the time to run the suite of tests is drastically shorter.&lt;/p&gt;

&lt;p&gt;One important part of this setup is how these tests are run. Vstest.console handles running tests from each test framework for us as long as we can &lt;a href="https://github.com/Microsoft/vstest-docs/blob/master/RFCs/0004-Adapter-Extensibility.md#specifying-an-adapter"&gt;specify the necessary test adapter&lt;/a&gt;. For both NUnit3 and MSTestv2, the adapters are available as NuGet packages. Adding these NuGet packages to your test project will cause the necessary libraries to be copied out upon build and vstest.console will find them for you so long as they are in the same directory as the test containers you specify.&lt;/p&gt;

&lt;p&gt;I hope this helps explain some of the options regarding parallelizing tests, especially parameterized ones. I learned a ton while studying this stuff and am I sure this will continue to be the case while writing these types of test suites. If you believe anything I have written above needs to be corrected please reach out to me.&lt;/p&gt;

</description>
      <category>c</category>
      <category>testing</category>
    </item>
    <item>
      <title>Experimenting with non-tabular ways of showing test results</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Sat, 28 Oct 2017 01:09:29 +0000</pubDate>
      <link>https://forem.com/franndotexe/experimenting-with-non-tabular-ways-of-showing-test-results-9n9</link>
      <guid>https://forem.com/franndotexe/experimenting-with-non-tabular-ways-of-showing-test-results-9n9</guid>
      <description>&lt;p&gt;Ever heard of &lt;a href="http://fitnesse.org/" rel="noopener noreferrer"&gt;FitNesse&lt;/a&gt;?  If not, imagine what the father of &lt;a href="http://specflow.org/" rel="noopener noreferrer"&gt;SpecFlow&lt;/a&gt; would look like and how it might be used.  &lt;/p&gt;

&lt;p&gt;If you imagined HTML tables filled with wiki-style syntax to perform assertions on the data placed in them you're on the right track.&lt;/p&gt;

&lt;p&gt;A small example from the &lt;a href="http://www.fitnesse.org/FitNesse.UserGuide.TwoMinuteExample" rel="noopener noreferrer"&gt;FitNesse Two Minute Example page&lt;/a&gt;:&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%2Ffrannsoft.github.io%2Fassets%2Fimages%2Fposts%2Fenhancing-old-tests%2Ffitnesse-example.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%2Ffrannsoft.github.io%2Fassets%2Fimages%2Fposts%2Fenhancing-old-tests%2Ffitnesse-example.png" alt="fitnesse-example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the backing markup that creates the table:&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%2Ffrannsoft.github.io%2Fassets%2Fimages%2Fposts%2Fenhancing-old-tests%2Fwikimarkup.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%2Ffrannsoft.github.io%2Fassets%2Fimages%2Fposts%2Fenhancing-old-tests%2Fwikimarkup.png" alt="fitnesse-wiki-markup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This seems relatively simple for straightforward tests.  While maybe not an ultra-modern way of writing tests, enabling testers who aren't necessarily able to write code with the ability to write automated tests is a good thing.  &lt;/p&gt;

&lt;p&gt;These tests can be executed in the browser and are backed by code in a similar fashion to SpecFlow.  Users don't need to install anything at all in order to get started; only go to a url.&lt;/p&gt;

&lt;p&gt;Recently, I came across a scenario where a small suite of these tables were being used to verify data coming from an external service.  These tests were necessary as we need to make sure data arrives in the expected format and expected value.  &lt;/p&gt;

&lt;p&gt;The problem was some of these FitNesse tables had over 90 columns.  This reduces the readability of the tests to almost zero since the only way someone reading the results could find a specific failure is manually searching the results or CTRL+F on the results page.  Any web page with a 90+ column table isn't navigable using this format.   &lt;/p&gt;

&lt;p&gt;Be that as it may, these types of tests have been around for a while and simply porting them to something other than FitNesse wasn't really an option.  This poses a problem as these types of tests hold a key role in ensuring the provider services apps integrate with are working properly.  The search was on for a better way to visualize these results.&lt;/p&gt;

&lt;h3&gt;
  
  
  D3.js
&lt;/h3&gt;

&lt;p&gt;Enter &lt;a href="https://d3js.org" rel="noopener noreferrer"&gt;d3.js&lt;/a&gt;.  I encourage you to check out the link, but essentially d3.js specializes in rendering complex graphs of data in html (often as &lt;code&gt;SVG&lt;/code&gt;s).  After doing some research on non-tabular ways to present tabular data I chose a &lt;a href="https://datavizcatalogue.com/methods/sunburst_diagram.html" rel="noopener noreferrer"&gt;Sunburst diagram&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Visualizing these large FitNesse results tables as a zoomable sunburst diagram allows readers to view all tables at once without needing to scroll.  The sunburst diagram also offers a high-level view of the results ("Did any tests fail?") as well as the ability to zoom in to a specific table for more in-depth analysis.  Placing additional details for results at specific layers on the diagram eases readers into the mass amount of info presented with these results.&lt;/p&gt;

&lt;p&gt;Ideally, the highest level uses a unique color for each table and red/green colors for each database column to show fail/pass, respectively.  Hovering over a 'table' section section reveals database table-specific info and hovering over a specific section for a single test shows fail/pass details, the database table and column under test.&lt;/p&gt;

&lt;p&gt;Here's an example of the generated diagram:&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%2Ffrannsoft.github.io%2Fassets%2Fimages%2Fposts%2Fenhancing-old-tests%2Fsunburst.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%2Ffrannsoft.github.io%2Fassets%2Fimages%2Fposts%2Fenhancing-old-tests%2Fsunburst.png" alt="sunburst"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, the FitNesse test results page is scraped for these database column test results and proper JSON structure is built.  My technique was nothing magical - just some Regex and selectors to get at the necessary HTML elements with the test result data I needed.  According to &lt;a href="https://bl.ocks.org/mbostock/4348373" rel="noopener noreferrer"&gt;this example by Mike Bostock&lt;/a&gt; the necessary JSON for a sunburst diagram in d3.js would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"children"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"children"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;...and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;so&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each 'layer' inside the &lt;code&gt;children&lt;/code&gt; property represents another layer in the sunburst diagram.  The first layer is the root and contains overarching information about the system under test.  The second layer of &lt;code&gt;children&lt;/code&gt; nodes each contain a database table with information about that table.  The third layer of &lt;code&gt;children&lt;/code&gt; contains database column information specific to the parent table and test result information.  Referring to the image shown above, one can see how this JSON structure is rendered.&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;It's worth mentioning that JSON is not the only format d3.js accepts, I just found it to be the easiest for this experiment.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;The last step is generating the sunburst diagram using the scraped and parsed FitNesse results data.  This ended up being very similar to the &lt;a href="https://bl.ocks.org/mbostock/4348373" rel="noopener noreferrer"&gt;previously mentioned example sunburst diagram by Mike Bostock&lt;/a&gt;.  Here's an simplified example of what that may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//get all the path elements in the SVG.  At thsi point it's empty so there will only be one.  https://github.com/d3/d3-selection&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//nodes is the parsed FitNesse results JSON data.  Give the data to d3 and begin the process of generating the SVG.&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//iterates nodes, creating a new path element for each.  https://github.com/d3/d3-selection#selection_enter&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//wire events for each create path element that contains a node.  Click will cause the diagram to zoom in on the clicked node&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mouseover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseover&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//show info about the cell in question&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mouseleave&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseleave&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//clear shown info&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//determine the measurements of the generated path for this node&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="c1"&gt;//set the color of the arc based on whether or not the node is a test and passed/fail or the node is another non-test piece of data like a db table&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;testPassedColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;testFailedColor&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rootColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;data&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="c1"&gt;//ensure unique colors are used for tables&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a &lt;a href="https://i.imgur.com/ZLnNzVd.gif" rel="noopener noreferrer"&gt;link to a gif&lt;/a&gt; of how it all comes together.&lt;/p&gt;

&lt;p&gt;The dashboard pieces shown in the gif like the breadcrumb trail and combobox are designed to additionally ease the process of finding the info readers need.  As a result, readers can delve directly into the results instead of having to scroll through them.  The comboboxes used are from &lt;a href="https://github.com/select2/select2" rel="noopener noreferrer"&gt;select2.js&lt;/a&gt; and contain autocomplete functionality so users can start typing the name of a database table or column they would like to see and filter results as needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;All of this is aimed towards making test results easier to digest for consumers.  Ultimately, readability is one of the most important features of test suites.  If users have a hard time analyzing test results they will start to ignore them as it becomes too tedious to work through them over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expansion
&lt;/h3&gt;

&lt;p&gt;FitNesse uses a fairly generic structure for test tables, so resuing the code to create JSON from results on other test results would theoretically be straightforward.&lt;/p&gt;

&lt;p&gt;By way of a disclaimer, I am in no way a FitNesse expert.  In the event of a more efficient, alternative method existing that would allow us to obtain the raw data from the FitNesse wiki test results tables as JSON then that should be used to eliminate a step in the process.  I did come across a url parameter that can be used to return the test results as XML, but it appeared to return the test results inside a 'table' of sorts rather than just containing the raw data.&lt;/p&gt;

&lt;p&gt;It's also worth noting that while this proof of concept code is geared towards creating a sunburst diagram, any type of visualization is possible.  If another type of diagram is desired, I highly recommend taking a look at the &lt;a href="https://github.com/d3/d3/wiki/gallery" rel="noopener noreferrer"&gt;d3.js gallery&lt;/a&gt; to get ideas.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>d3js</category>
    </item>
    <item>
      <title>MSTest v2 - New (Old) Kid on the Block</title>
      <dc:creator>Jordan Polaniec</dc:creator>
      <pubDate>Tue, 22 Aug 2017 02:06:26 +0000</pubDate>
      <link>https://forem.com/franndotexe/mstest-v2---new-old-kid-on-the-block</link>
      <guid>https://forem.com/franndotexe/mstest-v2---new-old-kid-on-the-block</guid>
      <description>&lt;p&gt;It's been a long time since MSTest was in discussions of modern testing frameworks.  However, here we are in 2017 and MSTest is getting active updates once again, due in part to it being open-sourced.  Let's take a look at what some of these new and exciting features are.&lt;/p&gt;

&lt;p&gt;Please note, this post will cover features that are currently in &lt;code&gt;pre-release&lt;/code&gt; for the MSTest v2 NuGet package.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Remember to use the &lt;code&gt;pre-release&lt;/code&gt; flag with the NuGet package you will be using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/MSTest.TestFramework/1.2.0-beta"&gt;MSTest.Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/MSTest.TestAdapter/1.2.0-beta"&gt;MSTest.TestAdapter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visual Studio 2015+ is also recommended; your mileage may vary with 2013.&lt;/p&gt;

&lt;p&gt;This beta release is NOT the first public iteration of MSTest v2, but contains some of the features discussed below.  Having stronger support around parameterized test cases is very important and those features are in the 1.2.0 beta; version 1.1.13 was the first v2 &lt;a href="https://github.com/Microsoft/testfx-docs/blob/master/docs/releases.md#1113"&gt;release from github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Release notes for each version can be found &lt;a href="https://github.com/Microsoft/testfx/releases"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On a post about the future of MSTest v2 and mstest.exe, user (and presumed employee of Microsoft) 'Abhitej_MSFT' clarifies that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;MSTest V2 tests are not supported with “mstest.exe”. In the TFS build template the Test Runner should be “Visual Studio Test Runner”(&lt;a href="https://msdn.microsoft.com/en-us/library/ms253138(v=vs.110).aspx#Runner"&gt;https://msdn.microsoft.com/en-us/library/ms253138(v=vs.110).aspx#Runner&lt;/a&gt;) . I hope your definition does not require the legacy testsettings. Do let us know on &lt;a href="mailto:aajohn@microsoft.com"&gt;aajohn@microsoft.com&lt;/a&gt; if you hit any issues.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;Source - &lt;a href="https://blogs.msdn.microsoft.com/devops/2017/02/25/mstest-v2-now-and-ahead/#comment-78796"&gt;https://blogs.msdn.microsoft.com/devops/2017/02/25/mstest-v2-now-and-ahead/#comment-78796&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have yet to come across an MSTest v2 feature that NUnit has not already had for quite some time.  However, the features discussed here are still very useful if you decide to use MSTest over another test framework.&lt;/p&gt;

&lt;p&gt;For those who want to see it all, the repository for MSTest can be found on &lt;a href="https://github.com/Microsoft/testfx"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DynamicData &lt;a href="https://github.com/Microsoft/testfx/issues/141"&gt;#141&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Parameterized testing has long been available in MSTest, using the &lt;code&gt;DataRowAttribute&lt;/code&gt; which look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DataRow&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="m"&gt;2&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;DataRow&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="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MyParameterizedTest&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//perform assertions on a,b and c&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The resulting two tests each use one instance of the 'rows' of data.  However, what if we want to reuse these same values across tests?  As it would be inefficient to have to duplicate these values on every test where they are used, v2 offers &lt;code&gt;DynamicDataAttribute&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ReusableTestData&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&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="m"&gt;2&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="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&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="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6&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="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DynamicData&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReusableTestData&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MyParameterizedTest&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//perform the same assertions the same as before.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now this test data can be used on any number of tests without having to duplicate the actual data, making the process cleaner.  Those of you who are familiar with NUnit may know this as the &lt;code&gt;TestCaseSourceAttribute&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Methods can also be used as test data. To do so, simply use the overload of the &lt;code&gt;DynamicDataAttribute&lt;/code&gt; constructor that takes in a &lt;code&gt;DynamicDataSourceType.Method&lt;/code&gt; &lt;code&gt;enum&lt;/code&gt;.  By default, the framework will assume the name of the dynamic data passed in is a &lt;code&gt;Property&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Custom Test Data for Parameterized Tests &lt;a href="https://github.com/Microsoft/testfx/issues/141"&gt;#141&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Taking &lt;code&gt;DynamicData&lt;/code&gt; one step further is useful if the parameters used in your tests are a bit more complex.  Using the &lt;code&gt;CustomTestDataSourceAttribute&lt;/code&gt; you can now create an attribute that loads up this data for you to consume in your tests while keeping your test nice and clean.  Here is the end result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CarTestData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ATestUsingACar&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;carUnderTest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Car&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Year&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;//perform assertion on the car object.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//config for the 'CarTestData' attribute:&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CarTestDataAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ITestDataSource&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetData&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MethodInfo&lt;/span&gt; &lt;span class="n"&gt;methodInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Ford"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1990&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Nisson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2017&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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetDisplayName&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MethodInfo&lt;/span&gt; &lt;span class="n"&gt;methodInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Format&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentCulture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"{0} ({1})"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Car&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Year&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;Creating a new &lt;code&gt;Attribute&lt;/code&gt; that implements the MSTest v2 &lt;code&gt;ITestDataSource&lt;/code&gt; interface allows us to set up our test objects outside of the test, provide a variable amount of them for parameterized tests, and reuse them in other tests without duplication.&lt;/p&gt;

&lt;p&gt;This can be pretty powerful in some scenarios, however I am not entirely in love with &lt;code&gt;ITestDataSource.GetData()&lt;/code&gt; returning an &lt;code&gt;IEnumerable&amp;lt;object[]&amp;gt;&lt;/code&gt; instead of returning an &lt;code&gt;IEnumerable&amp;lt;object&amp;gt;&lt;/code&gt;, &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; or offering some other interface.  This makes it somewhat cumbersome to use this mechanism to return non-primitives. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ITestDataSource.GetDisplayName&lt;/code&gt; will be displayed when the test is executed via the target runner (VSTest if running in Visual Studio), allowing us to determine which cases of a test failed when tests are parameterized.&lt;/p&gt;




&lt;h3&gt;
  
  
  Assert.That &lt;a href="https://github.com/Microsoft/testfx/issues/116"&gt;#116&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;With the introduction of a focused extension point for assertion logic, MSTest v2 now offers &lt;code&gt;That&lt;/code&gt;, a static property on &lt;code&gt;Assert&lt;/code&gt; which returns the instance and allows for an easy jumping point for extension assertion methods.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAssertExtensions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;CountIsGreaterThan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt; &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;objs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;actualCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actualCount&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AssertFailedException&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Expected &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objs&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt; count to be greater than &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, but was &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actualCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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;This means tests are much more readable in their assertions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestClass&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyTests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ATest&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;aList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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="m"&gt;2&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="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;That&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CountIsGreaterThan&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&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;&lt;code&gt;Assert.That&lt;/code&gt; can be used to replace and/or supplement calls to &lt;code&gt;Assert.AreEqual()&lt;/code&gt; and &lt;code&gt;Assert.IsTrue()&lt;/code&gt;.  By themselves, these are such broad calls that often times they can lead to confusion when debugging or reading tests.  In my experience this is especially true with &lt;code&gt;Assert.IsTrue()&lt;/code&gt; due to its default failure output of &lt;code&gt;Assert.IsTrue failed&lt;/code&gt;. &lt;code&gt;AreEquals()&lt;/code&gt; attempts to mitigate any confusion by reporting the actual and expected values.&lt;/p&gt;

&lt;p&gt;NOTE:  If you are not going the route of extension methods a quick way to help solve this problem is to pass the name of the property/method under test when passing a failure message.  An example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MyTest&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;aList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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="m"&gt;2&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="c1"&gt;//nameof() is a C#6 feature, but is not required to make this work.&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsTrue&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//failure prints: Assert.IsTrue failed. Count&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an example of enhancing &lt;code&gt;Assert.IsTrue&lt;/code&gt; with this type of approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAssertExtensions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;IsTrue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt; &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Expression&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assertionExpression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assertionExpression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Compile&lt;/span&gt; &lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AssertFailedException&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Assertion failed for expression &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;assertionExpression&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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="c1"&gt;//Example&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;IsTrue_Extended_Test&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;aList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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="m"&gt;2&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="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;That&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsTrue&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//failure prints: Assertion failed for expression 'a =&amp;gt; (a.Count &amp;gt; 4)'.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Taking this a bit further we can create a fluent syntax that lends itself to more extensibility in the future:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;IsGreaterThan_Redux&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;aList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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="m"&gt;2&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="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;That&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;For&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aList&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;IsTrue&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//failure output: Assertion failed for expression 'list =&amp;gt; list.Count &amp;gt; 4'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAssertionExtensions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt; &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;_instanceUnderTest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;For&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;instanceUnderTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_instanceUnderTest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instanceUnderTest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;IsTrue&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Expression&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assertionExpression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assertionExpression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Compile&lt;/span&gt; &lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_instanceUnderTest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AssertFailedException&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Assertion failed for expression '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;assertionExpression&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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;Clearly, creating extension methods with &lt;code&gt;Assert.That&lt;/code&gt; heavily mitigates the readability problem.  For an additional point of reference, see the &lt;a href="https://github.com/Microsoft/testfx-docs/blob/master/RFCs/002-Framework-Extensibility-Custom-Assertions.md"&gt;original RFC&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While this is not a comprehensive overview of the new features in the pipeline for MSTest v2, I have covered those that strike me as being particularly useful.  &lt;/p&gt;

&lt;p&gt;For more information on MSTest v2 check out these links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/testfx"&gt;Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/testfx-docs"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogs.msdn.microsoft.com/devops/2016/06/17/taking-the-mstest-framework-forward-with-mstest-v2/"&gt;Initial Microsoft Blog post for v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MSTest v2 is definitely making MSTest a more relevant testing framework.  The concepts here are the aspects I was most interested in as a current user of NUnit.&lt;br&gt;
While I myself have not yet made the switch from NUnit to MSTest v2, these new features have made considering the possibility much more favorable. Hopefully these features combined with more features coming down the pipeline in the future continue to improve developers view of MSTest when compared to other testing frameworks. &lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
