Thursday, January 31, 2019

Random Notes on C#, Windows, Linux

.Net Core 2.0, to include an HTML fragment into a .cshtml page:

    @await Html.PartialAsync("~/Views/Shared/_Address.cshtml")

To prepare for Dependency Injection in .Net Core 2.0

public void ConfigureServices(IServiceCollection services)
 {
     services.AddSingleton(provider => Configuration);
     services.AddTransient<IUserRepository, UserRepository>();
     services.AddSingleton<IMailer, SmtpMailer>();
 }

How to convert a json string to a C# Object

var myObject= JsonConvert.DeserializeObject(jsonString);

How to create a log4net factory outside of Startup.cs for unit testing.

public static ILoggerFactory GetLoggerFactory()
{
    var loggerFactory = new LoggerFactory();
    loggerFactory.AddProvider(new Log4NetProvider());
    loggerFactory.AddDebug();
    loggerFactory.AddConsole(GetTestConfiguration().GetSection("Logging"));
    loggerFactory.AddLog4Net("log4net.config");

    return loggerFactory;
}

Create simple Moq object in C#

var imailerMoq = new Mock();
imailerMoq.Setup(x => x.SendEmail(It.IsAny(), It.IsAny())).Returns(true);

How to return async task for a unit test

public async Task GetAwesomeString() {
   return await Task.FromResult("I'm the awesome returned text");
}

sudo -s //makes the entire session root

In C# you cannot downcast an object, only cast an object upwards:

wrong    Sheep sheep = (sheep)mammal;
right  Mammal mammal = (Mammal)sheep;

C# -- to get prettyprint of json object with indentation:

var json = JsonConvert.SerializeObject(myObject, Formatting.Indented);

C# to get a key from a value:

Dictionary dictionary = GetMyDictionary();
var name = dictionary.FirstOrDefault(x => x.Value == id).Key;

To change the prompt in linux:

echo "PS1='\w\$ '" >> ~/.bash_profile; source ~/.bash_profile

Wednesday, January 30, 2019

Personal Notes from Udemy Course "ASP.NET Core 2.0"

Here are my notes for my future reference from Jonas Fagerberg's excellent Udemy Course. I would highly recommend you go buy the course: "ASP.NET Core 2.0"

Chapter 3 - MVC

Startup.cs - Let's get wired up!

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AspNetCoreVideoCourse.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AspNetCoreVideoCourse
{
    public class Startup
    {
        public IConfiguration Configuration { get; set; }
    
        public Startup()
        {
            Configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();
        }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddSingleton(provider => Configuration);
            services.AddSingleton();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMessageService messageService)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(messageService.GetMessage());
            });
        }
    }
}

Explicit Attribute Routing

namespace AspNetCoreVideoCourse.Controllers
{
    [Route("[controller]")]
    public class EmployeeController
    {
        [Route("[action]")]
        public string Name()
        {
            return "Jonas";
        }
        [Route("[action]")]
        public string Country()
        {
            return "Sweden";
        }
        [Route("")]
        [Route("[action]")]
        public string Index()
        {
            return "Hello from Employee";
        }
    }
}

Generalized Attribute Routing:

namespace AspNetCoreVideoCourse.Controllers
{
    [Route("company/[controller]/[action]")]
    public class EmployeeController
    {
        public string Name()
        {
            return "Jonas";
        }
        public string Country()
        {
            return "Sweden";
        }
        [Route("")]
        public string Index()
        {
            return "Hello from Employee";
        }
    }
}

To return json data directly to the browser:

public class HomeController : Controller
    {
        public ObjectResult Index()
        {
            var model = new Video {Id = 1, Title = "Shreck"};
            return new ObjectResult(model);
        }
    }

To display an IEnumerable in Razor

@model IEnumerable<AspNetCoreVideoCourse.Models.Video>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Video</title>
</head>
<body>
    <table>
        @foreach (var video in Model)
        {
            <tr>
                <td>@video.Id</td>
                <td>@video.Title</td>
            </tr>
        }
    </table>

</body>
</html>

Chapter 5: Models

Creating an object through POST Example

Data annotations are found in System.ComponentModel.DataAnnotations.

The most common are:
MinLength,MaxLength,Range, RegularExpression, Display, DataType, Required, Compare

Example

in Video.cs
public class Video
{
    public int Id { get; set; }
    [Required, MinLength(3)]
    [DataType(DataType.Password)]
    public string Title { get; set; }
    [Display(Name = "Film Genre")]
    public Genres Genre { get; set; }
}
in ViewModels/ViedoEditViewModel.cs:
namespace AspNetCoreVideoCourse.ViewModels
{
    public class VideoEditViewModel
    {
        public int Id { get; set; }
        [Required, MinLength(3), MaxLength(80)]
        public string Title { get; set; }
        public Genres Genre { get; set; }
    }
}

in HomeController.cs:
HttpGet]
public IActionResult Create()
{
    return View();
}
[HttpPost]
public IActionResult Create(VideoEditViewModel model)
{
    if (ModelState.IsValid)
    {
        var video = new Video
        {
            Title = model.Title,
            Genre = model.Genre
        };
        _videoData.Add(video);

        return RedirectToAction("Details", new {id = video.Id});
    }

    return View();
}
in Views/Home/Create.cshtml:
@using AspNetCoreVideoCourse.Models
@model AspNetCoreVideoCourse.Entities.Video
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h2>Create Video</h2>
<form asp-action="Create" method="post">
    <div asp-validation-summary="All"></div>
    <table>
        <tr>
            <td><label asp-for="Title"></label></td>
            <td><input asp-for="Title"/></td>
            <td><span asp-validation-for="Title"></span></td>
        </tr>
        <tr>
            <td><label asp-for="Genre"></label></td>
            <td><select asp-for="Genre" asp-items="Html.GetEnumSelectList<Genres>()"></select></td>
            <td><span asp-validation-for="Genre"></span></td>
        </tr>
    </table>
    <input type="submit" value="Create"/>
</form>
<div><a asp-action="Index">Back to List</a></div>

Entity Framework

in package manager:

PM> add-Migration
cmdlet Add-Migration at command pipeline position 1
Supply values for the following parameters:
Name: Initial
PM> update-database

Additional Files:

In Startup.cs:
public class Startup

public IConfiguration Configuration { get; set; }

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: true);

    if (env.IsDevelopment())
    {
        builder.AddUserSecrets<Startup>();

    }

    Configuration = builder.Build();

}
public void ConfigureServices(IServiceCollection services)
{
    var conn = Configuration.GetConnectionString("DefaultConnection");
    services.AddDbContext<VideoDbContext>(options => options.UseSqlServer(conn));
    services.AddMvc();
    services.AddSingleton(provider => Configuration);
    services.AddSingleton<IMessageService, ConfigurationMessageService>();
    services.AddScoped<IVideoData, SqlVideoData>();
}

In SqlVideoData.cs:
:
using System.Collections.Generic;
using AspNetCoreVideoCourse.Data;
using AspNetCoreVideoCourse.Entities;

namespace AspNetCoreVideoCourse.Services
{
    public class SqlVideoData : IVideoData
    {
        private VideoDbContext _db;

        public SqlVideoData(VideoDbContext db)
        {
            _db = db;
        }

        public IEnumerable<Video> GetAll()
        {
            return _db.Videos;
        }

        public Video Get(int id)
        {
            return _db.Find<Video>(id);
        }

        public void Add(Video newVideo)
        {
            _db.Add(newVideo);
            _db.SaveChanges();
        }
    }
}
in VideoDbContext.cs:
using AspNetCoreVideoCourse.Entities;
using Microsoft.EntityFrameworkCore;

namespace AspNetCoreVideoCourse.Data
{
    public class VideoDbContext : DbContext
    {
        public DbSet<Video> Videos { get; set; }

        public VideoDbContext(DbContextOptions<VideoDbContext> options) : base(options)
        {
            
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }
}
       

To Add an "Edit" View

in Edit.cshtml:
<form asp-action="Edit" method="post">
    <div asp-validation-summary="All"></div>
    <table>
        <tr>
            <td><label asp-for="Title"></label></td>
            <td><input asp-for="Title" /></td>
            <td><span asp-validation-for="Title"></span></td>
        </tr>
        <tr>
            <td><label asp-for="Genre"></label></td>
            <td><select asp-for="Genre" asp-items="Html.GetEnumSelectList<Genres>()"></select></td>
            <td><span asp-validation-for="Genre"></span></td>
        </tr>
    </table>
    <input type="submit" value="Edit" />
</form>

in HomeController.cs:
[HttpGet]
public IActionResult Edit(int id)
{
    var video = _videoData.Get(id);
    if (video == null)
    {
        return RedirectToAction("Index");
    }

    return View(video);
}
[HttpPost]
public IActionResult Edit(int id, VideoEditViewModel model)
{
    var video = _videoData.Get(id);
    if (video == null || !ModelState.IsValid) return View(model);

    video.Title = model.Title;
    video.Genre = model.Genre;

    _videoData.Commit();

    return RedirectToAction("Details", new {id = video.Id});
}
in SqlVideoData.cs:
using System.Collections.Generic;
using AspNetCoreVideoCourse.Data;
using AspNetCoreVideoCourse.Entities;

namespace AspNetCoreVideoCourse.Services
{
    public class SqlVideoData : IVideoData
    {
        private VideoDbContext _db;

        public SqlVideoData(VideoDbContext db)
        {
            _db = db;
        }

        public IEnumerable<Video> GetAll()
        {
            return _db.Videos;
        }

        public Video Get(int id)
        {
            return _db.Find<Video>(id);
        }

        public void Add(Video newVideo)
        {
            _db.Add(newVideo);
            _db.SaveChanges();
        }

        public int Commit()
        {
            return _db.SaveChanges();
        }
    }
}

Section 7 Razor Views

_ViewStart.cshtml will define variables for all pages in lower directories
_ViewImports.cshtml will define import statements for all lower pages

Partial Views

  @Html.Partial("_Video", video);
  @await Html.PartialAsync("_Video", video);

Components

Components allow you to inject html from code.

in Views/Shared/Components/Message/Default.cshtml:
using AspNetCoreVideoCourse.Services;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreVideoCourse.ViewComponents
{
    public class Message : ViewComponent
    {
        private readonly IMessageService _message;

        public Message(IMessageService message)
        {
            _message = message;
        }

        public IViewComponentResult Invoke()
        {
            var model = _message.GetMessage();
            return View("Default", model);
        }
    }
}
in Views/Shared/_Layout.cs:
<footer>
    @RenderSection("footer",false)
    @await Component.InvokeAsync("Message")
</footer>

Section 8: Forms Authentication / Identity Management

in AccountController:
using System.Threading.Tasks;
using AspNetCoreVideoCourse.Entities;
using AspNetCoreVideoCourse.ViewModels;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreVideoCourse.Controllers
{
    public class AccountController : Controller
    {
        private readonly UserManager<User> _userManager;
        private readonly SignInManager<User> _signInManager;

        public AccountController(UserManager<User> userManager, SignInManager<User> signInManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }
        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if (!ModelState.IsValid) return View();

            var user = new User {UserName = model.Username};
            var result = await _userManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                await _signInManager.SignInAsync(user, false);
                return RedirectToAction("Index", "Home");
            }
            else
            {
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError("",error.Description);
                }
            }

            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }

        [HttpGet]
        public IActionResult Login(string returnUrl = "")
        {
            var model = new LoginViewModel {ReturnUrl = returnUrl};
            return View(model);
        }
        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            if (!ModelState.IsValid) return View(model);
            var result =
                await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false);

            if (result.Succeeded)
            {
                if (!string.IsNullOrEmpty(model.ReturnUrl) &&
                    Url.IsLocalUrl(model.ReturnUrl))
                {
                    return Redirect(model.ReturnUrl);
                }
                else
                {
                    return RedirectToAction("Index", "Home");
                }
            }
            ModelState.AddModelError("","Login failed");
            return View(model);
        }
    }
}  
in Views/Account/Login:
@model LoginViewModel

@{ ViewBag.Title = "Login";}

<h2>Login</h2>

<form method="post" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">
    <div asp-validation-summary="ModelOnly"></div>
    
    <div>
        <label asp-for="Username"></label>
        <input asp-for="Username"/>
        <span asp-validation-for="Username"></span>
    </div>

    <div>
        <label asp-for="Password"></label>
        <input asp-for="Password"/>
        <span asp-validation-for="Password"></span>
    </div>

    <div>
        <label asp-for="RememberMe"></label>
        <input asp-for="RememberMe"/>
        <span asp-validation-for="RememberMe"></span>
    </div>
    
    <div>
        <input type="submit" value="Register"/>
    </div>

</form>
in Views/Account/Register:
@model RegisterViewModel

@{ ViewBag.Title = "Register";}

<h1>Register</h1>
<form method="post" asp-controller="Account" asp-action="Register">
    <div asp-validation-summary="ModelOnly"></div>
    
    <div>
        <label asp-for="Username"></label>
        <input asp-for="Username"/>
        <span asp-validation-for="Username"></span>
    </div>

   <div>
        <label asp-for="Password"></label>
        <input asp-for="Password"/>
        <span asp-validation-for="Password"></span>
    </div>

   <div>
        <label asp-for="ConfirmPassword"></label>
        <input asp-for="ConfirmPassword"/>
        <span asp-validation-for="ConfirmPassword"></span>
    </div>
    
    <div>
        <input type="submit" value="Register"/>
    </div>
</form>
in ViewModels/LoginViewModel.cs:
using System.ComponentModel.DataAnnotations;

namespace AspNetCoreVideoCourse.ViewModels
{
    public class LoginViewModel
    {
        [Required]
        public string Username { get; set; }
        [Required, DataType(DataType.Password)]
        public string Password { get; set; }
        public string ReturnUrl { get; set; }
        [Display(Name = "Remember Me")]
        public bool RememberMe { get; set; }
    }
}
in ViewModels/RegisterViewModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreVideoCourse.ViewModels
{
    public class RegisterViewModel
    {
        [Required, MaxLength(255)]
        public string Username { get; set; }
        [Required, DataType(DataType.Password)]
        public string Password { get; set; }
        [Required, DataType(DataType.Password), Compare(nameof(Password))]
        public string ConfirmPassword { get; set; }
    }
}
in Views/Shared/_LoginLinks.cshtml:
@using Microsoft.AspNetCore.Identity

@inject SignInManager<User> SignInManager
@inject UserManager<User> UserManger

@if (SignInManager.IsSignedIn(User))
{
    <div>@User.Identity.Name</div>
    <form method="post" asp-controller="Account" asp-action="Logout">
        <input type="submit" value="Logout"/>
    </form>
}
else
{
    <a asp-controller="Account" asp-action="Login">Login</a>
    <a asp-controller="Account" asp-action="Register">Register</a>
}


Austin .Net User's Group - Jan 14, 2018 - Shawn Weisfeld on the Future of DevOps

Shawn Weisfeld, Cloud Solution Architect with Microsoft, gave a great presentation on Azure DevOps to 30 people at Austin .Net User's Group. The video is at usergroup.tv/videos/devops-in-a-cloud-world-2.

What is Dev Ops? "DevOps is the union of people, process, and products to enable continuous delivery of value to your end users."

He showed a very fun youtube video of Formula One Pit Stop 1950, 2014 demonstrating the promise of DevOps.

40-50% of Azure machines are Linux. Microsoft is taking a strong agnostic stand on operating systems, which is good for the community and good from Microsoft. It's refreshing to see them being even handed with OSes.

To me the most informative subject Shawn covered was that Visual Studio Online and other tools are being rebranded as Azure DevOps

Here's the Workflow:

Azure Boards - takes the place of tfs
Azure Repos
Azure Pipelines
Azure Test Plans
Azure Artifacts

Shawn used a tool to zoom in during the demo from zoomit.com

We had a discussion towards the end of his talk about "Infrastructure as Code", meaning you can write code to write the descriptions of containers and them launch those containers, so it's all software.

Tuesday, January 29, 2019

Java User's Group - Jan 29th, 2019 - The Future of Java

Simon Ritter, Deputy CTO, from Azul Systems gave a great presentation to 100 java developers about new things in Java 9,10,11, and 12. Slides are at www.slideshare.net/SimonRitter/moving-towards-jdk-12.

I did java programming 12 years ago and it was great fun to come back to the Java User's Group to see old friends again. Currently I'm doing C# .Net Core work, but I wanted to see what Java has been faring.

TL;DR: My impressions: Java is going to face headwinds with Oracle requiring money for licenses for production versions of JDK and beyond. Especially for small shops it's just easier to spin up a container with all open source and not worry about payments. Many organizations will stick with older free versions of Java (which is where Azul can make some money and help the community), but after a while, do you want to be on older versions of a language while the world is moving forward? It will be like watching ice melt on a 33 degree day, but we will see a slow abandonment of Java.

Java is still playing catchup with C# on features like "var", expanding switch statements, and raw string literals, but it's good to see the progress.

Here's my random, jumbled notes:

JDK 9, September 2017 86 new items. Oracle says JDK 9 was the last big release.

Java Platform Module System (JPMS) (JEP 220) breaks the 4,500 classes in Java into smaller chunks; with Modules you can create a runtime specific for your application only including modules needed for the app.

jlink (JEP 282) creates the java hierarchy with a smaller set of modules.

Instead of a release every two years, Oracle will release new JDKs twice a year, once in March and once in September. With so many releases, only certain ones will have long term support.

In JDK 11, some parts of the JDK will be open sourced like Flight recorder and Mission control. Some commercial features will be removed like JavaFX.

Java 8 had 350 methods deprecated, but never removed. JDK 9 removed six methods and one class (JavaBeans had a dependency on the desktop). JDK 10 removed 1 package, 6 classes, 9 methods and 1 field.

Compatibility not guaranteed in the future. Developers will have one release (six months) warning of deprecated.

Oracle JDK binary has LTS versions like 11, 17; Oracle OpenJDK does not.

Oracle JDK 11 has new license. Must have a paid-for license from Oracle for production for JavaSE. JDK 8 can be used forever for free, but without any further security patches and bug fixes.

What's new in JDK 10

Local Variable Type Inference (JEP 286) Java gets "var", infers type (like C#). "var" is not a reserved word, just a reserved type (meaning you can't create a class named "var").

JEP 307: Parallel Full GC for G1

JEP 310: Application Class-Data Sharing. Write java "schema" info to a file, to be read next time to speed startup.

JEP 317: JIT compiler (Graal)

Root Certificates, Heap allocation on other devices, remove javah tool, Garbage Collector Interface (Red Hat), Thread-Local Handshakes.

73 New APIs, like the collections List, Set, Map get a static copyOf() method to get an immutable copy.

What's new in JDK 11

17 JEPs, 3 from outside Oracle, like Red Hat's JEP 318 Epsilon garbage collector.

JEP 323: Local-variable syntax for lambda parameters.

JEP 327: new Unicode 10 Support for 8,518 new characters.

JEP 330: Launch Single file source code. Can launch a single file like "$java Factorial.java 4"

Many security features

Nashorn JavaScript engine deprecated

New I/O methods like nullInuptStream

String has new methods like isBlank(), repeat(int), strip()

Predicate now has Predicate.not()

What's new in JDK 11

Six modules removed, including java.corba, java.transaction, java.activation, java.xml.bind, java.xml.ws, java.xml.ws.annotation

Removes 27 command line flags and added 53

JDK 12 - A Small Release

JEP 325: Switch Expressions (Preview)

JEP 189: Shenandoah GC

JEP 334, 230, 341

New APIs: teeing(Collector, Collector, joinerFunc)

Class - describeConstable

Long Term Future

Project Amber, how to simplifying Java Language syntax. Kotlin is popular due to its clean syntax.

JEP 302: Lambda bits, single underscore for unused jparameters in lambda

JEP 326: Raw string literals, use backtick to start raw strings

JEP 305: Pattern Matching

Project Valhalla

Primitives are good for performance, objects good for OO concepts

Can have collections of primitives

Value types, are immutable, really

Project Loom

Loom introduces "fibres" which are lighter weight threads, less memory used, lower overhead

Zulu Java

Zulu community - Free versions of Open versions.

Zulu Enterprise - For money you can get backports of bug fixes and security patches from latest OpenJDK to previous releases.

Questions?

What is the effect of Oracle's new licensing pricing on the cloud? Cloud providers will provide support.

No more free JDK 11 from Oracle.

<

Thursday, January 17, 2019

Random Cheatsheet (mostly C# .net core)

Latest endpoint to connect Visual Studio to the Nuget.org repository is

https://api.nuget.org/v3/index.json

.Net Core 2.0, to include an HTML fragment into a .cshtml page:

    @await Html.PartialAsync("~/Views/Shared/_Address.cshtml")

To prepare for Dependency Injection in .Net Core 2.0

public void ConfigureServices(IServiceCollection services)
 {
     services.AddSingleton(provider => Configuration);
     services.AddTransient<IUserRepository, UserRepository>();
     services.AddSingleton<IMailer, SmtpMailer>();
 }

How to convert a json string to a C# Object

var myObject= JsonConvert.DeserializeObject(jsonString);

How to create a log4net factory outside of Startup.cs for unit testing.

public static ILoggerFactory GetLoggerFactory()
{
    var loggerFactory = new LoggerFactory();
    loggerFactory.AddProvider(new Log4NetProvider());
    loggerFactory.AddDebug();
    loggerFactory.AddConsole(GetTestConfiguration().GetSection("Logging"));
    loggerFactory.AddLog4Net("log4net.config");

    return loggerFactory;
}

Create simple Moq object in C#

var imailerMoq = new Mock();
imailerMoq.Setup(x => x.SendEmail(It.IsAny(), It.IsAny())).Returns(true);

How to return async task for a unit test

public async Task GetAwesomeString() {
   return await Task.FromResult("I'm the awesome returned text");
}

sudo -s //makes the entire session root

In C# you cannot downcast an object, only cast an object upwards:

wrong    Sheep sheep = (sheep)mammal;
right  Mammal mammal = (Mammal)sheep;

C# -- to get prettyprint of json object with indentation:

var json = JsonConvert.SerializeObject(myObject, Formatting.Indented);

C# to get a key from a value:

Dictionary dictionary = GetMyDictionary();
var name = dictionary.FirstOrDefault(x => x.Value == id).Key;

To change the prompt in linux:

echo "PS1='\w\$ '" >> ~/.bash_profile; source ~/.bash_profile

Wednesday, January 16, 2019

A TestDriven Workshop Example Using C# and NUnit

I've done this workshop multiple times at work to teach developers Test Driven Development using C# and NUnit.

You can clone it at https://github.com/fincher42/TDDWorkshopNunit.git

The theme of the workshop is from the movie "We Bought A Zoo".

Enjoy and leave me feedback below on how to improve the workshop.


Here's an outline of my notes:

Essence of TDD: Write failing tests **first**, before coding the functionality.

Ping Pong

  1. Bob writes a failing test
  2. Ashley writes just enough functionality in the method so it will pass.
  3. The code may then be refactored to make it cleaner.
  4. Bob and Ashley now switch roles and continue.

This is the Red/Green/Refactor cycle.

One method of arranging your code is to have Arrange/Act/Assert sections in your test.

Benefits of Test Driven Development:

  1. Encourages better architectural design
  2. Makes debugging easier
  3. Guarantees tests are written
  4. Encourages us to think about requirements and edge cases
  5. Let's you be courageous and refactor code

Drawbacks of Test Driven Development:

  1. Tests are expensive. They are expensive to write and more expensive to maintain.
  2. Perfect Unit Tests with 100% coverage do not offer any guarantees on overall code quality. Don't be lulled into a sense of false security.
  3. GUI, Integration, Performance, and Endurance tests are also needed.

Thursday, January 10, 2019

The Four Biggest Changes in Software Development in the Last 40 Years

I was giving a workshop on Test Driven Development today. Being a history nerd (don't get me started about the causes of the fall of the Roman Empire), I was reflecting back on how much has changed in software and how much hasn't. I started programming 42 years ago on punched cards. Here's my list of the most game changing developments in software in the last 40 years.

  1. Duh, It's the Internet
  2. Being able to use Stackoverflow and its kin, we can peruse the minds of thousands of people and find a solution quickly. Online training courses are available to teach us the latest software. It's simply amazing how much more we can create and learn.

  3. Computers are so much faster.
  4. With punched cards at Texas Tech University in the fall of 1976, a diligent student could get in five compile/executes cycles a day. Five. Now you can do that in a few minutes. This changes the way we develop software. Instead of mapping out your entire program on quadral paper using the IBM flowchart template with a Pentel mechanical pencil, we can start programming and build the software iteratively.

  5. Interactive Development Environments (IDE)
  6. Having an IDE that can pop-up all the available methods on an object is such a time saver. Having Resharper to refactor code on the fly, perfectly, is such an asset.

  7. Test Driven Development (TDD)
  8. Test Driven Development encourages us to write software that is easy to test, but more importantly, it demands a layered architecture and encourages small modules. TDD gives us the courage to do massive refactoring. Thank you Kent Beck.

My major professor at Tech use to tell us about a principle of good software engineering, but follow it up with "But, of course, this doesn't matter if you have really good people." That is still true today. The heart of producing good software is excellent people with small egos who have a passion to hone their craft and cooperate with their peers.