Azure Functions Continuous Deployment with Azure Pipelines: Part 7 - Running Functional End-to-end Tests in a Release Azure Pipeline

This is the seventh part in a series demonstrating how to setup continuous deployment of an Azure Functions App using Azure DevOps build and release pipelines.

Demo app source code is available on GitHub.

In the previous instalment we created the release pipeline and now have continuous deployment working. Currently if the unit tests pass (and the rest of the build is ok) in the build pipeline, the release pipeline will automatically execute and deploy the  Function App to Azure.

In this instalment of this series we’ll add some additional stages in the release pipeline to deploy first to a test Function App in Azure, then run some functional test against this test deployment, and only if those tests pass, deploy to the production Function App.

Deploying a Function App to Test

The first step is to edit the release pipeline that we created earlier in this series and add a new stage. A quick way to do this is to click the Clone button on the existing “Deploy to Production” stage:

Cloning a stage in a release Azure pipeline

Change the name of the cloned stage to “Deploy to Production” and the original stage to “Deploy to Test”, it should now look like the following screenshot:

Release Azure Pipeline

Next edit the tasks in the “Deploy to Test” phase, click the Disable Testing Functions task and click Remove to delete the task from the test stage.

We need to change the deployment target, so click the Azure App Service Deploy task and change the App Service name to “InvestFunctionAppDemoTest” – we want to deploy to the test Azure Function App not the production one.

Creating Functional End-to-End Tests

If you check out the demo source code on GitHub you can see the end-to-end test project.

The AddToPortfolioFunctionShould test class contains a test called BuyStocks. This test performs the following logical steps:

  1. Create a new test Investor in Azure Table storage (by calling the test Azure function CreateInvestor)
  2. Call the Portfolio function to add funds to a portfolio
  3. Wait for a while
  4. Check that the test Investor created in  step 1 has had its stock value updated – this is done by called the test function GetInvestor

Side Note: In this example we’ve create 2 additional Azure Functions to help facilitate testing, one to create a test investor and one to retrieve Investor details so we can assert against the final result. We could have just manipulated Azure Table storage directly within the test methods but I wanted to show this approach to demonstrate a number of features such as automating function disabling as part of deployments and passing pipeline variables to test code. Normally we don’t want to deploy testing-related items to production for a whole host of reasons (performance, security, data integrity, etc.), but this approach if properly managed and secured can be quite a useful quick win. If you do implement these kind of test functions you must ensure that they cannot be called if deployed to production by restricting them at multiple layers: first by securing the functions with a secret function key and second by ensuring as part of the deployment the testing functions are disabling in the app settings. You could even add a 3rd level of checking by asserting that the function is executing in a testing environment with like an AssertInTestEnvironment being called at the start of each test function. All that being said, deploying test functions to production adds all this additional complexity and risk and  so is best avoided.

There’s a few things that this end-to-end test needs.

Firstly it needs to know the Azure Function keys for the two test functions and also the Portfolio function. We don’t want to commit these keys to source control, so we can instead create pipeline variables for them and then access them via environment variables in the C# test code by using Environment.GetEnvironmentVariable(variableName).

Secondly there is an extra level of checking around the test functions being able to be called in production. The functions will be disabled in production, in addition to being protected by a function key. Whilst these two things should make it impossible for them to be called, there is an extra check implemented in the following class:

internal class Testing
{
    internal const string TestFunctionRoute = "testing";
    internal const string TestEnvironmentConfigKey = "InvestFunctionApp.IsTestEnvironment";

    /// <summary>
    /// Testing functions should be disabled in Azure, this is an additional level of checking.
    /// </summary>
    internal static void AssertInTestEnvironment(ILogger log)
    {
        var value = Environment.GetEnvironmentVariable(TestEnvironmentConfigKey);

        var isTestingEnvonment = value != null && value == "true";

        if (!isTestingEnvonment)
        {
            log.LogError("This function should be disabled in non-testing environments but was called. Check that all testing functions are disabled in production.");
            throw new InvalidOperationException();
        }
    }
}

The AssertInTestEnvironment method is called in the test functions that should be disabled in production, for example:

namespace InvestFunctionApp.TestFunctions
{
    public static class CreateInvestor
    {
        [FunctionName("CreateInvestor")]
        [return: Table("Portfolio")]
        public static async Task<Investor> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = Testing.TestFunctionRoute + "/createinvestor")] HttpRequest req,            
            ILogger log)
        {
            Testing.AssertInTestEnvironment(log);

            log.LogInformation("C# HTTP trigger function processed a request.");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            return JsonConvert.DeserializeObject<Investor>(requestBody);
        }
    }
}

For the test functions to be enabled in the test Azure Function App, there needs to be an application setting called “InvestFunctionApp.IsTestEnvironment” set to “true”.

Adding Function Keys as Pipeline Variables

As we did earlier in the build pipeline, we can add variables to the release pipeline, the variables are called CreateInvestorFunctionKey, GetInvestorInvestorFunctionKey, and PortfolioFunctionKey. The values of these should be the function keys of those functions deployed to the test environment. You may need to create a release and run the release pipeline first to deploy the app to test before you can get the test function keys (a bit of the chicken and the egg here).

Release Azure Pipeline variables

Notice in the preceding screenshot that we’re not marking these test keys as secret, though we should ideally go and do that but it introduces a little extra complexity if we want to access them as environment variables in the C# test code.

Adding a Functional End-to-End Test Stage

Now we have the app deployed to test, we want to run the functional tests against it.

To do this we create a new empty stage called “Run Functional Tests” and modify the “Deploy to Production” stage trigger to be run after the new testing stage completes as the following screenshot shows:

Stages in a release Azure Pipeline

Notice here that we’re creating a completely new stage to run the functional tests, this is for demonstration purposes to show the flexibility of being able to design your release pipeline however you want though this approach doesn’t align fully with the conceptual idea of a stage being a: “logical and independent entity that represents where you want to deploy a release generated from a release pipeline.” [Microsoft] . It does however conform to the idea that a stage is a “logical entity that can be used to perform any automation”[Microsoft]. In any case, it is more likely that in a real scenario we wouldn’t create a new stage just to run the functional tests. What we could have instead are a couple of stages, one called “QA” (a testing environment deployment) and one called “Production”. We could then run the functional tests as a separate task in the “QA” stage. You should make sure you read the documentation  to fully understand what stages are and the nuances such as “Having one or more release pipelines, each with multiple stages intended to run test automation for a build, is a common practice. This is fine if each of the stages deploys the build independently, and then runs tests. However, if you set up the first stage to deploy the build, and subsequent stages to test the same shared deployment, you risk overriding the shared stage with a newer build while testing of the previous builds is still in progress” [Microsoft].Another option would be to create a new Function App in Azure (with a unique name) for each execution of the stage, run the functional tests against it, and then delete the Function App.The great thing about Azure Pipelines is the flexibility they offer, however this flexibility comes at the cost of potentially shooting yourself in the foot.In the future I intend to write more about good practices and concepts when designing pipelines.

Continuing with the demo scenario, we now need a task in the new testing stage to execute dotnet test on the functional end-to-end test project.

To do this we can add a .Net Core task, set the command to test and the path to project as “$(System.DefaultWorkingDirectory)/_InvestFunctionApp/e2etests/InvestFunctionApp.EndToEndTests/InvestFunctionApp.EndToEndTests.csproj” (notice in this path we’re accessing the e2etests artifact created in the YAML build).

Setting Test Azure Function Application Settings

When deploying to the test Function App in Azure we need to set the application setting “InvestFunctionApp.IsTestEnvironment” to “true”. Rather than using Azure CLI we can do this as part of the Azure App Service Deploy task in the Application and Configurations Settings as the following screenshot shows:

Setting Azure Function App settings when deploying from Azure Pipelines

Testing the Updated Release Pipeline

Once you’ve made all these changes and saved them you can queue up another manual release to see if everything works. Just click the + Release button and choose “Create a release”. Specify the latest build in the artifacts section and click Create.

This will queue and start a new release:

Azure release Pipeline executing

After a while the release should complete and all stages should complete successfully:

Azure release Pipeline executing

Clicking on the “Run Functional Tests” stage and then the Tests tab you can see the “AddToPortfolioFunctionShould.BuyStocks” test executed and passed:

Passing tests in an Azure Pipeline

 

In the final part of this series, we’ll see how to execute a smoke test against the deployed production Function App to verify at a high level that all is well with the deployment.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Azure Functions Continuous Deployment with Azure Pipelines: Part 6 - Creating an Azure DevOps Release Pipeline

This is the sixth part in a series demonstrating how to setup continuous deployment of an Azure Functions App using Azure DevOps build and release pipelines.

Demo app source code is available on GitHub.

In the previous instalment we saw how unit tests are executed as part of the build pipeline and that if tests fail then the build fails. If the tests (and the rest of the build) succeed, a release pipeline can be triggered to deploy the Function App automatically to Azure.

Creating an Initial Function App Release Pipeline

In your Azure DevOps project, click Pipelines and Releases.If you have no pipelines currently defined you will see a “No release pipelines found” message and a New Pipeline button.Clicking this button will start the release creation wizard, the first step of which is to choose a starter template (or start with an empty job).You should explorer these templates to get an idea for what’s possible. For the purposes of this series, click the Empty job link:

Selecting a release Azure Pipeline template

This will automatically create a stage in the new release pipeline, rename this stage to “Deploy To Production” and click the close button:

Naming a release Azre Pipeline stage

Adding Build Artifacts to a Release Pipeline

Next we need to add artifacts from the build pipeline to be used in the release. To do this click the Add link in the Artifacts section.

Leave the source type as Build (artifacts are being published from a build pipeline). As the source, select the build definition that we created earlier in this series. For the source alias enter “_InvestFunctionApp” and click Add:

Adding artifacts to an Azure release Pipeline

Adding an Azure App Service Deploy Release Task

In the “Deploy to production” stage click the “1 job, 0 task” link:

Editing Azure Pipeline release stage tasks

Click the + button next to “Agent job” to show a list of prebuilt release tasks that can be added to the stage:

Adding Azure Pipeline release stage tasks

In the search box type “app service deploy” and click the Azure App Service Deploy task that shows up, then click the Add button to add the task to the stage:

Deploying a Function App using the Azure App Service Deploy task

You will now have a new task in the stage that will state: “Some settings need attention”. Click on the newly added Azure App Service Deploy task to configure it.

First you’ll need to specify the Azure subscription you want to deploy into - you will need to click the Authorize button and go through the authorization process.

Next change “App type” to “Function App”.

From the App Service name dropdown select the Function App you want to deploy into, in this series it’s the InvestFunctionAppDemo function created earlier in this series in the Azure Portal.

The next step is to choose what to deploy. To do this use the “” next to Package or folder. This will allow you to choose the artifact that was created in the build pipeline. In this case we want to select the “app” artifact that contains the published Function App:

Selecting a deployment artifact

Select the “app” folder and click OK. You should see something similar to the following screenshot though your Azure Subscription details will be different:

Configuring a Azure App Service Deploy task

Disabling Testing Azure Functions in Production

Later in this series we’ll be adding functional end-to-end tests that make use of a number of test functions. We do not want these test functions to be enabled in the deployed production Function App in Azure. (In the next part in the series we’ll discuss these testing functions more).

To disable functions in Azure Functions V2, a setting can be added in the format “AzureWebJobs.FUNCTION_NAME.Disabled” and set the value to “true”.

One way to do this as part of the release to production is to use the Azure CLI.

To execute Azure CLI commands, you can add a new task to the stage, the Azure CLI task. This task should come before the Azure App Service Deploy task. The task can be configured as follows:

  • Display name: Disable Testing Functions
  • Azure subscription: your Azure Subscription / resource group that contains the Function App you are deploying to
  • Script location: Inline script (you could also supply a path to a source controlled script)

The contents of the inline script box are as follows:

call echo These could also be set in the Azure App Service Deploy task in the Application and Configuration Settings
call echo We are doing it this way so we ca have a separate task in the stage to make it more obvious
call az webapp config appsettings set -g DontCodeTiredDemos -n InvestFunctionAppDemo --settings AzureWebJobs.CreateInvestor.Disabled=true
call az webapp config appsettings set -g DontCodeTiredDemos -n InvestFunctionAppDemo --settings AzureWebJobs.GetInvestor.Disabled=true

This preceding script uses the az webapp config appsettings set command specifying the resource group “-g DontCodeTiredDemos”, the name of the Function App “-n InvestFunctionAppDemo” and the setting name and value “--settings AzureWebJobs.CreateInvestor.Disabled=true”.

Once configured, click the Save button and move the task above the deploy task as the following screenshot shows:

Azure release pipeline stage tasks

At this point you can also name the release pipeline by clicking “New Release Pipeline” at the top and choosing your own name, for example “InvestFunctionAppReleasePipeline”.

Enabling Continuous Deployment in an Azure Release Pipeline

If you want the release pipeline to automatically start when the build pipeline finishes, head back to the pipeline view and click the lightening bolt icon in the Artifacts area and enable the continuous deployment toggle switch as the following screenshot shows:

image

 

If you get a “Problem connecting to the service” error message it may not actually prevent the CD trigger but  you may want to refer here.

Click the Save button.

Creating a Manual Release

Click the + Release button at the top right and click Create a release.

Choose the latest build from the build pipeline in the Artifacts section and click the Create button.

You will see a message saying “Release Release-1 has been created” with a handy link to click – click this and it will take you to the release being executed:

Azure release pipeline being executed

And once the release pipeline has executed you should see a “Succeeded” message in the Deploy to Production stage.

If you head over to the Function App in the Azure Portal you should see the functions deployed, along with the two testing functions being disabled and the deployment showing for Release 1 as the following screenshot shows:

Deployed Function App from Azure Pipelines

Testing the CI Trigger

To test the CI trigger, make a change in the repository, commit the change, and then push to GitHub.

After a short while, the build pipeline should notice the change and kick off a new run of the build pipeline.

Once the build pipeline completes without error, the release pipeline should be automatically triggered and the changes (for Release 2) automatically deployed to the production Function App in Azure.

So now, we can add a feature or fix a bug, push the changes, and within a few minutes have the new feature or fix in production without us having to do anything else.

In the next instalment in this series we’ll add some functional end-to-end tests because at the moment ,all we have are unit tests in the build pipeline to verify correctness.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Azure Functions Continuous Deployment with Azure Pipelines: Part 5 - Adding Unit Tests

This is the fifth part in a series demonstrating how to setup continuous deployment of an Azure Functions App using Azure DevOps build and release pipelines.

Demo app source code is available on GitHub.

In the demo app solution there is a testing project. This project contains unit tests that can be run automatically as part of the build pipeline.

As an example, take the following function:

public static class Portfolio
{
    [FunctionName("Portfolio")]
    [return: Queue("deposit-requests")]
    public static async Task<DepositRequest> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "portfolio/{investorId}")]HttpRequest req,
        [Table("Portfolio", InvestorType.Individual, "{investorId}")] Investor investor,
        string investorId,
        ILogger log)
    {
        log.LogInformation($"C# HTTP trigger function processed a request.");

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

        log.LogInformation($"Request body: {requestBody}");

        var deposit = JsonConvert.DeserializeObject<Deposit>(requestBody);

        if (investor == null)
        {
            throw new ArgumentException($"Invalid investorId '{investorId}.");
        }
        if (deposit is null)
        {
            throw new ArgumentException($"Invalid deposit.");
        }
        if (deposit.Amount <= 0)
        {
            throw new ArgumentException($"Deposit amount must be greater than 1.");
        }

        // Additional validation omitted for demo purposes

        var depositRequest = new DepositRequest
        {
            Amount = deposit.Amount,
            Investor = investor
        };

        log.LogInformation($"Deposit created: {depositRequest}");

        return depositRequest;
    }
}

The function Run method has a number of parameters that need to be satisfied when executed: HttpRequest, Investor, investorId, and an ILogger. The ILogger and HttpRequest can be mocked using Moq:

public class AddToPortfolioShould
{
    [Fact]
    public async Task ReturnCorrectDepositInformation()
    {
        var deposit = new Deposit { Amount = 42 };
        var investor = new Investor { };

        Mock<HttpRequest> mockRequest = CreateMockRequest(deposit);

        DepositRequest result = await Portfolio.Run(mockRequest.Object, investor, "42", new Mock<ILogger>().Object);

        Assert.Equal(42, result.Amount);
        Assert.Same(investor, result.Investor);
    }

    [Fact]
    public async Task ErrorWhenInvestorDoesNotExist()
    {
        var deposit = new Deposit { Amount = 42 };

        Mock<HttpRequest> mockRequest = CreateMockRequest(deposit);

        await Assert.ThrowsAsync<ArgumentException>(() => Portfolio.Run(mockRequest.Object, null, "42", new Mock<ILogger>().Object));
    }

    private static Mock<HttpRequest> CreateMockRequest(object body)
    {            
        var ms = new MemoryStream();
        var sw = new StreamWriter(ms);

        var json = JsonConvert.SerializeObject(body);

        sw.Write(json);
        sw.Flush();

        ms.Position = 0;

        var mockRequest = new Mock<HttpRequest>();
        mockRequest.Setup(x => x.Body).Returns(ms);

        return mockRequest;
    }
}

Side note: In the preceding code, the CreateMockRequest method makes use of .NET streams, if you’re not familiar with streams or want to understand them better, check out my Working with Files and Streams in C# Pluralsight course.

There are also CalculatePortfolioAllocationShould and NaiveInvestementAllocatorShould test classes.

Executing .Net Core Unit Tests in an Azure Pipeline

Recall from earlier in this series that we defined the build using a YAML file.

There are a couple of steps that are related to unit tests.

The first is to execute dotnet test:

- script: dotnet test 'src/InvestFunctionApp/InvestFunctionApp.Tests' --configuration $(buildConfiguration) --logger trx
  displayName: 'Run unit tests'

Notice in the preceding YAML that the test project being executed is “src/InvestFunctionApp/InvestFunctionApp.Tests” and the trx option is being specified to log test results into a Visual Studio Test Results File (TRX) format file.

Once the tests execute, the results need to be made available to the pipeline  so we can see/explore the test results in the GUI. To do this the PublishTestResults@2 task can be used:

- task: PublishTestResults@2
  inputs:
    testRunner: VSTest
    testResultsFiles: '**/*.trx'

If the test(s) fail the build will fail and the release pipeline won’t execute and try and deploy a build with failing tests.

Once a pipeline build job has executed, the test results can be viewed by clicking on the Tests tab for a specific build job as the following screenshot shows:

Viewing Unit Test Result in Azure Pipelines

Note that in this series we’re using xUnit.net as the testing framework and Moq as the mocking library but you could use MSTest or NUnit and a different mocking library if you wish. If you want to learn more about  xUnit.net or Moq, check out my Pluralsight courses: Testing .NET Core Code with xUnit.net: Getting Started and Mocking in .NET Core Unit Tests with Moq: Getting Started.

In the next part of this series, now that we have a build pipeline that will run tests and create artifacts, we can create a release pipeline to automatically deploy the app to production if the build succeeds.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Azure Functions Continuous Deployment with Azure Pipelines: Part 4 - Defining and Consuming Pipeline Variables

This is the fourth part in a series demonstrating how to setup continuous deployment of an Azure Functions App using Azure DevOps build and release pipelines.

Demo app source code is available on GitHub.

In the previous instalment we created a build pipeline, defined the build steps using YAML, but when we tried to execute the build, it failed with the error:”buildConfiguration: command not found”. This is because in the YAML build definition we are referencing the $(buildConfiguration) variable which has not  yet been defined.

An Overview of Azure Pipeline Build Variables

“Variables give you a convenient way to get key bits of data into various parts of your build pipeline.” [Microsoft]

Essentially variables allow you to define key/value pairs that can be used throughout the pipeline. They can be passed to YAML build definitions for use in steps, they can be accessed as environment variables in scrips and from unit tests, and can be used in the pipeline GUI when configuring tasks.

There are a whole host of predefined variables such as Agent.JobStatus, Build.BuildNumber, System.PullRequest.PullRequestId, etc.

You can also create your own custom variables.

Creating a Custom Variable in an Azure Pipeline

First off, open the build pipeline by clicking Builds, select the build pipeline you want to edit, and click the Edit button:

Editing an Azure Pipeline build

In the Variables section click Add:

Creating a new Azure Pipeline variable

For the variable Name enter “buildConfiguration” and for the variable Value enter “Release”, also check the “Settable at queue time check box”. This variable will default to “Release” but can also be set to “Debug” when manually queuing a build if you want to create a debug version of the Function App:

Setting an Azure Pipeline variable

Notice the little padlock to the right of the value, clicking this enables you to store “sensitive values in a way that they cannot be seen or changed by users of the release pipelines” [Microsoft]

“The values of hidden (secret) variables are stored securely on the server and cannot be viewed by users after they are saved. During a deployment, the Azure Pipelines release service decrypts these values when referenced by the tasks and passes them to the agent over a secure HTTPS channel.” [Microsoft]

Another nice thing about designating a value as sensitive is that it will also be redacted in log messages produced by Azure Pipelines. Sensitive variables can also be passed to scripts – you should read the documentation and be careful about any custom logging or other output you create as you may inadvertently reveal secrets.

To save the changes, click the Save & queue dropdown and choose Save:

Saving changes to and Azure Pipeline

Once defined, build variables can be consumed in the YAML build template by using the token pattern $(VARIABLENAME), for example $(buildConfiguration):

- script: dotnet build 'src/InvestFunctionApp/InvestFunctionApp.sln' --configuration $(buildConfiguration)
  displayName: 'Build solution'

Manually Queuing an Azure Pipeline Build

To queue up a new build, click the Queue button, optionally choose a branch/commit and click Queue. (Also notice here that it’s possible to set the buildConfiguration variable to a custom value for this build or leave it as the default “Release”):

Manually Queuing an Azure Pipeline Build

Click the handy shortcut in the notification:

Manually Queuing an Azure Pipeline Build

And this will take you to the build that should start executing.

Now that we have the custom variable defined, the build should now complete successfully:

Successfull Azure Pipeline build

Also notice that the steps in the build from “Build solution” to “Publish end to end test artifact” correspond to the steps defined in the YAML and that these are set using displayName: 'Run unit tests'.

In the next instalment of this series we’ll look at how unit tests are executed as part of the build pipeline.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Azure Functions Continuous Deployment with Azure Pipelines: Part 3 - Creating an Initial Build Pipeline

This is the third part in a series demonstrating how to setup continuous deployment of an Azure Functions App using Azure DevOps build and release pipelines.

Demo app source code is available on GitHub.

In the previous instalment we created an Azure DevOps organization and project, we can now create a build pipeline by clicking the New pipeline button:

Starting to create a new build Azure Pipeline

Next we need to specify where the source code is located, this could be hosted inside Azure DevOps using Azure Repos or GitHub. The demo app is located in GitHub.

Triggering an Azure Pipeline from GitHub

Next we need to give permission from GitHub to enable the pipeline to access the repository. The easiest way to do this is to click the Install our app from the GitHub Marketplace link as shown in the following screenshot:

Installing the Azure Pipelines GitHub App

You can allow access to all current (and future) repositories or specified ones:

Installing the Azure Pipelines GitHub App

Click the Install button, confirm your GitHub password, and authorize Azure Pipelines:

Authorize Azure Pipelines

You will be redirected back to Azure DevOps where you can now select the InvestFunctionApp GitHub repository.

You can now select a starter YAML template that will define what happens during the build:

Defining a starter YAML Azure Pipeline build template

Choose the ASP.NET Core template:

Defining a starter YAML Azure Pipeline build template

And click Save and run and then Save and run again.

The build job will now run:

Initial build pipeline executing

Initial build pipeline executing

Initial build pipeline executing

This initial build will fail but don’t worry as we need to go and edit the YAML.

Defining an Azure Pipeline Build for an Azure Function App in YAML

When we chose the starter template earlier, a new file was added to the root of the repository in GitHub called azure-pipelines.yml.

This file defines the steps that make up the build using YAML schema.

Essentially there are a number of steps in a build, each step could be a handwritten custom script, calling a prebuilt task, or referencing another template.

We can  now customize the YAML to defined the build as follows:

pool:
  vmImage: 'Ubuntu 16.04'
  
steps:

- script: dotnet build 'src/InvestFunctionApp/InvestFunctionApp.sln' --configuration $(buildConfiguration)
  displayName: 'Build solution'
    
- script: dotnet test 'src/InvestFunctionApp/InvestFunctionApp.Tests' --configuration $(buildConfiguration) --logger trx
  displayName: 'Run unit tests'

- task: PublishTestResults@2
  inputs:
    testRunner: VSTest
    testResultsFiles: '**/*.trx'

- script: dotnet publish 'src/InvestFunctionApp/InvestFunctionApp/InvestFunctionApp.csproj' --configuration $(buildConfiguration) --output '$(Build.ArtifactStagingDirectory)/app'
  displayName: 'Package function app'

- task: PublishBuildArtifacts@1
  displayName: 'Publishing app artifact'
  inputs:
    pathtoPublish: '$(Build.ArtifactStagingDirectory)/app'
    artifactName: app

- task: CopyFiles@2
  displayName: 'Copy end to end tests'
  inputs:
    sourceFolder: 'src/InvestFunctionApp'
    targetFolder: '$(Build.ArtifactStagingDirectory)/e2etests'

- task: PublishBuildArtifacts@1
  displayName: 'Publish end to end test artifact'
  inputs:
    pathtoPublish: '$(Build.ArtifactStagingDirectory)/e2etests'
    artifactName: e2etests

In the preceding YAML, the displayName items help to describe what is happening and are hopefully fairly descriptive (they will also appear in the Azure Pipeline GUI/logs when builds are run).

Creating Multiple Build Artifacts in Azure Pipelines

One important thing to note in the preceding YAML, is that we are creating two separate build artifacts, one that contains only the Function App contents (for deployment to Azure) and one to be able to run the tests.

The Function App artifact is created by first calling dotnet publish and choosing the (temporary) output directory app with the switch --output '$(Build.ArtifactStagingDirectory)/app'. To actually create the build artifact that can be consumed in a release pipeline, the prebuilt PublishBuildArtifacts@1 task is called. This task takes its input from pathtoPublish: '$(Build.ArtifactStagingDirectory)/app' and will create a build artifact with the name app by virtue of the artifactName: app setting.

To create a separate artifact for the tests, the CopyFiles@2 task is used to copy (effectively the entire solution including the test projects) to the (temporary) targetFolder: '$(Build.ArtifactStagingDirectory)/e2etests'. The final PublishBuildArtifacts@1 task creates a second build artifact called e2etests.

We’ll see these artifacts used later in this series of blog posts.

The reason we create two artifacts here is to separate out what will get deployed to Azure Functions from  the code/binaries that contain the tests. What we don’t want is to publish the tests, xUnit.net DLLs etc. to the deployed function apps in Azure. This can also help the in readability of the release pipeline and potentially help to segregate things so that only the indented things get deployed.

We can now save the changes to azure-pipelines.yml  and push them to GitHub (which will actually trigger a new CI build).

Viewing Azure Pipeline Builds

Once the changes are pushed, the Azure Pipeline that we created will notice the changes and execute. If you click on Builds you will see the build running:

Azure Pipeline Build Running

Clicking in the build will show you all the steps:

Azure Pipeline build details

Notice in the preceding screenshot that the “Build solution” step is failing. Clicking it will show you the logs for the step:

Viewing Azure Pipeline build logs

Notice in the preceding screenshot the error”buildConfiguration: command not found”. This is because in the YAML build definition we are referencing the $(buildConfiguration) variable which is not yet defined.

In the next instalment of this series we’ll learn how to add variables to a pipeline and fix this problem.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Azure Functions Continuous Deployment with Azure Pipelines: Part 2 - Getting Started

This is the second part in a series demonstrating how to setup continuous deployment of an Azure Functions App using Azure DevOps build and release pipelines.

In the previous instalment we got an overview  of Azure DevOps and of the app we’ll be building/deploying.

Demo app source code is available on GitHub.

Creating Azure Function Apps

There will be two Function Apps created in Azure. One will be a self-contained test environment (with separate Azure Storage account) and to keep things simple they both belong to the same resource group. The other Function App will be the production version. For simplicity we won’t be using slots, proxies, etc.

The two Function Apps are:

  • InvestFunctionAppDemo (production)
  • InvestFunctionAppDemoTest (test)

The test environment will be used to check the deployment  artifacts and as a target to execute functional end-to-end tests against.

Once these two Function Aps have been created in the Portal, all of the deployments will happen automatically from the release pipeline, including setting test/production specific Function App settings.

If you don’t have an Azure account you can currently sign up for free.

Signing Up or Azure DevOps

Now that there are some Function Apps created in Azure to deploy to, you can deploy to them from an Azure release pipelines. To do this you’ll need to sign up to Azure DevOps.

Azure DevOps contains a number of services, in this series we’ll be using the Azure Pipeline service.

An Overview of Azure Pipelines

Azure DevOps is currently free for open source projects and small teams with some limitations on things such as parallel job execution all the way up to 1,000 users for $7,833.26 USD/month. You can find the latest pricing information as part of the product information and compare features of different plans.

You can just use Azure Pipelines on their own, you don’t have to use all of the DevOps services (Boards, etc.).

Some of the features of Azure Pipelines include:

  • Build, test, and deploy for multiple languages including .NET. Python, Java, iOS, etc.
  • Container build and publish support
  • Deploy to clouds including Azure, AWS, and Google Cloud Platform
  • 10 free parallel jobs and unlimited build minutes for all open source projects
  • Advanced workflow features, testing, reporting, gates, etc.

Azure DevOps Organizations and Projects

Once you’ve signed up for Azure DevOps you’ll need an organization and a project.

“..an organization is a mechanism for organizing and connecting groups of related projects. Examples are business divisions, regional divisions, or other organizational structure. You can choose one organization for your entire company, or separate organizations for specific business units, or an organization just for you.” [Microsoft]

“Each organization contains one or more projects. Each project contains a set of features: boards and backlogs for agile planning, pipelines for continuous integration and deployment, repos for version control and management of source code and artifacts, and continuous test integration throughout the life cycle.” [Microsoft]

Creating a new Azure DevOps Project

Once the project is created, you can navigate to Pipelines section to create and manage build and release pipelines.

In the next part of this series we’ll create an initial build pipeline and get an introduction to defining source-controlled build definitions using YAML.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Azure Functions Continuous Deployment with Azure Pipelines: Part 1 - Overview

This is the first part in a series demonstrating how to setup continuous deployment of an Azure Functions App using Azure DevOps build and release pipelines.

In this series we’ll explore (among other things) how to:

  • Build an Azure Functions App when code is pushed to GitHub
  • Run unit tests as part of the build
  • Create multiple Azure Pipeline artifacts
  • Release to a test Function App environment in Azure
  • Run automated functional end to end tests
  • Release to a production Function App environment in Azure
  • Run smoke tests after a production deployment

…in addition to a whole host of other things such as: YAML build files, Azure Pipeline variables, setting Azure Function app settings from an Azure release pipeline, and using post-deployment gates.

An Overview of Azure DevOps

Azure DevOps is a collection of cloud services to facilitate team DevOps practices and includes:

  • Azure boards
  • Azure Pipelines
  • Azure repos
  • Azure test plans
  • Azure Artifacts

In this series we’ll be focussing on Azure Pipelines to build, test, and deploy the Function App automatically when new changes are pushed to GitHub.

InvestFunctionApp Overview

The demo code for these demos can be found on GitHub.

The InvestFunctionApp contains a number of functions that allow the investing of money in a portfolio. The initial entry point is a HTTP-triggered function that allows the investor id to be specified along with how much to add to the investors portfolio.Depending on the investors target allocations, the amount will be investing in either bonds or shares.

There are 4 functions involved in this workflow:

  1. Portfolio function: HTTP trigger, starts the workflow, creates a queue message
  2. CalculatePortfolioAllocation function: queue trigger from the output of (1), calculates where to invest and writes to either buy-stocks queue (3) or buy-bonds queue (4)
  3. BuyStocks function: triggered from buy-stocks queue, simulates buying stocks and updates the investor’s portfolio in Azure Table storage
  4. BuyBonds function: triggered from buy-bonds queue, simulates buying bonds and updates the investor’s portfolio in Azure Table storage

The app is designed to demonstrate the  facilities of Azure Pipelines and is simplified in many ways including minimal error checking, auditing, transactions, security, etc.

Azure DevOps Build Pipeline

The build Azure Pipeline will be automatically triggered from changes pushed to the GitHub repository.

The build pipeline will have the following steps:

  1. Build the solution
  2. Run unit tests
  3. Publish test results
  4. Package the Function App
  5. Publish the packaged Function App as a build artifact
  6. Copy the functional end-to-end tests
  7. Publish the functional end-to-end tests as a separate build artifact

Assuming all these step run without error, the two build artifacts can be passed to/used by the release pipeline.

Azure DevOps Release Pipeline

The release pipeline will be triggered automatically when the build pipeline completes without error.

The following diagram shows what the resulting release pipeline will look like:

Azure DevOps Release Pipeline example

The release pipeline will:

  1. Deploy the Function App to a test Function App in Azure
  2. Run functional end-to-end tests (written in xUnit.net) against this test deployment
  3. If the tests in (2) pass, deploy to production
  4. After deploying to production, call a smoke test function to verify the deployment was successful

In the next part of this series we’ll create Function Apps, signup to Azure DevOps, create a new DevOps project, and take a closer look at the demo function app.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Azure Functions Dependency Injection with Autofac

This post refers specifically to Azure Function V2.

If you want to write automated tests for Azure Functions methods and want to be able to control dependencies (e.g. to inject mock versions of things) you can set up dependency injection.

One way to do this is to install the AzureFunctions.Autofac NuGet package into your functions project.

Once installed, this package allows you to inject dependencies into your function methods at runtime.

Step 1: Create DI Mappings

The first step (after package installation) is to create a class that configures the dependencies. As an example suppose there was a function method that needed to make use of an implementation of an IInvestementAllocator. The following class can be added to the functions project:

using Autofac;
using AzureFunctions.Autofac.Configuration;

namespace InvestFunctionApp
{
    public class DIConfig
    {
        public DIConfig(string functionName)
        {
            DependencyInjection.Initialize(builder =>
            {
                builder.RegisterType<NaiveInvestementAllocator>().As<IInvestementAllocator>(); // Naive

            }, functionName);
        }
    }
}

In the preceding code, a constructor is defined that receives the name of the function that’s being injected into. Inside the constructor, types can be registered for dependency injection. In the preceding code the IInvestementAllocator interface is being mapped to the concrete class NaiveInvestementAllocator.

Step 2: Decorate Function Method Parameters

Now the DI registrations have been configured, the registered types can be injected in function methods. To do this the [Inject] attribute is applied to one or more parameters as the following code demonstrates:

[FunctionName("CalculatePortfolioAllocation")]
public static void Run(
    [QueueTrigger("deposit-requests")]DepositRequest depositRequest,
    [Inject] IInvestementAllocator investementAllocator,
    ILogger log)
    {
        log.LogInformation($"C# Queue trigger function processed: {depositRequest}");

        InvestementAllocation r = investementAllocator.Calculate(depositRequest.Amount, depositRequest.Investor);
    }

Notice in the preceding code the [Inject] attribute is applied to the IInvestementAllocator investementAllocator parameter. This IInvestementAllocator is the same interface that was registered earlier in the DIConfig class.

Step 3: Select DI Configuration

The final step to make all this work is to add an attribute to the class that contains the function method (that uses [Inject]). The attribute used is the DependencyInjectionConfig attribute that takes the type containing the DI configuration as a parameter, for example: [DependencyInjectionConfig(typeof(DIConfig))]

The full function code is as follows:

using AzureFunctions.Autofac;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;

namespace InvestFunctionApp
{
    [DependencyInjectionConfig(typeof(DIConfig))]
    public static class CalculatePortfolioAllocation
    {
        [FunctionName("CalculatePortfolioAllocation")]
        public static void Run(
            [QueueTrigger("deposit-requests")]DepositRequest depositRequest,
            [Inject] IInvestementAllocator investementAllocator,
            ILogger log)
        {
            log.LogInformation($"C# Queue trigger function processed: {depositRequest}");

            InvestementAllocation r = investementAllocator.Calculate(depositRequest.Amount, depositRequest.Investor);
        }
    }
}

At runtime, when the CalculatePortfolioAllocation runs, an instance of an NaiveInvestementAllocator will be supplied to the function.

The library also supports features such as named dependencies and multiple DI configurations, to read more check out GitHub.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Unit Testing C# File Access Code with System.IO.Abstractions

It can be difficult  to write unit tests for code that accesses the file system.

It’s possible to write integration tests that read in an actual file from the file system, do some processing, and check the resultant output file (or result) for correctness. There are a number of potential problems with these types of integration tests including the potential for them to more run slowly (real IO access overheads), additional test file management/setup code, etc. (this does not mean that some integration tests wouldn’t be useful however).

The System.IO.Abstractions NuGet package can help to make file access code more testable. This package provides a layer of abstraction over the file system that is API-compatible with existing code.

Take the following code as an example:

using System.IO;
namespace ConsoleApp1
{
    public class FileProcessorNotTestable
    {
        public void ConvertFirstLineToUpper(string inputFilePath)
        {
            string outputFilePath = Path.ChangeExtension(inputFilePath, ".out.txt");

            using (StreamReader inputReader = File.OpenText(inputFilePath))
            using (StreamWriter outputWriter = File.CreateText(outputFilePath))
            {
                bool isFirstLine = true;

                while (!inputReader.EndOfStream)
                {
                    string line = inputReader.ReadLine();

                    if (isFirstLine)
                    {
                        line = line.ToUpperInvariant();
                        isFirstLine = false;
                    }

                    outputWriter.WriteLine(line);
                }
            }
        }
    }
}

The preceding code opens a text file, and writes it to a new output file, but with the first line converted to uppercase.

This class is not easy to unit test however, it is tightly coupled to the physical file system with the calls to File.OpenText and File.CreateText.

Once the System.IO.Abstractions NuGet package is installed, the class can be refactored as follows:

using System.IO;
using System.IO.Abstractions;

namespace ConsoleApp1
{
    public class FileProcessorTestable
    {
        private readonly IFileSystem _fileSystem;

        public FileProcessorTestable() : this (new FileSystem()) {}

        public FileProcessorTestable(IFileSystem fileSystem)
        {
            _fileSystem = fileSystem;
        }

        public void ConvertFirstLineToUpper(string inputFilePath)
        {
            string outputFilePath = Path.ChangeExtension(inputFilePath, ".out.txt");

            using (StreamReader inputReader = _fileSystem.File.OpenText(inputFilePath))
            using (StreamWriter outputWriter = _fileSystem.File.CreateText(outputFilePath))
            {
                bool isFirstLine = true;

                while (!inputReader.EndOfStream)
                {
                    string line = inputReader.ReadLine();

                    if (isFirstLine)
                    {
                        line = line.ToUpperInvariant();
                        isFirstLine = false;
                    }

                    outputWriter.WriteLine(line);
                }
            }
        }
    }
}

The key things to notice in the preceding code is the ability to pass in an IFileSystem as a constructor parameter. The calls to File.OpenText and File.CreateText are now redirected to _fileSystem.File.OpenText and _fileSystem.File.CreateText  respectively.

If the parameterless constructor is used (e.g. in production at runtime) an instance of FileSystem will be used, however at test time, a mock IFileSystem can be supplied.

Handily, the System.IO.Abstractions.TestingHelpers NuGet package provides a pre-built mock file system that can be used in unit tests, as the following simple test demonstrates:

using System.IO.Abstractions.TestingHelpers;
using Xunit;

namespace XUnitTestProject1
{
    public class FileProcessorTestableShould
    {
        [Fact]
        public void ConvertFirstLine()
        {
            var mockFileSystem = new MockFileSystem();

            var mockInputFile = new MockFileData("line1\nline2\nline3");

            mockFileSystem.AddFile(@"C:\temp\in.txt", mockInputFile);

            var sut = new FileProcessorTestable(mockFileSystem);
            sut.ConvertFirstLineToUpper(@"C:\temp\in.txt");

            MockFileData mockOutputFile = mockFileSystem.GetFile(@"C:\temp\in.out.txt");

            string[] outputLines = mockOutputFile.TextContents.SplitLines();

            Assert.Equal("LINE1", outputLines[0]);
            Assert.Equal("line2", outputLines[1]);
            Assert.Equal("line3", outputLines[2]);
        }
    }
}

To see this in action or to learn more about file access, check out my Working with Files and Streams in C# Pluralsight course.

You can start watching with a Pluralsight free trial.

SHARE:

Testing for Thrown Exceptions in MSTest V2

In previous posts we looked at testing for thrown exceptions in xUnit.net and NUnit. In this post we’re going to see how to test in MSTest V2.

As with the previous posts, the class being tested is as follows:

public class TemperatureSensor
{
    bool _isInitialized;

    public void Initialize()
    {
        // Initialize hardware interface
        _isInitialized = true;
    }

    public int ReadCurrentTemperature()
    {
        if (!_isInitialized)
        {
            throw new InvalidOperationException("Cannot read temperature before initializing.");
        }

        // Read hardware temp
        return 42; // Simulate for demo code purposes
    }
}

And the first test to check the normal execution:

[TestMethod]
public void ReadTemperature()
{
    var sut = new TemperatureSensor();

    sut.Initialize();

    var temperature = sut.ReadCurrentTemperature();

    Assert.AreEqual(42, temperature);
}

Next, a test can be written to check that the expected exception is thrown:

[TestMethod]
public void ErrorIfReadingBeforeInitialized()
{
    var sut = new TemperatureSensor();

    Assert.ThrowsException<InvalidOperationException>(() => sut.ReadCurrentTemperature());
}

The preceding code using the Assert.ThrowsException method, this method takes the type of the expected exception as the generic type parameter (in this case InvalidOperationException). As the method parameter an action/function can be specified – this is the code that is supposed to cause the exception to be thrown.

The thrown exception can also be captured if you need to test the exception property values:

[TestMethod]
public void ErrorIfReadingBeforeInitialized_CaptureExDemo()
{
    var sut = new TemperatureSensor();

    var ex = Assert.ThrowsException<InvalidOperationException>(() => sut.ReadCurrentTemperature());

    Assert.AreEqual("Cannot read temperature before initializing.", ex.Message);
}

To learn more about using exceptions to handle errors in C#, check out my Error Handling in C# with Exceptions Pluralsight course or to learn more about MS Test V2 check out my Automated Testing with MSTest V2 Pluralsight course.

You can start watching with a Pluralsight free trial.

SHARE: