Azure Functions SRE, The First Cut
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Release Pipeline | |
- stage: Release | |
jobs: | |
- deployment: HostedVs2017 | |
... | |
variables: | |
- name: TestRunStatus | |
value: '' | |
strategy: | |
runOnce: | |
deploy: | |
steps: | |
... | |
- task: CmdLine@2 | |
displayName: 'Run E2E Tests' | |
inputs: | |
script: 'dotnet vstest $(Pipeline.Workspace)\e2e\FunctionApp.Tests\FunctionApp.Tests.dll --testCaseFilter:TestCategory=E2E --logger:trx --resultsDirectory:$(Pipeline.Workspace)\TestResults' | |
continueOnError: true | |
env: | |
ServerName: FunctionApp | |
FunctionAuthKey: $(FunctionAuthKey) | |
FunctionAppName: $(FunctionAppName) | |
- task: PowerShell@2 | |
displayName: 'Save Test Run Status' | |
inputs: | |
targetType: Inline | |
script: 'Write-Host "##vso[task.setvariable variable=TestRunStatus]$(Agent.JobStatus)"' | |
- task: PublishTestResults@2 | |
displayName: 'Publish E2E Test Results' | |
inputs: | |
testRunTitle: 'E2E Tests' | |
testResultsFormat: VSTest | |
testResultsFiles: '$(Pipeline.Workspace)/TestResults/*.trx' | |
mergeTestResults: true | |
- task: PowerShell@2 | |
displayName: 'Cancel Pipeline on Test Run Failure' | |
condition: and(succeeded(), or(eq(variables['TestRunStatus'], 'Failed'), eq(variables['TestRunStatus'], 'SucceededWithIssues'))) | |
inputs: | |
targetType: Inline | |
script: | | |
Write-Host "##vso[task.setvariable variable=Agent.JobStatus]Failed" | |
Write-Host "##vso[task.complete result=Failed]DONE" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dotnet test [Test_Project_Name].csproj -c Release --filter:"TestCategory=E2E" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
set ServerName=FunctionApp | |
set FunctionAuthKey=uCQISIGoaMYz6d/6yR/Q2aw6PVdxLhzOh1gy9IjfoRbTN9OgmOFgTQ== | |
set FunctionAppName=fncapp-mountebank |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FunctionAppServerFixture : ServerFixture | |
{ | |
private const string FunctionAppNameKey = "FunctionAppName"; | |
private const string FunctionAuthKeyKey = "FunctionAuthKey"; | |
private readonly string _functionAppName; | |
private readonly string _functionAuthKey; | |
public FunctionAppServerFixture() | |
{ | |
this._functionAppName = Environment.GetEnvironmentVariable(FunctionAppNameKey); | |
this._functionAuthKey = Environment.GetEnvironmentVariable(FunctionAuthKeyKey); | |
} | |
public override string GetHealthCheckUrl(HttpStatusCode statusCode = HttpStatusCode.OK) | |
{ | |
return $"https://{this._functionAppName}.azurewebsites.net/api/ping?code={this._functionAuthKey}"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IHealthCheckFunction : IFunction<ILogger> | |
{ | |
} | |
public class HealthCheckFunction : FunctionBase<ILogger>, IHealthCheckFunction | |
{ | |
... | |
// Dependency injections here | |
public override async Task<TOutput> InvokeAsync<TInput, TOutput>( | |
TInput input, | |
functionOptionsBase options = null) | |
{ | |
var result = (IActionResult)null; | |
var requestUri = $"{this._settings.BaseUri.TrimEnd('/')}/{this._settings.Endpoints.HealthCheck.TrimStart('/')}"; | |
using (var response = await this._httpClient.GetAsync(requestUri).ConfigureAwaitfalse)) | |
{ | |
try | |
{ | |
response.EnsureSuccessStatusCode(); | |
result = new OkResult(); | |
} | |
catch (Exception ex) | |
{ | |
var error = new ErrorResponse(ex); | |
result = new ObjectResult(error) { StatusCode = (int)response.StatusCode }; | |
} | |
} | |
return (TOutput)result; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestClass] | |
public class HealthCheckHttpTriggerTests | |
{ | |
private const string CategoryIntegration = "Integration"; | |
private const string CategoryE2E = "E2E"; | |
private const string DefaultServerName = "Localhost"; | |
private const string ServerNameKey = "ServerName"; | |
private ServerFixture _fixture; | |
[TestInitialize] | |
public void Init() | |
{ | |
// Gets the server name from the environment variable. | |
var serverName = Environment.GetEnvironmentVariable(ServerNameKey); | |
if (string.IsNullOrWhiteSpace(serverName)) | |
{ | |
serverName = DefaultServerName; | |
} | |
// Creates the fixture from the factory method pattern. | |
this._fixture = ServerFixture.CreateInstance(serverName); | |
} | |
... | |
[TestMethod] | |
[TestCategory(CategoryIntegration)] | |
// Adds the test category for end-to-end testing | |
[TestCategory(CategoryE2E)] | |
public async Task Given_Url_When_Invoked_Then_Trigger_Should_Return_Healthy() | |
{ | |
... | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class HealthCheckHttpTrigger | |
{ | |
... | |
// Dependency injections here | |
[FunctionName(nameof(HealthCheckHttpTrigger.PingAsync))] | |
public async Task<IActionResult> PingAsync( | |
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "ping")] HttpRequest req, | |
ILogger log) | |
{ | |
return result = await this._function | |
.InvokeAsync<HttpRequest, IActionResult>(req) | |
.ConfigureAwait(false); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
npm install -g mountebank |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
stages: | |
# Build Pipeline | |
- stage: Build | |
jobs: | |
- job: HostedVs2017 | |
... | |
variables: | |
- name: IntegrationTestRunStatus | |
value: '' | |
steps: | |
... | |
- task: CmdLine@2 | |
displayName: 'Integration Test Function App' | |
inputs: | |
script: 'dotnet test $(Build.SourcesDirectory)\test\FunctionApp.Tests\FunctionApp.Tests.csproj -c $(BuildConfiguration) --filter:"TestCategory=Integration" --logger:trx --results-directory:$(System.DefaultWorkingDirectory)\IntegrationTestResults' | |
env: | |
ServerName: Localhost | |
continueOnError: true | |
- task: PowerShell@2 | |
displayName: 'Save Integration Test Run Status' | |
inputs: | |
targetType: Inline | |
script: 'Write-Host "##vso[task.setvariable variable=IntegrationTestRunStatus]$(Agent.JobStatus)"' | |
- task: PublishTestResults@2 | |
displayName: 'Publish Integration Test Results' | |
inputs: | |
testRunTitle: 'Integration Tests' | |
testResultsFormat: VSTest | |
testResultsFiles: '$(System.DefaultWorkingDirectory)/IntegrationTestResults/*.trx' | |
mergeTestResults: true | |
- task: PowerShell@2 | |
displayName: 'Cancel Pipeline on Test Run Failure' | |
condition: and(succeeded(), or(eq(variables['UnitTestRunStatus'], 'Failed'), eq(variables['UnitTestRunStatus'], 'SucceededWithIssues'), eq(variables['IntegrationTestRunStatus'], 'Failed'), eq(variables['IntegrationTestRunStatus'], 'SucceededWithIssues'))) | |
inputs: | |
targetType: Inline | |
script: | | |
Write-Host "##vso[task.setvariable variable=Agent.JobStatus]Failed" | |
Write-Host "##vso[task.complete result=Failed]DONE" | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dotnet test [Test_Project_Name].csproj -c Release --filter:"TestCategory=Integration" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestClass] | |
public class HealthCheckHttpTriggerTests | |
{ | |
private const string CategoryIntegration = "Integration"; | |
private ServerFixture _fixture; | |
[TestInitialize] | |
public void Init() | |
{ | |
this._fixture = new LocalhostServerFixture(); | |
} | |
[TestMethod] | |
[TestCategory(CategoryIntegration)] | |
public async Task Given_Url_When_Invoked_Then_Trigger_Should_Return_Healthy() | |
{ | |
// Arrange | |
var uri = this._fixture.GetHealthCheckUrl(); | |
using (var http = new HttpClient()) | |
// Act | |
using (var res = await http.GetAsync(uri)) | |
{ | |
// Assert | |
res.StatusCode.Should().Be(HttpStatusCode.OK); | |
} | |
} | |
[TestMethod] | |
[TestCategory(CategoryIntegration)] | |
public async Task Given_Url_When_Invoked_Then_Trigger_Should_Return_Unhealthy() | |
{ | |
// Arrange | |
var uri = this._fixture.GetHealthCheckUrl(HttpStatusCode.InternalServerError); | |
using (var http = new HttpClient()) | |
// Act | |
using (var res = await http.GetAsync(uri)) | |
{ | |
// Assert | |
res.StatusCode.Should().Be(HttpStatusCode.InternalServerError); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class LocalhostServerFixture : ServerFixture | |
{ | |
... | |
public override string GetHealthCheckUrl(HttpStatusCode statusCode = HttpStatusCode.OK) | |
{ | |
... | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class LocalhostServerFixture | |
{ | |
private readonly MountebankClient _client; | |
public MountebankServerFixture() | |
{ | |
this._client = new MountebankClient(); | |
} | |
public string GetHealthCheckUrl(HttpStatusCode statusCode = HttpStatusCode.OK) | |
{ | |
this._client.DeleteImposter(8080); | |
var imposter = this._client | |
.CreateHttpImposter(8080, statusCode.ToString()); | |
imposter.AddStub() | |
.OnPathAndMethodEqual("/api/ping", Method.Get) | |
.ReturnsStatus(statusCode); | |
this._client.Submit(imposter); | |
return "http://localhost:7071/api/ping"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mb |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
start /b mb --noLogFile | |
start /b func host start --csharp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Console #1 | |
mb --noLogFile | |
# Console #2 | |
func host start --csharp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class ServerFixture | |
{ | |
public static ServerFixture CreateInstance(string serverName) | |
{ | |
var type = Type.GetType($"FunctionApp.Tests.Fixtures.{serverName}ServerFixture"); | |
var instance = (ServerFixture)Activator.CreateInstance(type); | |
return instance; | |
} | |
public abstract string GetHealthCheckUrl(HttpStatusCode statusCode = HttpStatusCode.OK); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
stages: | |
# Build Pipeline | |
- stage: Build | |
jobs: | |
- job: HostedVs2017 | |
... | |
variables: | |
- name: UnitTestRunStatus | |
value: '' | |
steps: | |
... | |
- task: CmdLine@2 | |
displayName: 'Unit Test Function App' | |
inputs: | |
script: 'dotnet test $(Build.SourcesDirectory)\test\FunctionApp.Tests\FunctionApp.Tests.csproj -c $(BuildConfiguration) --filter:"TestCategory!=Integration&TestCategory!=E2E" --logger:trx --results-directory:$(System.DefaultWorkingDirectory)\UnitTestResults /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=$(System.DefaultWorkingDirectory)\CoverageResults\coverage' | |
env: | |
ServerName: Localhost | |
continueOnError: true | |
- task: PowerShell@2 | |
displayName: 'Save Unit Test Run Status' | |
inputs: | |
targetType: Inline | |
script: 'Write-Host "##vso[task.setvariable variable=UnitTestRunStatus]$(Agent.JobStatus)"' | |
- task: PublishTestResults@2 | |
displayName: 'Publish Unit Test Results' | |
inputs: | |
testRunTitle: 'Unit Tests' | |
testResultsFormat: VSTest | |
testResultsFiles: '$(System.DefaultWorkingDirectory)/UnitTestResults/*.trx' | |
mergeTestResults: true | |
- task: PublishCodeCoverageResults@1 | |
displayName: 'Publish Code Coverage Results' | |
inputs: | |
codeCoverageTool: 'cobertura' | |
summaryFileLocation: '$(System.DefaultWorkingDirectory)/CoverageResults/*.xml' | |
failIfCoverageEmpty: false | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dotnet test [Test_Project_Name].csproj -c Release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestMethod] | |
public async Task Given_Parameters_When_Invoked_Then_InvokeAsync_Should_Return_Result() | |
{ | |
// Arrange | |
var result = new OkResult(); | |
var function = new Mock<IHealthCheckFunction>(); | |
function.Setup(p => p.InvokeAsync<HttpRequest, IActionResult>(It.IsAny<HttpRequest>(), It.IsAny<FunctionOptionsBase>())) | |
.ReturnsAsync(result); | |
var trigger = new HealthCheckHttpTrigger(function.Object); | |
var req = new Mock<HttpRequest>(); | |
var log = new Mock<ILogger>(); | |
// Action | |
var response = await trigger.PingAsync(req.Object, log.Object).ConfigureAwait(false); | |
// Assert | |
response | |
.Should().BeOfType<OkResult>() | |
.And.Subject.As<OkResult>() | |
.StatusCode.Should().Be((int)HttpStatusCode.OK); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestMethod] | |
public async Task Given_Parameters_When_Invoked_Then_InvokeAsync_Should_Return_Result() | |
{ | |
// Arrange | |
var result = new OkResult(); | |
var function = new Mock<IHealthCheckFunction>(); | |
function.Setup(p => p.InvokeAsync<HttpRequest, IActionResult>(It.IsAny<HttpRequest>(), It.IsAny<FunctionOptionsBase>())) | |
.ReturnsAsync(result); | |
var trigger = new HealthCheckHttpTrigger(function.Object); | |
var req = new Mock<HttpRequest>(); | |
var log = new Mock<ILogger>(); | |
// Action | |
var response = await trigger.PingAsync(req.Object, log.Object).ConfigureAwait(false); | |
// Assert | |
response | |
.Should().BeOfType<OkResult>() | |
.And.Subject.As<OkResult>() | |
.StatusCode.Should().Be((int)HttpStatusCode.OK); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment