-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Merge main into live #45456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merge main into live #45456
Changes from all commits
8438d77
6c43d6c
f4fa369
23c0f85
ec685db
59c7691
39311c9
d1470c0
239ef23
9284ba3
3949c06
9dec418
48cdcf5
887adbe
b2d0b7e
e44b66e
c7b0e55
169f6d9
0eca2fb
f7c7339
75412af
debae25
c284277
4fe248d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ | |
|
|
||
| # Create the PR for the work done by the "clean repo" tool | ||
| - name: create-pull-request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 5: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: cleanrepo-orphaned-images | ||
| title: "Monthly chores: Delete orphaned images" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ | |
|
|
||
| # Create the PR for the work done by the "clean repo" tool | ||
| - name: create-pull-request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 5: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: cleanrepo-orphaned-includes | ||
| title: "Monthly chores: Delete orphaned include files" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ | |
|
|
||
| # Create the PR for the work done by the "clean repo" tool | ||
| - name: create-pull-request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 5: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: cleanrepo-orphaned-snippets | ||
| title: "Monthly chores: Delete orphaned snippets" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ | |
|
|
||
| # Create the PR for the work done by the "clean repo" tool | ||
| - name: create-pull-request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 5: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: cleanrepo-remove-hops | ||
| title: "Monthly chores: Remove redirect hops" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ | |
|
|
||
| # Create the PR for the work done by the "clean repo" tool | ||
| - name: create-pull-request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 5: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: cleanrepo-relative-links | ||
| title: "Monthly chores: Use relative links" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ | |
|
|
||
| # Create the PR for the work done by the "clean repo" tool | ||
| - name: create-pull-request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 5: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: cleanrepo-replace-redirects | ||
| title: "Monthly chores: Replace redirect targets" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,7 +52,7 @@ | |
| dependabot-yml-path: ".github/dependabot.yml" | ||
| - name: Create pull request | ||
| if: github.event_name == 'workflow_dispatch' || github.repository_owner == 'dotnet' | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 5: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: create-dependabotconfig-pull-request/patch | ||
| title: "Update dependabot.yml - automatically." | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,7 +52,7 @@ | |
| savedir: './docs/whats-new' | ||
|
|
||
| - name: create-pull-request | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e | ||
| uses: dotnet/actions-create-pull-request@v4 | ||
Check warningCode scanning / Scorecard Pinned-Dependencies Medium
score is 6: third-party GitHubAction not pinned by hash
Remediation tip: update your workflow using https://app.stepsecurity.io Click Remediation section below for further remediation help |
||
| with: | ||
| branch: create-whatsnew-pull-request/patch | ||
| title: "What's new article" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| --- | ||
| title: Mutation testing | ||
| author: sigmade | ||
| description: Learn about the Stryker.net tool for mutation testing, to evaluate the quality of your unit tests. | ||
| ms.date: 03/11/2025 | ||
| --- | ||
|
|
||
| # Mutation testing | ||
|
|
||
| Mutation testing is a way to evaluate the quality of our unit tests. For mutation testing, the **Stryker.NET** tool automatically performs mutations in your code, runs tests, and generates a detailed report with the results. | ||
|
|
||
| ## Example test scenario | ||
|
|
||
| Consider a sample _PriceCalculator.cs_ class with a `Calculate` method that calculates the price, taking into account the discount. | ||
|
|
||
| ```csharp | ||
| public class PriceCalculator | ||
| { | ||
| public decimal CalculatePrice(decimal price, decimal discountPercent) | ||
| { | ||
| if (price <= 0) | ||
| { | ||
| throw new ArgumentException("Price must be greater than zero."); | ||
| } | ||
|
|
||
| if (discountPercent < 0 || discountPercent > 100) | ||
| { | ||
| throw new ArgumentException("Discount percent must be between 0 and 100."); | ||
| } | ||
|
|
||
| var discount = price * (discountPercent / 100); | ||
| var discountedPrice = price - discount; | ||
|
|
||
| return Math.Round(discountedPrice, 2); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The preceding method is covered by the following unit tests: | ||
|
|
||
| ```csharp | ||
| [Fact] | ||
| public void ApplyDiscountCorrectly() | ||
| { | ||
| decimal price = 100; | ||
| decimal discountPercent = 10; | ||
|
|
||
| var calculator = new PriceCalculator(); | ||
|
|
||
| var result = calculator.CalculatePrice(price, discountPercent); | ||
|
|
||
| Assert.Equal(90.00m, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void InvalidDiscountPercent_ShouldThrowException() | ||
| { | ||
| var calculator = new PriceCalculator(); | ||
|
|
||
| Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, -1)); | ||
| Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, 101)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void InvalidPrice_ShouldThrowException() | ||
| { | ||
| var calculator = new PriceCalculator(); | ||
|
|
||
| Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(-10, 10)); | ||
| } | ||
| ``` | ||
|
|
||
| The preceding code highlights two projects, one for the service that acts as a `PriceCalculator` and the other is the test project. | ||
|
|
||
| ## Install the global tool | ||
|
|
||
| First, install **Stryker.NET**. | ||
| To do this, you need to execute the command: | ||
|
|
||
| ```dotnetcli | ||
| dotnet tool install -g dotnet-stryker | ||
| ``` | ||
|
|
||
| To run `stryker`, invoke it from the command line in the directory where the unit test project is located: | ||
|
|
||
| ```dotnetcli | ||
| dotnet stryker | ||
| ``` | ||
|
|
||
| After the tests have run, a report is displayed in the console. | ||
|
|
||
| :::image type="content" source="media/stryker-console-report.png" lightbox="media/stryker-console-report.png" alt-text="Stryker console report"::: | ||
|
|
||
| **Stryker.NET** saves a detailed HTML report in the StrykerOutput directory. | ||
|
|
||
| :::image type="content" source="media/stryker-first-report.png" lightbox="media/stryker-first-report.png" alt-text="Stryker first report"::: | ||
|
|
||
| Now, consider what mutants are and what 'survived' and 'killed' mean. A mutant is a small change in your code that Stryker makes on purpose. The idea is simple: if your tests are good, they should catch the change and fail. If they still pass, your tests might not be strong enough. | ||
|
|
||
| In our example, a mutant will be the replacement of the expression `price <= 0`, for example, with `price < 0`, after which unit tests are run. | ||
|
|
||
| Stryker supports several types of mutations: | ||
|
|
||
| | Type | Description | | ||
| |--|--| | ||
| | Equivalent | The equivalent operator is used to replace an operator with its equivalent. For example, `x < y` becomes `x <= y`. | | ||
| | Arithmetic | The arithmetic operator is used to replace an arithmetic operator with its equivalent. For example, `x + y` becomes `x - y`. | | ||
| | String | The string operator is used to replace a string with its equivalent. For example, `"text"` becomes `""`. | | ||
| | Logical | The logical operator is used to replace a logical operator with its equivalent. For example, `x && y` becomes `x \|\| y`. | | ||
|
|
||
| For additional mutation types, see the [Stryker.NET: Mutations](https://stryker-mutator.io/docs/stryker-net/mutations) documentation. | ||
|
|
||
| ## Incremental improvement | ||
|
|
||
| If, after changing your code, the unit tests pass successfully, then they aren't sufficiently robust, and the mutant survived. | ||
| After mutation testing, five mutants survive. | ||
|
|
||
| Let's add test data for boundary values and run mutation testing again. | ||
|
|
||
| ```csharp | ||
| [Fact] | ||
| public void InvalidPrice_ShouldThrowException() | ||
| { | ||
| var calculator = new PriceCalculator(); | ||
|
|
||
| // changed price from -10 to 0 | ||
| Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(0, 10)); | ||
| } | ||
|
|
||
| [Fact] // Added test for 0 and 100 discount | ||
| public void NoExceptionForZeroAnd100Discount() | ||
| { | ||
| var calculator = new PriceCalculator(); | ||
|
|
||
| var exceptionWhen0 = Record.Exception(() => calculator.CalculatePrice(100, 0)); | ||
| var exceptionWhen100 = Record.Exception(() => calculator.CalculatePrice(100, 100)); | ||
|
|
||
| Assert.Null(exceptionWhen0); | ||
| Assert.Null(exceptionWhen100); | ||
| } | ||
| ``` | ||
|
|
||
| :::image type="content" source="media/stryker-second-report.png" lightbox="media/stryker-second-report.png" alt-text="Stryker second report"::: | ||
|
|
||
| As you can see, after correcting the equivalent mutants, we only have string mutations left, which we can easily 'kill' by checking the text of the exception message. | ||
|
|
||
| ```csharp | ||
| [Fact] | ||
| public void InvalidDiscountPercent_ShouldThrowExceptionWithCorrectMessage() | ||
| { | ||
| var calculator = new PriceCalculator(); | ||
|
|
||
| var ex1 = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, -1)); | ||
| Assert.Equal("Discount percent must be between 0 and 100.", ex1.Message); | ||
|
|
||
| var ex2 = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(100, 101)); | ||
| Assert.Equal("Discount percent must be between 0 and 100.", ex2.Message); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void InvalidPrice_ShouldThrowExceptionWithCorrectMessage() | ||
| { | ||
| var calculator = new PriceCalculator(); | ||
|
|
||
| var ex = Assert.Throws<ArgumentException>(() => calculator.CalculatePrice(0, 10)); | ||
| Assert.Equal("Price must be greater than zero.", ex.Message); | ||
| } | ||
| ``` | ||
|
|
||
| :::image type="content" source="media/stryker-final-report.png" lightbox="media/stryker-final-report.png" alt-text="Stryker final report"::: | ||
|
|
||
| Mutation testing helps to find opportunities to improve tests that make them more reliable. It forces you to check not only the 'happy path', but also complex boundary cases, reducing the likelihood of bugs in production. |
Check warning
Code scanning / Scorecard
Pinned-Dependencies Medium