Friday, July 14, 2023

Test-Driven Development Tutorial

 Many years ago when I first learned how to do test-driven development I used a guide I found on C# Corner and I really liked it. Over the years I've referred people to that same guide, but when I checked it out again recently I realized it has never been updated so it still refers to Visual Studio features that are no longer available, and uses NUnit instead of XUnit. I figured I'd give it a makeover and post it here, but you should definitely credit Dom Millar for this one. Everything after this point is lifted straight from that post, with the only changes being those for Visual Studio versions, NUnit -> XUnit, and maybe some syntax.


Introduction - Defining the Battlefield

This tutorial is a short introduction to using Test Driven Development (TDD) in Visual Studio 2022 (VS2022) with C#. Like most of my examples it's based on a game.

By completing this tutorial you will:

  • Get a taste of TDD through a series of small iterations
  • Learn how VS2022 provides TDD support through a number of features
  • Learn a number of C# features

CannonAttack is a simple text based game in which a player enters an angle and velocity of a cannonball to hit a target at a given distance. The game uses a basic formula for calculating trajectory of the cannonball and the player keeps taking turns shooting at the target until it has been hit. I won't go into TDD theory in any great detail now, but you should check out the number of great references to TDD including:

http://en.wikipedia.org/wiki/Test_driven_development

http://www.codeproject.com/KB/dotnet/tdd_in_dotnet.aspx

C# .NET 2.0 Test Driven Development by Matthew Cochran

http://msdn.microsoft.com/en-us/library/dd998313(VS.100).aspx

The following are the fundamental steps of a TDD iteration:

  • RED - take a piece of functionality and build a test for it and make it fail the test by writing a minimum amount of code (basically just get it to compile and run the test)
  • GREEN - write minimal code for the test to make it succeed
  • REFACTOR - clean up and reorganize the code and ensure it passes the test and any previous tests
     

In this tutorial we will be progressing through a number of iterations of the TDD cycle to produce a fully functional simple application. Each iteration will pick up one or more of the requirements/specs from our list (see The CannonAttack Requirements/Specs below). We won't test for absolutely everything and some of the tests are fairly basic and simplistic, but I am just trying to keep things reasonably simple at this stage

VS2022 and C# 4.0:

This tutorial covers the use of VS2022 and targets a number of features of C# 4.0 [editor's note: the current version of C# is 11.0, but this guide goes out of its way to introduce features of C# 4.0 so I left all references to C# 4.0 intact],

  • Generating stubs for TDD in VS2022
  • Test Impact View in VS2022 [editor's note: this feature disappeared sometime between VS2010 and VS2022 and the closest feature I've been able to find is Live Unit Testing, which is only available in Enterprise Edition]
  • Tuples
  • String.IsNullOrWhiteSpace method

There are many more features of C# 4.0 and we will be covering them in future tutorials.

What you need:

  • This is a C# tutorial so a background in C# would be very useful
  • VS2022

The CannonAttack Requirements/Specs:

The following is a combination of Requirements and Specifications that will give us some guide in terms of the application we are trying to build: 

  • Windows Console Application
  • Player identified by an id, default is set to a constant "Human"
  • Single player only, no multi-play yet
  • Allow player to set Angle and Speed of the Cannon Ball to Shoot at a Target
  • Target Distance is simply the distance of the Cannon to Target, and is created randomly by default but can be overridden
  • Angle and Speed needs to be validated (specifically not greater than 90 degrees and Speed not greater than speed of light)
  • Max distance for target is 20000 meters
  • Base the algorithm for the calculation of the cannon's trajectory upon the following C# code (distance and height is meters and velocity is meters per second):
    • distance = velocity * Math.Cos(angleInRadians) * time;
    • height = (velocity * Math.Sin(angleInRadians) * time) - (GRAVITY * Math.Pow(time, 2)) / 2;
  • A hit occurs if the cannon is within 50m of the target
  • Game text will be similar to the following:
    Welcome to Cannon Attack
    Target Distance: 12621m
    Please Enter Angle: 40
    Please Enter Speed: 350
    Missed cannonball landed at 12333m
    Please Enter Angle: 45
    Please Enter Speed: 350
    Hit - 2 Shots
    Would you like to play again (Y/N)
    Y
    Target Distance: 2078m
    Please Enter Angle: 45
    Please Enter Speed: 100
    Missed cannonball landed at 1060m
    Please Enter Angle: 45
    Please Enter Speed: 170
    Missed cannonball landed at 3005m
    Please Enter Angle: 45
    Please Enter Speed: 140
    Hit - 3 shots
    Would you like to play again (Y/N)
    N
    Thank you for playing CannonAttack

OK so now we are ready to code, let's go...

Iteration 1 - Creating the Cannon

Steps

  • Start Visual Studio
  • Click Create a new project
  • From the list of available project types, select Console App (be sure to select the C# version and not the VB version) and click Next
  • Call the application CannonAttack and click Next
  • Select .NET 7.0 (Standard Term Support) from the Framework dropdown and click Create
  • When the solution has loaded right click on the solution in Solution Explorer and select ADD -> NEW PROJECT
    • If you can't see the Solution Explorer expand the View menu and choose Solution Explorer
  • Select xUnit Test Project and click Next
  • Call the test project CannonAttackTest and click Next
  • Select .NET 7.0 (Standard Term Support) from the Framework dropdown and click Create
  • Rename UnitTest1.cs to CannonAttackTest.cs
  • If you see the following select YES
  • You should see the Solution Explorer as:
  • Open the code window for the CannonAttackTest.cs file and you should see the default test method
    • The [Fact] attribute above a method in this file indicates VS2022 will add this method as a unit test
  • Rename the default test method to CannonShouldHaveAValidId
  • Add the code var cannon = new Cannon(); to the body of the unit test method
  • Your updated unit test should look like this:
[Fact]
public void CannonShouldHaveAValidId()
{
    var cannon = new Cannon();
}
  • You'll notice an error because the type Cannon does not exist yet
  • Place your insertion point on Cannon and use the CTRL+. keyboard shortcut to get the Intellisense menu to display
  • Choose Generate new type... from the Intellisense menu
  • Select CannonAttack from the Project dropdown and choose the Create new file radio button
    • Leave the default new file name as Cannon.cs
  • At the top of the test file, make sure to add a using statement for the CannonAttack namespace
  • Update the test method to this:
[Fact]
public void CannonShouldHaveAValidId()
{
    var cannon = new Cannon();
 
    Assert.NotNull(cannon.Id);
}
  • Once again the solution will not compile because the Cannon type does not have an Id property
  • Use the CTRL+. keyboard shortcut to get the Intellisense menu to display
  • This time choose Generate property 'Id' from the Intellisene menu
    • This creates an auto property which will meet our needs for now
  • The solution should compile at this point
  • Save all projects and run all tests by using the Test Explorer
    • If you can't see the Test Explorer expand the Test menu and choose Test Explorer
    • To run all tests, click the button with a solid green arrow on top of a green arrow outline
  • The test failed, which is correct and expected as the first phase of TDD is RED - failed!!!
  • Now that we have a red test, let's get this test to pass
  • Select Cannon.cs in the CannonAttack project and make the following change to the Id property
public string Id
{
    get
    {
        return "Human";
    }
}
  • Run all tests again by using the Test Explorer
    • You can also repeat the last test by using the keyboard shortcut CTRL+R, l
  • All tests should now be passing and your Test Explorer should look similar to this:
  • We have just completed the second stage of a TDD cycle, GREEN - Pass!!!
[editor's note: I've added the next few steps so that unit tests are written to verify the default value is used unless a value is provided]
  • Add two more unit tests called CannonShouldUseDefaultValueForIdWhenNoValueIsProvided and CannonShouldUseProvidedValueForIdWhenAValueIsProvided that looks like this:
[Fact]
public void CannonShouldUseDefaultValueForIdWhenNoValueIsProvided()
{
    var cannon = new Cannon();
 
    Assert.Equal("Human", cannon.Id);
}
[Fact]
public void CannonShouldUseProvidedValueForIdWhenAValueIsProvided()
{
    var cannon = new Cannon
    {
        Id = "Test Value"
    };
 
    Assert.Equal("Test Value", cannon.Id);
}
  • The second test won't compile right now, and the first test should pass
  • Modify the Cannon class to look like this so the code compiles and the third test fails:
namespace CannonAttack
{
    public class Cannon
    {
        public Cannon()
        {
        }
 
        public string Id
        {
            get
            {
                return "Human";
            }
            set { }
        }
    }
}
  • We now have three tests, two that pass and one that fails; it is time to refactor our code
  • Make the following changes to the Cannon class so that all three tests pass:
namespace CannonAttack
{
    public sealed class Cannon
    {
        private readonly string CANNONID = "Human";
        private string CannonId;
 
        public string Id
        {
            get
            {
                return string.IsNullOrWhiteSpace(CannonId) ? CANNONID : CannonId;
            }
            set
            {
                CannonId = value;
            }
        }
    }
}

We have made this class sealed so that it is not inherited by anything. Also, we have added a readonly string to store a default ID if not set by the user. I am going to use runtime constants (readonly) because they are more flexible than compile time constants (const) and if you are interested check out Bill Wagner's book (effective C# - 50 Ways to Improve your C#) for further details.

Let's run the tests again. Again they should compile and pass because although we have made some changes to the code, we should not have impacted the tests and this is an important part of the Refactor phase. We should make the changes we need to make the code more efficient and reusable, but it is critical that the same tests that we made pass in the Green phase still pass in the Refactor phase.

The refactoring is complete. Now for ITERATION 2 of the CannonAttack project.

Iteration 2 - One Cannon, and only one Cannon - Using the Singleton Pattern.

Like the previous iteration we will pick an element of functionality and work through the same sequence again RED->GREEN->REFACTOR. Our next requirement is to allow only a single player. Given that we can allow 1 player we really should only use one instance, let's create a test for only one instance of the cannon object. We can compare two objects to ensure they are pointing at the same instance like (obj == obj2).

  • Add a new test beneath the three we already have in CannonAttackTest.cs that looks like this:
[Fact]
public void CannonCannotBeCreatedMoreThanOnce()
{
    var cannon = new Cannon();
    var cannon2 = new Cannon();
 
    Assert.Equal(cannon, cannon2);
}
  • Run all tests using Test Explorer and you'll see the new test fail, so we are at stage 1 of TDD again (RED)
  • The reason that this failed is we have created two different instances. What we need is the singleton pattern to solve our problem. I have a great book on patterns called HEAD FIRST DESIGN PATTERNS if you want to know more about design patterns it's a great start - sure its Java but the code is so close to C# you should not have any real problems.
  • We are going to use the singleton pattern to meet the requirement of a single player. Really we don't want multiple instances of cannons hanging around - 1 and only 1 instance is needed. Insert the following Singleton code below the property for the ID.
namespace CannonAttack
{
    public sealed class Cannon
    {
        private readonly string CANNONID = "Human";
        private string CannonId;
        private static Cannon cannonInstance;
 
        private Cannon()
        {
        }
 
        public static  Cannon GetInstance()
        {
            if (cannonInstance == null)
            {
                cannonInstance = new ();
            }
 
            return cannonInstance;
        }
        public string Id
        {
            get
            {
                return string.IsNullOrWhiteSpace(CannonId) ? CANNONID : CannonId;
            }
            set
            {
                CannonId = value;
            }
        }
    }
}
  • If we try to run the tests we won't compile because the cannon object can't be created with Cannon cannon = new Cannon(); So make sure that we use Cannon.GetInstance() instead of new Cannon(). The four test methods should now look like:
[Fact]
public void CannonShouldHaveAValidId()
{
    var cannon = Cannon.GetInstance();
 
    Assert.NotNull(cannon.Id);
}
 
[Fact]
public void CannonShouldUseDefaultValueForIdWhenNoValueIsProvided()
{
    var cannon = Cannon.GetInstance();
 
    Assert.Equal("Human", cannon.Id);
}
 
[Fact]
public void CannonShouldUseProvidedValueForIdWhenAValueIsProvided()
{
    var cannon = Cannon.GetInstance();
    cannon.Id = "Test Value";
 
    Assert.Equal("Test Value", cannon.Id);
}
 
[Fact]
public void CannonCannotBeCreatedMoreThanOnce()
{
    var cannon = Cannon.GetInstance();
    var cannon2 = Cannon.GetInstance();
 
    Assert.Equal(cannon, cannon2);
}
  • Run the tests again

This time they pass GREEN so time to refactor. We are going to change our Singleton code because although the code works (and is pretty much 100% the same as the singleton code in the HEAD FIRST DESIGN PATTERNS Book) it is not thread safe in C# (see http://msdn.microsoft.com/en-us/library/ff650316.aspx) So we replace the original singleton code with:

namespace CannonAttack
{
    public sealed class Cannon
    {
        private readonly string CANNONID = "Human";
        private string CannonId;
        private static Cannon cannonInstance;
        static readonly object padlock = new object();
 
        private Cannon()
        {
        }
 
        public static  Cannon GetInstance()
        {
            lock(padlock)
            {
                if (cannonInstance == null)
                {
                    cannonInstance = new ();
                }
 
                return cannonInstance;
            }
        }
 
        public string Id
        {
            get
            {
                return string.IsNullOrWhiteSpace(CannonId) ? CANNONID : CannonId;
            }
            set
            {
                CannonId = value;
            }
        }
    }
}

The block inside the lock ensures that only one thread enters this block at any given time. Given the importance of determining if there is an instance or not, we should definitely use the lock.

  • Run the tests again and they should all still pass

That's the end of the second iteration and I think you should be getting the hang of it by now, so lets get onto the 3rd iteration.

Iteration 3 - Angling for something...

We will add another couple of test methods. This time we want to ensure that an incorrect angle (say 95 degrees) will not hit. So we need a Shoot method and a return type (lets keep it simple and make it a Boolean for now).

  • Add the following test below the last test:
[Fact]
public void CannonShouldNotFireWithAnAngleTooLarge()
{
    var cannon = Cannon.GetInstance();
 
    Assert.False(cannon.Shoot(95, 100));
}
 
[Fact]
public void CannonShouldNotFireWithAnAngleTooSmall()
{
    var cannon = Cannon.GetInstance();
 
    Assert.False(cannon.Shoot(-1, 100));
}
  • Of course the compiler will complain and we get it to compile by putting the insertion point on Shoot and using the CTRL+. keyboard shortcut to view the Intellisense menu and choosing Generate method 'Shoot'
  • Run all tests from Test Explorer to see that all of the previous tests pass, but the two new tests fail
  • Open Cannon.cs in the CannonAttack project and replace the generated Shoot method with:
public bool Shoot(int angle, int velocity)
{
    if (angle > 90 || angle < 0)
    {
        return false;
    }
 
    return true;
}
  • Run all of the tests again and you'll see they all pass so we're back to the GREEN stage again so it's time to refactor
  • We need to add two extra readonly integers to the class
    • We will add them as public so they are exposed to the console application eventually
public static readonly int MAXANGLE = 90;
public static readonly int MINANGLE = 1;
  • Now refactor the Shoot method to use the new constants
public (bool, string) Shoot(int angle, int velocity)
{
    if (angle > MAXANGLE || angle < MINANGLE)
    {
        return (false, "Angle Incorrect");
    }
 
    return (true, "Angle OK");
}

We have changed the interface of the method so it returns a Tuple indicating if its a hit (BOOL) and also a message (STRING) containing the display text. The Tuple is a feature of C# 4.0. used to group a number of types together and we have used it in conjunction with the type inference of the var to give a neat and quick way to handle the messages from our shoot method. See the following article for further information:http://www.davidhayden.me/2009/12/tuple-in-c-4-c-4-examples.html

  • To handle the change to the Shoot method, we change our two newest tests to:
[Fact]
public void CannonShouldNotFireWithAnAngleTooLarge()
{
    var cannon = Cannon.GetInstance();
 
    var shot = cannon.Shoot(95, 100);
 
    Assert.False(shot.Item1);
}
 
[Fact]
public void CannonShouldNotFireWithAnAngleTooSmall()
{
    var cannon = Cannon.GetInstance();
 
    var shot = cannon.Shoot(0, 100);
 
    Assert.False(shot.Item1);
}

[editor's note: in newer versions of C# you can further refactor this, as shown below]

[Fact]
public void CannonShouldNotFireWithAnAngleTooLarge()
{
    var cannon = Cannon.GetInstance();
 
    var (shotStatus, _) = cannon.Shoot(95, 100);
 
    Assert.False(shotStatus);
}
 
[Fact]
public void CannonShouldNotFireWithAnAngleTooSmall()
{
    var cannon = Cannon.GetInstance();
 
    var (shotStatus, _) = cannon.Shoot(0, 100);
 
    Assert.False(shotStatus);
}

At this point we can refactor our tests, which is an important (though frequently overlooked) part of TDD. Since we're always going to create the Cannon instance the same way, we can move that into the constructor of our CannonAttackTest class so it is automatically run before each test.

The entire CannonAttackTest class now looks as you see below. I'm going to stop here and work on a Part 2 later because this post is long and I have other stuff I need to get done today.

using CannonAttack;
 
namespace CannonAttackTest
{
    public class CannonAttackTest
    {
        private readonly Cannon _cannon;
 
        public CannonAttackTest()
        {
            _cannon = Cannon.GetInstance();
        }
 
        [Fact]
        public void CannonShouldHaveAValidId()
        {
            Assert.NotNull(_cannon.Id);
        }
 
        [Fact]
        public void CannonShouldUseDefaultValueForIdWhenNoValueIsProvided()
        {
            Assert.Equal("Human", _cannon.Id);
        }
 
        [Fact]
        public void CannonShouldUseProvidedValueForIdWhenAValueIsProvided()
        {
            _cannon.Id = "Test Value";
 
            Assert.Equal("Test Value", _cannon.Id);
        }
 
        [Fact]
        public void CannonCannotBeCreatedMoreThanOnce()
        {
            var cannon2 = Cannon.GetInstance();
            Assert.Equal(_cannon, cannon2);
        }
 
        [Fact]
        public void CannonShouldNotFireWithAnAngleTooLarge()
        {
            var (shotStatus, _) = _cannon.Shoot(95, 100);
 
            Assert.False(shotStatus);
        }
 
        [Fact]
        public void CannonShouldNotFireWithAnAngleTooSmall()
        {
            var (shotStatus, _) = _cannon.Shoot(0, 100);
 
            Assert.False(shotStatus);
        }
    }
}

Monday, July 10, 2023

.Net Dependency Injection Options

I was asked today by a colleague about the differences between AddScoped, AddTransient, and AddSingleton. I generally know the differences, but struggled to explain them to her. Fortunately, a quick Google search lead me to a super easy answer.

First, here's the full Stack Overflow answer. There's a TL;DR right at the top that says:

  •  Transient objects are always different; a new instance is provided to every controller and every service
  • Scoped objects are the same within a request, but different across different requests
  • Singleton objects are the same for every object and every request 

That summary comes from this Microsoft tutorial on configuring Dependency Injection.

Monday, April 17, 2023

Unit Test and Code Coverage Reports with Angular

I recently endeavored to have code coverage available as a metric in Azure DevOps for my Angular application. It turned out to be a little bit (but not much) trickier than I thought it would be so here I am.

I started with this two part article on how to get started, but had to make a few tweaks. You should go read those posts if they're still up, then come back here and skip down to the heading "The Changes".

The Summary

The first thing we have to do is get our test results generated locally. The linked posts above use junit and that met my needs so that's what I'm doing as well.

  1. Install the junit karma reporter: npm install karma-junit-reporter --save-dev
  2. Configure Karma to require junit
    • In karma.conf.js, add require('karma-junit-reporter') to the plugins array (which should already require things like karma-jasmine)
  3. Configure Karma to use junit as a reporter
    • In karma.conf.js, add 'junit' to the reporters array (which should already include 'progress' and 'kjhtml')
  4. Configure the junit reporter within Karma
    • In karma.conf.js, add the following configuration as a sibling of the reporters array in the previous step:
      •         junitReporter: {
          outputDir: 'reports/junit',
          outputFile: 'unit-test-results.xml',
          useBrowserName: false
        }
        		
        		
    • This configuration uses junit to create a file named unit-test-results.xml in a folder named reports/junit as a sibling of the src folder
    • Change the values of outputDir and outputFile to use a different file/folder combination
  5. Make sure Karma is configured to use a headless browser
    • The browsers array accepts ChromeHeadless as a value (it also accepts FirefoxHeadless, but I've had issues with headless Firefox holding on to resources and crashing my system so I tend to avoid it)
  6. We can now run ng test --watch=false to run all of our unit tests once and generate test results

Now that we have test results in an XML file we want to add code coverage results as well. We're going to use karma-coverage to do that part.

  1. Install the karma coverage reporter: npm install karma-coverage --save-dev
  2. Configure Karma to require karma coverage
    • In karma.conf.js, add require('karma-coverage') to the plugins array (the same array from step 2 in the previous section)
  3. Configure Karma to use karma coverage as a reporter
    • In karma.conf.js, add 'coverage' to the reporters array
  4. Configure the karma coverage reporter the way you want it
    • When you use the Angular CLI, karma-coverage is actually configured by default, but you may want to change the values
    • You can see the default configuration by looking for the coverageReporter node that is a sibling of the reporters array
    • I changed mine to output the file in the same reporters folder as the unit test results (reports/), in a subfolder called coverage
      • dir: require('path').join(__dirname, './reports/coverage'),
    • I also removed the text-summary reporter configuration and added the cobertura reporter configuration, specifying the file name to be code-coverage.xml
      • coverageReporter: {
          dir: require('path').join(__dirname, './reports/coverage'),
          subdir: '.',
          reporters: [
            { type: 'html' },
            {
              type: 'cobertura',
              file: 'code-coverage.xml'
            }
          ]
        }
        
        
  5. We can now run ng test --watch=false --code-coverage to run all of our unit tests once and generate test results and code coverage results
  6. The last thing we need to do is exclude files from code coverage that we don't want to... well, include
    • In angular.json, locate the projects/<project name>/architect/test node and add a codeCoverageExclude property, which is an array of strings
      • In the array, specify each fully qualified path - starting with src - to exclude from code coverage

That's it! Just like that, we have unit tests and code coverage results, but now we want to display those results in Azure DevOps. It turns out that's pretty easy, too.

  1. In your package.json, create a script to run your tests without watching and with code coverage results generated, to make it easier to do this from your build
    • "test-code-coverage": "ng test --watch=false --code-coverage"
  2. Create a new build (or add a test step to your current build, but I prefer to run my unit tests when a pull request is created rather than while I'm trying to deploy something)
    • pool:
        name: Azure Pipelines
      steps:
        - task: Npm@1
          displayName: 'npm install'
          inputs:
            verbose: false
        
        - task: Npm@1
          displayName: 'Execute tests'
          input:
            command: custom
            verbose: false
            customCommand: 'run test-code-coverage'
            
        - task: PublishTestResults@1
          displayName: 'Publish Test Results - Generate Test Report'
          inputs:
            testResultsFiles: '**/reports/junit/unit-test-results.xml'
            
        - task: PublishCodeCoverageResults@1
          displayName: 'Unit Test Code Coverage Report'
          inputs:
            codeCoverageTool: Cobertura
            summaryFileLocation: '**/reports/coverage/code-coverage.xml'
      
  3. As long as you publish your test results to the pipeline (as in the sample YAML above), Azure DevOps will automatically pick up the results and display them in a nice little UI for you

Thursday, March 23, 2023

From Waterfall to Scrum: A Software Development Journey

If you've spent any time working with software developers in the past 20 years you've probably heard the terms "waterfall", "agile", and "scrum" with varying levels of derision. I've been writing software for most of those 20 years and I have some... let's say strong... feelings about those terms. Since this is my blog and I get to write whatever I want, I figured I'd take a crack at explaining these things and why they are derided, fairly and unfairly.

Waterfall

Waterfall is kind of a catch-all term most commonly used as a synonym for "the worst way to develop software" these days. In my humble opinion, that is absolutely spot-on. Waterfall sucks. An oft-cited problem with the waterfall development model is that it takes too long to deliver anything. Waterfall got its name from the way the phases of the project look in project planning software: the first phase has to complete before the next phase can begin. 


I'll give you an example: I once worked as a consultant for a major health insurance company. They had a major initiative, the details of which I can't even remember anymore, but it was a Big DealTM. To accomplish this major initiative, the Powers That Be spent a significant amount of time setting forth everything they wanted the initiative to encompass. I have no idea how long they spent on this venture, but when they were finished they turned over an 18 page document of "business requirements". One of my fellow consultants then spent several weeks (I think six, but I could be wrong) converting that business requirements document into a technical requirements document. Then we started working on the actual code. We spent six-ish months implementing every single scenario we could think of that might come up during testing in order to fulfill those technical requirements and we finally delivered what we had to QA. They destroyed it. They had built test cases around the original business requirements, we had written our code based on the technical requirements, and apparently some things had been lost in translation. The translator had since left the consulting firm. Everything was a mess. We spent another two weeks fixing the code, then two more weeks having it all tested again and it was finally ready. Roughly 8 months after it was requested. It was not what the Powers That Be had envisioned and the changes were scrapped.

If Waterfall is so bad, why did we use it? Well, because it worked. Software used to be delivered on much longer timelines. If you're old enough to think about computers and software in the '90s, we weren't cranking out new features on any kind of regular basis because there was no reason to. Even Windows didn't really have patches, and if they did they had to be shipped out on floppy disks. The Internet changed everything. With the Internet - particularly high speed Internet service - we could deliver updates to software quickly and easily to all of our customers, and our customers knew that and (rightfully) demanded bug fixes. Waterfall was still the dominant software development methodology, but that wasn't going to work for the future.

Agile

Enter Agile. I've made a few jumps from Waterfall to Agile, but ultimately those jumps are small stepping stones on the larger path from Waterfall to Agile (the generically named iterative development is one such stepping stone, which was basically waterfall, but with shorter cycles). What is Agile? This is where things start to get confusing for people, I think. Agile is "a mindset". Super helpful, right? What the hell does it mean to be "a mindset"? In this case it simply means we're going to think about delivering software differently than we used to. The Agile Mindset was delivered unto us in the form of The Agile Manifesto.

A group of software folks got together and asked themselves how they could write software better. They came up with some bullet points they could refer back to and they published those bullet points as The Agile Manifesto. The Agile Manifesto makes some suggestions about what we can do to deliver better software that our customers actually want, but doesn't tell us anything about how to do it. It's just a list of suggestions on how we might do things, based on collectively decades (at that time) of software development experience. That's all Agile is. That's it. Agile doesn't prescribe a set of activities or actions or how we define requirements or anything. It's just a general set of suggestions (a mindset) that we can follow to write and deliver better software. Full stop.

Scrum

We've finally arrived at the much-maligned Scrum. In my experience people either love it or hate it, and they usually fall on the hate side. You can find a ton of hate online for Scrum (check out r/programmerhumor on Reddit if you want a taste) or just talk to pretty much any developer who has ever been subjected to daily standups that go on for an hour and planning meetings where nothing much gets planned. They'll all say some variation of the same thing: Scrum just added meetings to my schedule and didn't actually help me deliver software any better. Unfortunately for those people - and the reputation of Scrum - that's just abusing the name of Scrum. When done properly, Scrum works. Let me start by briefly explaining what Scrum is and what it is not. Then I'll go into areas that I see go wrong the most often.

Scrum is honestly pretty simple. You can get the gist of it by reading the Scrum Guide (it takes 20 - 30 minutes), but I'll go over the basics here since you're already reading my blog. Scrum just puts a framework around the Agile methodology, dictating specific events and what must happen during those events. Scrum defines three roles that participate on a Scrum Team and what each of those roles is supposed to do.

Roles

Scrum Master

Scrum Master is probably the most hated on position in software engineering. I've heard developers say Scrum Masters are useless, they have to justify their job by making the Developers' lives hard, and they don't even bring anything valuable to the team. Scrum Masters are important, but if they're doing their job properly it looks like they're not doing much. The whole purpose of a Scrum Master is to - as I call it - shepherd the process (I'm sure I picked that up somewhere so don't go thinking too highly of my creativity). The Scrum Master introduces and supports Scrum as a concept and a framework to the Scrum Team and the organization as a whole. In the event Developers are prevented from moving forward for some reason the Scrum Master seeks to fix whatever is blocking them. It's a pretty straightforward job that I do while also being a contributing software engineer on the Scrum Team.

Product Owner

The Product Owner owns the product. S/he is responsible for working with users to figure out what to have the product do. The primary job of the Product Owner is to define what they want the product to do so the Development Team can build it. They do that by putting items on the Product Backlog (called - shockingly - Product Backlog Items) and then clarifying those requirements for the Development Team.

Developer

"Developer" comprises everyone on the Scrum Team who isn't the Product Owner or Scrum Master. Whether their skills are front-end, back-end, full-stack, testing, or anything else makes no difference. Everyone who participates on the work product to move it toward the Product Goal is a Developer. That's it.

Events

The events of Scrum are: The Sprint, Sprint Planning, Daily Scrum, Sprint Review, and Sprint Retrospective.

The Sprint

The Sprint is a bit of a unique "event" in that it is a container for all of the other events. The Sprint has a pre-defined time period of up to one month. The most common length of a Sprint in my experience is two weeks. During those two weeks, the Scrum Team works toward delivering an Increment - a set of deliverables that move the Scrum Team toward delivery of the Product Goal - while engaging in the rest of the Scrum Events.

Sprint Planning

At the beginning of the Sprint, the Scrum Team meets to review the Product Backlog - the list of all work that defines what the Product will be when it is complete - and determine which parts of the Product Backlog the Scrum Team believes they can complete during the Sprint. Product Backlog Items (PBIs) are measured by the team for their complexity: how tricky is the PBI going to be to implement, how much risk is involved in implementing it, and how much is unknown about what needs to be done. It is important (and difficult) to remember that complexity may not correlate to actual time spent on a PBI once the work is taken on. Complexity is commonly measured using story points in the Fibonacci sequence or "t-shirt sizes".

After the complexity is set for a PBI, I find it best to have the Developers break each PBI down into tasks and estimate the hours of each task. This is the best guess the Developers can make for each item based on their experience. They might end up working many more or many fewer hours on each task; these are just estimates. Once the Scrum Team agrees they have enough work to fill the duration of the Sprint, they stop bringing work in. However, the Sprint Planning meeting may continue so the Scrum Team can ask questions about future work and even perhaps assess complexity of future PBIs (without diving into the tasks of those stories). Doing this can help the Scrum Team provide an estimate for when a given PBI might get worked on.

Daily Scrum

The purpose of Daily Scrum is for the Developers to tell each other how things are going. It is an opportunity to explain to each other where they're stuck, what they learned, helpful tips about something they've been working on, or anything else relevant to the Sprint. Since Scrum Masters aren't managers and managers shouldn't be in the Daily Scrum there's no reason to provide information based on what management expects to hear. If you're doing that, you're doing Scrum wrong.

Sprint Review

In my opinion, Sprint Review and Retrospective are the most important events in Scrum (not that any of them are optional). Sprint Review is the time when the Scrum Team goes over what they accomplished during the Sprint. It is common for key stakeholders to join this meeting and provide feedback on what was delivered in the Increment as well as guidance for what they'd like to see in upcoming Increments. However, it is vital to not turn the Sprint Review into a demo. The feedback is the most important part so hearing what the Product Owner and stakeholders have to say is key to the success of the meeting. I like to tell people the Sprint Review is the opportunity to review and improve the Product so use this time to work to that end.

Sprint Retrospective

Finally we come to the Sprint Retrospective. This is an opportunity for the Scrum Team to meet alone (no management; no stakeholders; just Developers, the Scrum Master, and the Product Owner) to discuss how the Sprint went. There are lots of ways to design a good retrospective, but I like to use a site called Retromat to build a retrospective plan. As the Sprint Review is where the Scrum Team reviews and improves the Product, the Retrospective is where the Scrum Team reviews and adopts changes to the people and processes. For that reason, Retrospective has to be confidential and protected. Nothing should ever come out of Retrospective except Action Items (which are one to two concrete actions the Scrum Team agrees to take to improve). The team is free to discuss anything related to the Sprint as it relates to the team. Did someone constantly show up late to Daily Scrum? Say something. Did you feel like you did all the work in the Sprint? Say something.

The ultimate goal of the Sprint Retrospective is to be a better team, but that can't happen if people hold back their true feelings on how each sprint goes. Be open. Be honest. Be fair.

Misunderstandings

Everyone Does Scrum Differently

I think the easiest place to start is with the idea that Scrum is different at each company. That's not usually true. Scrum is a framework and while we are free to adapt within that framework we cannot operate outside the framework and still be doing Scrum. If you are doing Scrum then you are adhering to the Scrum framework. If you're not adhering to the Scrum framework then you're not doing Scrum. And that's OK! Sometimes Scrum isn't right for your organization so you can't adhere to the Scrum framework. There are lots of other methodologies out there that work very well. If you're aggravated by Scrum then look closely at what you were doing and compare that to the Scrum framework. My bet is you weren't actually doing Scrum.

Scrum Master as Team Lead/Manager

In my experience, this is commonly the result of the Scrum Masters themselves not understanding their role as a facilitator and instead falling into a team manager. I've seen Scrum Masters assign tasks to Developers. I've seen Scrum Masters ask Developers during Daily Scrum for a detailed update of the work they completed the previous day. You've probably seen worse. The Scrum Master is absolutely not above anyone and absolutely should not operate like they are.

Daily Scrum as a Report to Management

I've already touched on this, but it's worth repeating: Daily Scrum is the time for Developers to communicate with each other, not with management. Management shouldn't even be in the Daily Scrum and the Scrum Master isn't in charge of anything. Daily Scrum is just for Developers to communicate how well they're moving toward the Sprint Goal.

Daily Scrum as Only a Status Update

I'm beating a dead horse here, but it's a common gripe. Communicate. Communicate. Communicate! That's what Daily Scrum is for. If you find your Daily Scrum meetings entail a bunch of people droning on about what they did yesterday, what they plan to do today and how they have no impediments, you're probably not getting the most out of your Daily Scrum. Tell your teammates where you got stuck or what you're stuck on now, even if it doesn't seem to you like it's an impediment and even if you don't want anyone's help resolving the problem right now. It's an opportunity for someone else to say "Oh, I'm dealing with that exact same thing. What have you tried?" COMMUNICATE!

Work Can Change on the Fly

Once Sprint Planning has been completed, the work in the Sprint is fixed unless the Scrum Team agrees to change it. That explicitly does not mean the Product Owner can demand the Developers work on something new. There can be a negotiation process if priorities have changed or something urgent has come up since Sprint Planning, but it's always up to the whole team whether they change what they're working on. If you find you're constantly being asked to change work once the Sprint starts, try shortening your sprints.

Some Ceremonies are Optional

I can't stress this enough: all five Scrum Events are required. If you're not doing one of them, you're not doing Scrum. You don't cancel the Sprint Review because you don't think you completed enough. You don't cancel the Sprint Retrospective because last time no one participated. Every single event serves a purpose and every single event is absolutely required.

Conclusion

So that's it. That's my take on software development in the middle of my career. Scrum isn't perfect, but when you do it properly, it's damn good. And anything is better than waterfall.

Saturday, November 19, 2022

Testing with JsonPatchDocument in Asp.Net

I've written in the past about how to use the PATCH verb for updating individual properties on larger objects in a RESTful way. When it comes time to test those controller methods, it helps to have a way to reliably setup your test data. That's what this post is about.

I created a static class that I can use from my tests to create a JsonPatchDocument to pass to your tests. Here's what that class looks like:

   1:  using Microsoft.AspNetCore.JsonPatch;
   2:  using System;
   3:  using System.Linq.Expressions;
   4:  
   5:  namespace Blog.UnitTests.Utilities.Setup
   6:  {
   7:      public static class JsonPatchDocumentSetup
   8:      {
   9:          public static JsonPatchDocument<T> BuildJsonPatchDocument<T, TProp>(Expression<Func<T, TProp>> expr, TProp newObject, string type) where T : class
  10:          {
  11:              var jsonPatchDocument = new JsonPatchDocument<T>();
  12:  
  13:              switch (type)
  14:              {
  15:                  case "add":
  16:                      jsonPatchDocument.Add(expr, newObject);
  17:                      break;
  18:                  case "copy":
  19:                      jsonPatchDocument.Copy(expr, expr);
  20:                      break;
  21:                  case "move":
  22:                      jsonPatchDocument.Move(expr, expr);
  23:                      break;
  24:                  case "remove":
  25:                      jsonPatchDocument.Remove(expr);
  26:                      break;
  27:                  case "replace":
  28:                      jsonPatchDocument.Replace(expr, newObject);
  29:                      break;
  30:              }
  31:  
  32:              return jsonPatchDocument;
  33:          }
  34:  
  35:          public static JsonPatchDocument<T> AppendToJsonPatchDocument<T, TProp>(this JsonPatchDocument<T> jsonPatchDocument, Expression<Func<T, TProp>> expr,
                                                TProp newObject, string type) where T : class
  36:          {
  37:              switch (type)
  38:              {
  39:                  case "add":
  40:                      jsonPatchDocument.Add(expr, newObject);
  41:                      break;
  42:                  case "copy":
  43:                      jsonPatchDocument.Copy(expr, expr);
  44:                      break;
  45:                  case "move":
  46:                      jsonPatchDocument.Move(expr, expr);
  47:                      break;
  48:                  case "remove":
  49:                      jsonPatchDocument.Remove(expr);
  50:                      break;
  51:                  case "replace":
  52:                      jsonPatchDocument.Replace(expr, newObject);
  53:                      break;
  54:              }
  55:  
  56:              return jsonPatchDocument;
  57:          }
  58:      }
  59:  }

And here are some examples of how you'd use it:

   1:  using Blog.BusinessLogic.Implementations;
   2:  using System.Collections.Generic;
   3:  using System.Threading.Tasks;
   4:  using Xunit;
   5:  
   6:  namespace Blog.BusinessLogic.UnitTests.Implementations
   7:  {
   8:      public class BlogClassUnitTests
   9:      {
  10:          ...test class setup...
  11:          [Theory]
  12:          [InlineData(true, true)]
  13:          [InlineData(true, false)]
  14:          [InlineData(true, null)]
  15:          [InlineData(null, true)]
  16:          [InlineData(null, false)]
  17:          [InlineData(null, null)]
  18:          [InlineData(false, true)]
  19:          [InlineData(false, false)]
  20:          [InlineData(false, null)]
  21:          public async Task DoingTheThingShouldDoWhatIsExpectedUsingInlineData(bool? firstUpdatedValue, bool? secondUpdatedValue)
  22:          {
  23:              // arrange
  24:              var jsonPatchDocument = JsonPatchDocumentSetup.BuildJsonPatchDocument<UpdateableViewModel, bool?>(p => p.FirstUpdateableProperty, firstUpdatedValue, "replace");
  25:              jsonPatchDocument.AppendToJsonPatchDocument(p => p.SecondUpdateableProperty, secondUpdatedValue, "replace");
  26:              ...additional setup...
  27:  
  28:              // act
  29:              ...take action...
  30:  
  31:              // assert
  32:              ...make assertions...
  33:          }
  34:  
  35:          public static IEnumerable<object[]> BlogTestCases
  36:          {
  37:              get
  38:              {
  39:                  return new[]
  40:                  {
  41:                      new object[]
  42:                      {
  43:                          new UpdateableViewModel
  44:                          {
  45:                              FirstUpdateableProperty = true
  46:                              NestedUpdateableProperty = new NestedUpdateableViewModel()
  47:                              SecondUpdateableProperty = false
  48:                          },
  49:                          (Expression<Func<UpdateableViewModel, NestedUpdateableViewModel>>)(p => p.NestedUpdateableProperty),
  50:                          new NestedUpdateableViewModel{Id = 987},
  51:                          new UpdateableViewModel
  52:                          {
  53:                              FirstUpdateableProperty = true
  54:                              NestedUpdateableProperty = new NestedUpdateableViewModel{Id = 987}
  55:                              SecondUpdateableProperty = false
  56:                          }
  57:                      }
  58:                  }
  59:              }
  60:          }
  61:  
  62:          [Theory, MemberData(nameof(BlogTestCases))]
  63:          public async Task DoingTheThingShouldDoWhatIsExpectedUsingMemberData<TProp1, TProp2>(UpdateableViewModel currentViewModel, Expression<Func<UpdateableViewModel, TProp1>> expression, TProp1 newObject, UpdateableViewModel expected)
  64:          {
  65:              // arrange
  66:              var jsonPatchDocument = JsonPatchDocumentSetup.BuildJsonPatchDocument&lt(expression, newObject, "replace");
  67:              ...additional setup...
  68:  
  69:              // act
  70:              ...take action...
  71:  
  72:              // assert
  73:              ...make assertions...
  74:          }
  75:      }
  76:  }

Hopefully next time I need this I remember I wrote this post, or maybe somebody else comes across this and it helps them with unit testing their own code. Maybe some day I'll turn this into a NuGet package. Ah, to dream.

Friday, July 22, 2022

Dynamic QueryParams with RouterLink

This is something that should have been obvious, but still required me to find an answer on SO. As I do, I'm putting it here to make it easier to find in the future. Basically I wanted to use an anchor tag with the routerLink directive, but send out dynamic parameters and I couldn't figure out how to do it. It turns out you can just bind the queryParams part of routerLink to a property on your component and pass it that way.

component.ts

<snip>...<snip>
public dynamicQueryParams: Params;
<snip>...<snip>
this.dynamicQueryParams = {first: 'value', second: 'empty'};
<snip>...<snip>

component.html

<snip>...<snip>
<a [routerLink]="['/support/case-history']" [queryParams]="dynamicQueryParams" target="_blank">Click Me!</a>
<snip>...<snip>
That's pretty much it. Obviously, you'll need to build your dynamicQueryParams variable the way you want it, but this is how you can pass dynamic parameters via a routerLink directive.

Monday, July 18, 2022

No value accessor for form control with name

 I'm going to be brief here and hopefully circle back to this with more details later. As of today (July 18, 2022) there is an open issue in the Angular Github repository stating the team should try to improve this error message. I wish they would because today I wasted two hours on something really stupid. Ultimately, it was my fault for doing the wrong thing, but the whole point of error messages should be to help you find and fix the error.

Anyway, what happened to me today is I was using a 3rd party component (Kolkov Angular Editor) and it was throwing an error that originally said "No value accessor for form control with unspecified name attribute". After some trial and error and moving a bunch of stuff around I thought I had identified that the problem was caused by the 3rd party control itself and there was some bug in it. I applied a workaround that fixed it in one place, but not the other. Eventually, I was able to piece together that I was importing the 3rd party module in my wrong module.

My app is broken up (as it should be) into separate modules by purpose. Then I have one SharedModule where I declare, import, and export anything that's used across multiple modules in my app. When I originally built the piece that uses the Kolkov Angular Editor I had it all in one module, but as my app grew I moved some of my components into my SharedModule, but left the importation of the Kolkov module in its original place. That was the root of my problem. Once I moved the import (and export) into my SharedModule everything worked perfectly. And it only took me two hours to figure out.