I'm blogging about programming, but ... hey look over there - it's something shiny!
Thursday, February 21, 2019
Where to Find Your Tesla "Motor Vehicle Purchase Agreement"
The FAQ at Tesla is out of date as of Feb 21, 2019.
You can print it from your Tesla account.
1. Login to www.Tesla.com
2. In the top right-hand corner select "TESLA ACCOUNT" (what's with this ALL-CAPS trend?)
3. Select "Manage"
4. Under the car's picture select "View Details".
5. On the right side, near the bottom by "Motor Vehicle Purchase Agreement" select "View".
6. Your pdf of the "Motor Vehicle Purchase Agreement" will be displayed.
By the way, the tax form to get your $7,500 federal tax rebate is 8936.
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 TaskGetAwesomeString() { 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 TaskGetAwesomeString() { 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
- Bob writes a failing test
- Ashley writes just enough functionality in the method so it will pass.
- The code may then be refactored to make it cleaner.
- 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:
- Encourages better architectural design
- Makes debugging easier
- Guarantees tests are written
- Encourages us to think about requirements and edge cases
- Let's you be courageous and refactor code
Drawbacks of Test Driven Development:
- Tests are expensive. They are expensive to write and more expensive to maintain.
- 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.
- GUI, Integration, Performance, and Endurance tests are also needed.

