You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
815 lines
25 KiB
Markdown
815 lines
25 KiB
Markdown
---
|
|
marp: true
|
|
paginate: true
|
|
math: mathjax
|
|
theme: buutti
|
|
title: 8. Testing
|
|
---
|
|
|
|
# Testing
|
|
|
|
<!-- headingDivider: 5 -->
|
|
<!-- class: invert -->
|
|
|
|
## Contents
|
|
|
|
- [Introduction to testing](#introduction-to-testing)
|
|
- [C# Testing Frameworks](#c-testing-frameworks)
|
|
- [An extensive unit test example](#an-extensive-unit-test-example)
|
|
- [Writing testable code](#writing-testable-code)
|
|
- [Testing with Postman](#testing-with-postman)
|
|
- [CI/CD/CT](#cicdct)
|
|
|
|
## Introduction to testing
|
|
|
|
### What is testing?
|
|
|
|
* Testing is much more than just trying out manually if the program works!
|
|
* [Manual testing](https://en.wikipedia.org/wiki/Manual_testing): acting as an end user of the application
|
|
* [Automated testing](https://en.wikipedia.org/wiki/Test_automation): using software to emulate the end user, or testing the functionality of the code itself
|
|
* Proving that the program is working as intended during development on a...
|
|
* ...***micro level***: individual pieces of code, such as methods
|
|
* ...***macro level***: larger functional components, such as services
|
|
* *All possible paths in a piece of code should be tested*
|
|
* i.e., [test coverage](https://en.wikipedia.org/wiki/Code_coverage) should be close to 100%
|
|
|
|
### Automated testing: Why?
|
|
|
|
* Why do automated testing?
|
|
* Sets ground truths that can be relied on during development:
|
|
* *"With this kind of input, this should happen"*
|
|
* (No need to take the programmers word for it)
|
|
* Ensures code functionality before moving on
|
|
* Ideally finding bugs before starting to debug
|
|
* Guides developers to create more testable and thus more readable, interchangeable and overall better code
|
|
* [Test-driven development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development) is based on writing tests first and code later!
|
|
|
|
### Types of automated testing
|
|
|
|
* Unit testing
|
|
* Testing individual units (methods, classes) within a project
|
|
* *"input x produces output y"*
|
|
* Each unit has to be entirely independent and ***deterministic*** for it to be unit testable!
|
|
* Deterministic: Always the same result with the same input
|
|
* Integration testing
|
|
* Testing complete modules and the interaction between them within a project
|
|
* Closer to real life scenarios
|
|
* End-to-end (E2E) testing
|
|
* Testing the whole program by emulating the end user's behaviour
|
|
|
|
### Test type comparison
|
|
|
|
* Unit tests are faster to write, E2E tests slowest
|
|
|
|
<div class='centered'>
|
|
|
|

|
|
|
|
</div>
|
|
|
|
## C# Testing Frameworks
|
|
|
|
* Three main testing frameworks:
|
|
* `MSTest`
|
|
* `NUnit`
|
|
* `XUnit`
|
|
* You can install these using NuGet, or in the newer versions of Visual Studio you can create a new testing project
|
|
* NUnit is the most popular of the three, and will be used in this lecture
|
|
|
|
### MSTest
|
|
|
|
* Built-in to most versions of Visual Studio since 2005
|
|
* Simple to use, easy structure
|
|
* `TestClass` for denoting classes containing unit tests
|
|
* `TestMethod` for denoting unit test methods
|
|
|
|
### NUnit
|
|
|
|
* Most popular C# unit testing framework
|
|
* Very similar structure to MSTest tests
|
|
* Instead of `TestClass`, we use `TestFixture`
|
|
* Instead of `TestMethod`, we use `Test` / `TestCase`
|
|
* Has some advantages to MSTest or XUnit tests, for example:
|
|
* Ability to test a single test method using multiple different arguments
|
|
|
|
### XUnit
|
|
|
|
* Free, open-source unit testing tool for .NET framework and .NET core
|
|
* Made by the creator of NUnit v2
|
|
* You can test C#, F#, VB.NET and other .NET languages
|
|
* Part of the .NET Foundation
|
|
* Unique and different style
|
|
|
|
### Note about test case naming
|
|
|
|
* In C# unit testing, the most commonly followed naming convention for test cases/methods is as follows:
|
|
```csharp
|
|
FunctionNameThatIsTested_Condition_ShouldDoX
|
|
```
|
|
* For example:
|
|
```csharp
|
|
Fibonacci_WhenNIs0_ShouldReturn0
|
|
```
|
|
|
|
## An extensive unit test Example
|
|
|
|
### Creating a solution & sample project
|
|
|
|
First, we need a solution and a sample project.
|
|
|
|
<div class='columns23' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
1) In Visual Studio, create a ***Class library*** that targets *__.NET Standard__*.
|
|
2) Name the project `Fibonacci.Library` and the solution `Fibonacci`. Remember to uncheck _Place solution and project in the same directory_ if it isn't unchecked already.
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|

|
|
|
|
</div>
|
|
</div>
|
|
|
|
### Adding the function to be tested
|
|
|
|
<div class='columns' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
3) Rename the default `Class1.cs` file to `Fibonacci.cs`.
|
|
4) Allow Visual Studio to also rename the class itself to `Fibonacci`.
|
|
* Make the class `public` and `static`.
|
|
5) Add a *recursive fibonacci function* shown here.
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
using System;
|
|
|
|
namespace Fibonacci.Library
|
|
{
|
|
public static class Fibonacci
|
|
{
|
|
public static long Recursive(
|
|
int n,
|
|
long previous = 0,
|
|
long current = 0,
|
|
int counter = 0
|
|
)
|
|
{
|
|
return counter == n ?
|
|
previous + current :
|
|
Recursive(
|
|
n,
|
|
current,
|
|
Math.Max(previous + current, 1),
|
|
counter + 1
|
|
);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
### Adding a testing project
|
|
|
|
* Now we need to add a testing project to our solution.
|
|
6) Right click your solution, and add a new project. Select the NUnit project for **_.NET Core_**, and call it `Fibonacci.UnitTests`.
|
|
7) Rename the created class file to `Fibonacci_Tests.cs`, and the class to `Fibonacci_Tests`
|
|
|
|
### Linking the testing project
|
|
|
|
8) We then need to link the `Fibonacci.Library` project to `Fibonacci.UnitTests`.
|
|
9) Right click `Fibonacci.UnitTests`, and select *Add > Reference...*, and under *Projects* select `Fibonacci.Library`. Press OK.
|
|
|
|
* Now we have linked `Fibonacci.Library` to `Fibonacci.UnitTests`, and we can begin testing our `Recursive` function in the `Fibonacci` class.
|
|
|
|
### Importing the class
|
|
|
|
10) Since we have many things called "Fibonacci", we have to import our `Fibonacci` class by using an alias
|
|
* Put this to the top of your file `Fibonacci_Tests.cs`, underneath<br>`using NUnit.Framework;`
|
|
```csharp
|
|
using FibonacciLib = Fibonacci.Library.Fibonacci;
|
|
```
|
|
|
|
### Test fixture for a class
|
|
|
|
11) Now, add `[TestFixture]` on top of our class, like so:
|
|
```csharp
|
|
using NUnit.Framework;
|
|
using FibonacciLib = Fibonacci.Library.Fibonacci;
|
|
|
|
namespace Fibonacci.UnitTests {
|
|
[TestFixture]
|
|
public class Fibonacci_Tests {
|
|
```
|
|
|
|
* This denotes that this class contains our unit tests.
|
|
|
|
### Adding the unit test method
|
|
|
|
12) Then, inside our class `Fibonacci_Tests`, let's create our first method to test the `Recursive` function, as shown here:
|
|
```csharp
|
|
using NUnit.Framework;
|
|
using FibonacciLib = Fibonacci.Library.Fibonacci;
|
|
|
|
namespace Fibonacci.UnitTests {
|
|
[TestFixture]
|
|
public class Fibonacci_Tests {
|
|
[Test]
|
|
public void Recursive_WhenNIs0_ShouldReturn0()
|
|
=> Assert.That(0, Is.EqualTo(FibonacciLib.Recursive(n: 0)));
|
|
}
|
|
}
|
|
```
|
|
|
|
### Things to take into consideration
|
|
|
|
* When passing in function arguments, to avoid ***magic numbers*** and to improve code readability, use the syntax `argumentName: value`
|
|
* Magic numbers = using numbers as input as-is without explanation, for example without assigning to a well-named variable, or without using a descriptive type
|
|
* `Assert.That` is one of many functions that can determine whether the test passes
|
|
* Its first argument is the ***expected value***. In the second argument you give a ***constraint*** for the actual output, i.e., value is equal to, greater than, starts with, [etc](https://docs.nunit.org/articles/nunit/writing-tests/constraints/Constraints.html).
|
|
* *Do not mix the arguments up!* If you do, the errors you get do not make any sense.
|
|
|
|
### Running Tests
|
|
|
|
* Now, let's open the built-in test runner in Visual Studio to run our tests.
|
|
|
|
* On the top of Visual Studio, click *Test*, then *Test Explorer*.
|
|
* In the Test Explorer, you should see the test case we created. If not, build the solution, and make sure there are no errors.
|
|
* Either way, let's run our test case. Press *Run All Tests* in the top-left corner of Test Explorer. You should see our test case *__passing__*.
|
|
|
|
### Adding More Test Cases
|
|
|
|
* While there's nothing inherently wrong about our test case, it doesn't cover much of anything.
|
|
* We need to add more test cases to test different values and outputs.
|
|
* In other testing frameworks, you would do this by creating more methods.
|
|
* But with `NUnit`, we can use our *existing* method.
|
|
|
|
### Adding a new test
|
|
|
|
1) Do the following modifications to our test method:
|
|
```csharp
|
|
[TestCase(0, 0)]
|
|
public void Recursive_Always_ShouldReturnNthFibonacci(
|
|
int n,
|
|
long expected
|
|
)
|
|
=> Assert.That(expected, Is.EqualTo(FibonacciLib.Recursive(n)));
|
|
```
|
|
* Using `TestCase` instead of `Test`, we can pass in arguments to our test case method, and we can use those passed in arguments in our actual tests.
|
|
|
|
### Adding more tests
|
|
|
|
2) Run the test cases again, and they should *__pass__*.
|
|
* But as you might notice, we haven't really changed anything yet.
|
|
3) Let's add more test cases, like so:
|
|
```csharp
|
|
[TestCase(0, 0)]
|
|
[TestCase(1, 1)]
|
|
public void Recursive_Always_ShouldReturnNthFibonacci(
|
|
```
|
|
|
|
4) Now we have 2 test cases with only one test method. Run the tests again, and both cases should *__pass__*.
|
|
|
|
### Adding a failing test
|
|
|
|
5) Let's add one more TestCase, like so:
|
|
```csharp
|
|
[TestCase(2, 1)]
|
|
```
|
|
6) Run the tests again, and you should see the test case *__fail__*.
|
|
* This is to be expected: the 2nd fibonacci number should be 1, but the actual value we got from our function was 2.
|
|
* This means our function does not work as expected.
|
|
|
|
### Fixing our code
|
|
|
|
<div class='columns' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
7) Let's fix our function, as seen here.
|
|
8) Run the tests again. You should see all *__pass__* now.
|
|
9) Add a few more test cases
|
|
* (If you don't know the fibonacci sequence, you can check it from the internet.)
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
public static long Recursive(
|
|
int n,
|
|
long previous = 0,
|
|
long current = 0,
|
|
int counter = 0
|
|
)
|
|
{
|
|
return counter == n ?
|
|
current : // changed from previous + current
|
|
Recursive(
|
|
n,
|
|
current,
|
|
Math.Max(previous + current, 1),
|
|
counter + 1
|
|
);
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</div>
|
|
|
|
### Exercise 1: Unit testing in practice
|
|
<!--_class: "exercise invert" -->
|
|
|
|
Answer the following questions:
|
|
|
|
1) How many test cases should we ideally have to test our `Recursive` function?
|
|
2) What happens if we pass in `n = int.MaxValue (2147483647)`?
|
|
* What about other huge numbers?
|
|
* How would you fix the function so it works as expected?
|
|
3) What happens if we pass in `n = -1`?
|
|
* What about other negative numbers?
|
|
* How would you fix the function so it works as expected?
|
|
|
|
### Answers to Exercise 1: Unit testing in practice
|
|
<!--_class: "exercise invert" -->
|
|
|
|
1) We should cover all the edge cases and code paths with our test cases. We do not need to have many test cases for the same code path.
|
|
2) `Stack Overflow`, `Int64 Overflow`, or the test case just won't run.
|
|
* We should have a maximum allowed value for `n`, throw the ***argument out of range*** exception if above
|
|
3) `Stack Overflow`, since `current` can never equal `n` that is less than `0`
|
|
* We should have a minimum allowed value for `n`, throw the ***argument out of range*** exception if below
|
|
|
|
### Testing exceptions
|
|
|
|
* To expect exceptions to be thrown in test cases, we can use the method `Assert.Throws`
|
|
* Syntax is as follows:
|
|
```csharp
|
|
Assert.Throws<ExceptionType>(delegate);
|
|
```
|
|
* In practice:
|
|
```csharp
|
|
Assert.Throws<NullReferenceException>(() => TestedFunction(n));
|
|
```
|
|
### Handling border cases
|
|
|
|
<div class='columns' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
* Continuing our Fibonacci test case, we need to add handling for `n` that is less than `0` or more than a set amount. Then we need to add another test method and two test cases to test our new functionality.
|
|
1) Let's start with modifying our existing `Recursive` function, as shown here:
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
public const int MinDepth = 0;
|
|
public const int MaxDepth = 10000;
|
|
|
|
public static long Recursive(
|
|
int n,
|
|
long previous = 0,
|
|
long current = 0,
|
|
int counter = 0
|
|
)
|
|
{
|
|
if (n < MinDepth || n > MaxDepth)
|
|
throw new ArgumentOutOfRangeException(
|
|
"n is out of bounds!"
|
|
);
|
|
|
|
return counter == n ?
|
|
current :
|
|
Recursive(
|
|
n,
|
|
current,
|
|
Math.Max(previous + current, 1),
|
|
counter + 1
|
|
);
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</div>
|
|
|
|
### Testing that everything works
|
|
|
|
* Now our `Recursive` function throws an `ArgumentOutOfRange` exception when `n` is less than `0` or more than `10000`.
|
|
2) Let's test that everything works as expected by adding a new test method and a few test cases:
|
|
```csharp
|
|
[TestCase(int.MaxValue)]
|
|
[TestCase(-1)]
|
|
public void Recursive_WhenNIsOutOfBounds_ShouldThrowArgumentOutOfRange(
|
|
int n
|
|
)
|
|
=> Assert.Throws<ArgumentOutOfRangeException>(() => FibonacciLib.Recursive(n));
|
|
```
|
|
|
|
3) Rename the other method to `Recursive_WhenNIsInBounds_ShouldReturnNthFibonacci`
|
|
|
|
### Unnecessary arguments
|
|
|
|
* We have one more problem:
|
|
* Our `Recursive` function is a `public` method that has in total 4 arguments it takes, yet we only test 1 argument (`n`).
|
|
* In order for our tests to be complete, we need to test all the arguments a `public` method takes in.
|
|
* So, how do we fix this?
|
|
|
|
### Removing unnecessary arguments
|
|
|
|
<div class='columns' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
* The naive way would be to test all the arguments with more test cases.
|
|
* It's important to realize that *__we don't have to__*.
|
|
* Since we never pass in any other arguments than `n` from outside `Recursive`, we could just create another `private` function that has those arguments, and remove them from our public function.
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
public static long Recursive(int nStartingValue)
|
|
{
|
|
if (nStartingValue < MinDepth
|
|
|| nStartingValue > MaxDepth)
|
|
throw new ArgumentOutOfRangeException(
|
|
"n is out of bounds!"
|
|
);
|
|
|
|
long getRecursive(
|
|
int n,
|
|
long previous = 0,
|
|
long current = 0,
|
|
int counter = 0)
|
|
=> counter == n ?
|
|
current :
|
|
getRecursive(
|
|
n,
|
|
current,
|
|
Math.Max(previous + current, 1),
|
|
counter + 1
|
|
);
|
|
return getRecursive(nStartingValue);
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</div>
|
|
|
|
### End Result
|
|
|
|
* Now we have tested all the arguments in our public `Recursive` function.
|
|
* We do not need to test private functions such as `getRecursive`, only the public API has to be tested.
|
|
* All tests should *__pass__* at this point.
|
|
|
|
### Exercise 2: Unit Testing
|
|
<!--_class: "exercise invert" -->
|
|
|
|
```csharp
|
|
public static long DoX(int nStartingValue) {
|
|
if (nStartingValue < 0 || nStartingValue > 150)
|
|
throw new ArgumentOutOfRangeException("n is out of bounds!");
|
|
|
|
long doY(int n) => n > 0 ? n * doY(n - 1) : 1;
|
|
|
|
return doY(nStartingValue);
|
|
}
|
|
```
|
|
|
|
* Following the previously used architecture as closely as possible, unit test the attached function using `NUnit`. Answer the following questions:
|
|
1) What does the function that we are testing actually *do*?
|
|
2) Do we have to test `doY` as well?
|
|
3) How would you name `DoX` so it is descriptive? How about `doY`?
|
|
|
|
### Answers to exercise 2: Unit Testing
|
|
<!--_class: "exercise invert" -->
|
|
|
|
1) It gets the nth factorial (`n!`)
|
|
2) `doY` is a private method that gets tested when we test `DoX`, so no.
|
|
3) Example names: `DoX` $\Rightarrow$ `TryGetFactorial`, `doY` $\Rightarrow$ `getFactorial`
|
|
|
|
|
|
### Unit Testing: Final testing code
|
|
|
|
*
|
|
```csharp
|
|
[TestCase(0, 1)]
|
|
[TestCase(1, 1)]
|
|
[TestCase(2, 2)]
|
|
[TestCase(3, 6)]
|
|
[TestCase(15, 1307674368000)]
|
|
public void TryGetFactorial_WhenNIsInBounds_ShouldReturnNthFactorial(
|
|
int n, long expected
|
|
)
|
|
=> Assert.That(expected, Is.EqualTo(TestExerciseLib.TryGetFactorial(n)));
|
|
```
|
|
|
|
```csharp
|
|
[TestCase(-1)]
|
|
[TestCase(151)]
|
|
public void TryGetFactorial_NIsOutOfBounds_ShouldThrowArgumentOutOfRange(
|
|
int n
|
|
)
|
|
=> Assert.Throws<ArgumentOutOfRangeException>(
|
|
() => TestExerciseLib.TryGetFactorial(n)
|
|
);
|
|
```
|
|
|
|
## Writing testable code
|
|
|
|
* Writing testable code often means that you're writing good code
|
|
* Testable units should
|
|
* be deterministic (functional): same input always produces the same output
|
|
* be [loosely coupled](https://en.wikipedia.org/wiki/Loose_coupling): components have a loose and limited connection to other components in the system, meaning changes in one component should not require changes to many others
|
|
* abide to the *__Single Responsibility Principle__*: a function, method or component is only responsible for one purpose
|
|
|
|
### Writing testable code: Example 1
|
|
|
|
<div class='columns23' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
* Can `GetCurrentSeason()` be unit tested? Why / why not?
|
|
* How to fix the problems, if any?
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
public static class Season
|
|
{
|
|
public static string GetCurrentSeason() {
|
|
DateTime currentTime = DateTime.Now;
|
|
if (currentTime.Month > 11 && currentTime.Month < 3)
|
|
return "Winter";
|
|
else if (currentTime.Month < 6)
|
|
return "Spring";
|
|
else if (currentTime.Month < 9)
|
|
return "Summer";
|
|
else
|
|
return "Fall";
|
|
}
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</div>
|
|
|
|
### Writing testable code: Example 1 fixed
|
|
|
|
<div class='columns23' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
* Now the method is purely functional and deterministic
|
|
* Will return the same value for every call with equal arguments
|
|
* `GetCurrentSeason()` can now easily be tested as shown next
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|
```csharp
|
|
public static class Season {
|
|
public static string GetCurrentSeason(
|
|
DateTime currentTime
|
|
) {
|
|
if (currentTime.Month > 11 && currentTime.Month < 3)
|
|
return "Winter";
|
|
else if (currentTime.Month < 6)
|
|
return "Spring";
|
|
else if (currentTime.Month < 9)
|
|
return "Summer";
|
|
else
|
|
return "Fall";
|
|
}
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</div>
|
|
|
|
---
|
|
|
|
```csharp
|
|
public class GetCurrentSeasonTest
|
|
{
|
|
[TestCase(12, "Winter")]
|
|
[TestCase(3, "Spring")]
|
|
[TestCase(6, "Summer")]
|
|
[TestCase(9, "Fall")]
|
|
public void GetCurrentSeason_ForFirstMonthsOfSeasons_ReturnsCorrectSeason(
|
|
int month,
|
|
string season
|
|
)
|
|
{
|
|
Assert.That(season,
|
|
Is.EqualTo(
|
|
Season.GetCurrentSeason(new DateTime(2020, month, 1))
|
|
)
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<div class='centered'>
|
|
|
|

|
|
|
|
</div>
|
|
|
|
* The tests fail, as shown above.
|
|
|
|
---
|
|
|
|
<div class='columns32' markdown='1'>
|
|
<div markdown='1'>
|
|
|
|
* Test results indicate there is something wrong with the code path that should return `Winter`
|
|
```csharp
|
|
if (currentTime.Month > 11 && currentTime.Month < 3)
|
|
```
|
|
* The line should be changed to
|
|
```csharp
|
|
if (currentTime.Month > 11 || currentTime.Month < 3)
|
|
```
|
|
|
|
</div>
|
|
<div markdown='1'>
|
|
|
|

|
|
|
|
</div>
|
|
</div>
|
|
|
|
### Writing testable code: Example 2
|
|
|
|
* Let's look at a `HeatingUnit` class that returns a heating setting based on the current season. The current date problem is back again:
|
|
```csharp
|
|
public class HeatingUnit
|
|
{
|
|
public string GetHeatingSetting()
|
|
{
|
|
DateTime currentDate = DateTime.Now;
|
|
if (Season.GetCurrentSeason(currentDate) == "Winter")
|
|
return "HEAT_SETTING_HIGH";
|
|
else if (Season.GetCurrentSeason(currentDate) == "Summer")
|
|
return "HEAT_SETTING_OFF";
|
|
else
|
|
return "HEAT_SETTING_MEDIUM";
|
|
}
|
|
}
|
|
```
|
|
|
|
### Writing testable code: Example 2 continued
|
|
|
|
* Instead of taking the current date initialization even higher in the class hierarchy, let's make a ***service*** that returns the current date
|
|
```csharp
|
|
public interface IDateTimeProvider
|
|
{
|
|
DateTime GetDateTime();
|
|
}
|
|
```
|
|
* After this the service can be injected into `HeatingUnit` with constructor injection
|
|
* Code example shown next
|
|
|
|
---
|
|
|
|
```csharp
|
|
public class HeatingUnit
|
|
{
|
|
private readonly IDateTimeProvider _dateTimeProvider;
|
|
|
|
public HeatingUnit(IDateTimeProvider dateTimeProvider)
|
|
{
|
|
_dateTimeProvider = dateTimeProvider;
|
|
}
|
|
|
|
public string GetHeatingSetting() {
|
|
if (Season.GetCurrentSeason(_dateTimeProvider.GetDateTime()) == "Winter")
|
|
return "HEAT_SETTING_HIGH";
|
|
else if (Season.GetCurrentSeason(_dateTimeProvider.GetDateTime()) == "Summer")
|
|
return "HEAT_SETTING_OFF";
|
|
else
|
|
return "HEAT_SETTING_MEDIUM";
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
* For testing, a fake `DateTimeProvider` service can be used for injecting the `HeatingUnit` with any date:
|
|
|
|
```csharp
|
|
class FakeDateTimeProvider : IDateTimeProvider
|
|
{
|
|
public DateTime Date { get; set; }
|
|
public DateTime GetDateTime() => Date;
|
|
}
|
|
```
|
|
|
|
* Using this kind of a fake service instead of the real one is called [mocking](https://en.wikipedia.org/wiki/Mock_object)
|
|
* In the real application the provider would return the real current date
|
|
|
|
---
|
|
|
|
```csharp
|
|
class SetHeatingSettingTest
|
|
{
|
|
[TestCase(12, "HEAT_SETTING_HIGH")]
|
|
[TestCase(3, "HEAT_SETTING_MEDIUM")]
|
|
[TestCase(6, "HEAT_SETTING_OFF")]
|
|
[TestCase(9, "HEAT_SETTING_MEDIUM")]
|
|
public void SetHeatingSetting_ForFirstMonthsOfSeasons_ShouldReturnCorrectSetting(
|
|
int month,
|
|
string setting
|
|
)
|
|
{
|
|
FakeDateTimeProvider timeProvider
|
|
= new FakeDateTimeProvider { Date = new DateTime(2020, month, 1) };
|
|
HeatingUnit heatingUnit = new HeatingUnit(timeProvider);
|
|
|
|
Assert.That(setting, Is.EqualTo(heatingUnit.GetHeatingSetting()));
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<div class='centered'>
|
|
|
|

|
|
Tests should pass!
|
|
|
|
</div>
|
|
|
|
## Testing with Postman
|
|
|
|
* So far, we have created individual request with Postman to see what the response of an API is for each request
|
|
* This is not testing an API, this is *__exploring__* an API
|
|
* Tests in Postman are inserted in the _Tests_ tab
|
|
* Postman uses the *__Chai.js__* testing library for creating tests
|
|

|
|
|
|
---
|
|
|
|
* To get started immediately, you can select *snippets* from the right
|
|
* Let's select the *Status code: Code is 200* snippet
|
|

|
|
|
|
---
|
|
|
|
* Successfully run tests show up in green in the *Test Result* tab of the response:
|
|

|
|
|
|
---
|
|
|
|
* [The official Postman web page](https://learning.postman.com/docs/writing-scripts/test-scripts/) is a good starting point for learning testing with Postman
|
|
* Using `pm.expect` will give a bit more info about the test:
|
|

|
|
|
|
---
|
|
|
|
* The response to /serverinfo has the following body:
|
|
```json
|
|
{
|
|
"appSettings": {
|
|
"applicationUrl": "http://localhost:63741",
|
|
"aspNetCoreEnvironment": "Development"
|
|
},
|
|
"serverStatus": {
|
|
"dbConnectionStatus": "Connected"
|
|
}
|
|
}
|
|
```
|
|
* Let's make a test to see whether the server is connected to the database
|
|
|
|
---
|
|
|
|
* Variables can be declared within the test:
|
|

|
|
|
|
### Exercise 3: Testing with Postman
|
|
<!--_class: "exercise invert" -->
|
|
|
|
1) Create a new collection in Postman by selecting the *Collections* tab and clicking *New Collection*. Name it `CourseAPI Tests`. Launch the CourseAPI you have developed during the lectures, and make sure the `course_db` database is connected
|
|
2) Create a new `GET` request for the URI `/api/courses`. Add a test: status code of the request should be 200. Save the request to the `CourseAPI Tests` collection.
|
|
3) Create a new `GET` request for the URI `/api/courses/999999999`. Add a test: status code of the request should be 404. Save the request to the `CourseAPI Tests` collection.
|
|
|
|
---
|
|
|
|
<!--_class: "exercise invert" -->
|
|
|
|
4) Create a new `POST` request for the URI `/api/courses`. Set the headers and the content correctly, but set the value of credits to 100. Add a test; status code should be 400. Save the request to the CourseAPI Tests collection.
|
|
5) Hover on CourseAPI Tests collection, click the arrow and click *Run*. From the opened window, scroll down and click *Run CourseAPI Tests*. This will create all the requests in your collection.
|
|
|
|
## CI/CD/CT
|
|
|
|
* CI stands for *__Continuous Integration__*
|
|
* Each change in code triggers a build-and-test sequence for the given project
|
|
* Goal is to have a consistent and automated way to build, package, and test applications
|
|
* CD stands for *__Continuous Delivery__*
|
|
* Automates the delivery of applications to selected environments, such as Azure, AWS, GCP
|
|
* Both require *__Continuous Testing__*, which includes automated regression, performance and other tests
|