如何使用Mock在C#中对IEnumerable接口进行TDD测试?附FizzBuzz代码片段
Alright, let's break this down step by step. I’ll walk you through how to use a mocking framework (Moq, the go-to for C#) to practice Test-Driven Development (TDD) with IEnumerable interfaces, then apply that directly to your FizzBuzz-focused BusinessHandler code.
First, let's align on basics: TDD follows the Red-Green-Refactor cycle—write a failing test first, write just enough production code to make it pass, then clean up both test and production code. For IEnumerable dependencies, we’ll mock individual rule implementations and pass them as a collection to our BusinessHandler.
1. Setup Your Test Environment
First, add the Moq package to your test project via NuGet:
- Using Package Manager Console:
Install-Package Moq - Using .NET CLI:
dotnet add package Moq
You’ll also need a testing framework like Xunit or NUnit (I’ll use Xunit for examples here).
2. Define the Missing Interface & Complete Production Code
Your snippet references IBusinessRule, so let's define that first, then finish the GetResult method (since it was incomplete):
IBusinessRule Interface
public interface IBusinessRule { // Returns a replacement string if the rule applies, null otherwise string Apply(int number); }
Completed BusinessHandler
public class BusinessHandler : IBusinessHandler { private readonly IEnumerable<IBusinessRule> _businessRules; public BusinessHandler(IEnumerable<IBusinessRule> businessRules) { _businessRules = businessRules; } public IEnumerable<string> GetResult(int inputValue) { var outputList = new List<string>(); for (int element = 1; element <= inputValue; element++) { string tempOutput = element.ToString(); // Apply all rules to the current number foreach (var rule in _businessRules) { var ruleResult = rule.Apply(element); if (!string.IsNullOrEmpty(ruleResult)) { tempOutput = ruleResult; } } outputList.Add(tempOutput); } return outputList; } } // Don't forget the IBusinessHandler interface public interface IBusinessHandler { IEnumerable<string> GetResult(int inputValue); }
3. Test Cases for FizzBuzz Behavior
Now let’s write tests using Moq to mock our IBusinessRule implementations and validate the handler’s behavior.
Test 1: No Rules → Returns Raw Numbers
This tests the base case where no rules are applied—we should get back string representations of the input range.
[Fact] public void GetResult_WithNoRules_ReturnsRawNumberStrings() { // Arrange var emptyRules = new List<IBusinessRule>(); var handler = new BusinessHandler(emptyRules); const int input = 3; // Act var result = handler.GetResult(input).ToList(); // Assert Assert.Equal(new List<string> { "1", "2", "3" }, result); }
Test 2: Fizz Rule Only → Replaces Multiples of 3
Mock a rule that triggers for multiples of 3 and returns "Fizz".
[Fact] public void GetResult_WithFizzRule_ReplacesMultiplesOf3WithFizz() { // Arrange var fizzRuleMock = new Mock<IBusinessRule>(); // Setup: return "Fizz" for multiples of 3, null otherwise fizzRuleMock.Setup(r => r.Apply(It.Is<int>(n => n % 3 == 0))) .Returns("Fizz"); fizzRuleMock.Setup(r => r.Apply(It.Is<int>(n => n % 3 != 0))) .Returns(null); var rules = new List<IBusinessRule> { fizzRuleMock.Object }; var handler = new BusinessHandler(rules); const int input = 4; // Act var result = handler.GetResult(input).ToList(); // Assert Assert.Equal(new List<string> { "1", "2", "Fizz", "4" }, result); }
Test 3: Fizz + Buzz Rules → FizzBuzz for Multiples of 15
Combine two rules to test the classic FizzBuzz behavior.
[Fact] public void GetResult_WithFizzAndBuzzRules_ReplacesMultiplesOf15WithFizzBuzz() { // Arrange // Fizz rule setup var fizzRuleMock = new Mock<IBusinessRule>(); fizzRuleMock.Setup(r => r.Apply(It.Is<int>(n => n % 3 == 0))) .Returns("Fizz"); fizzRuleMock.Setup(r => r.Apply(It.Is<int>(n => n % 3 != 0))) .Returns(null); // Buzz rule setup var buzzRuleMock = new Mock<IBusinessRule>(); buzzRuleMock.Setup(r => r.Apply(It.Is<int>(n => n % 5 == 0))) .Returns("Buzz"); buzzRuleMock.Setup(r => r.Apply(It.Is<int>(n => n % 5 != 0))) .Returns(null); var rules = new List<IBusinessRule> { fizzRuleMock.Object, buzzRuleMock.Object }; var handler = new BusinessHandler(rules); const int input = 15; // Act var result = handler.GetResult(input).ToList(); // Assert Assert.Equal("Fizz", result[2]); // Index 2 = number 3 Assert.Equal("Buzz", result[4]); // Index 4 = number 5 Assert.Equal("FizzBuzz", result[14]); // Index 14 = number 15 }
Test 4: Input = 0 → Returns Empty List
Validate edge case where input is zero (no numbers to process).
[Fact] public void GetResult_WithInput0_ReturnsEmptyList() { // Arrange var mockRules = new List<IBusinessRule>(); var handler = new BusinessHandler(mockRules); // Act var result = handler.GetResult(0).ToList(); // Assert Assert.Empty(result); }
4. Key Mocking Tips for IEnumerable
- Mock Individual Elements: For most cases, you don’t need to mock the IEnumerable itself—just create a list of mocked
IBusinessRuleinstances and pass it to the handler. This keeps tests simple and focused. - Verify Rule Execution (Optional): If you want to ensure rules are being called correctly, add verification steps:
// Verify Fizz rule was called once for number 3 fizzRuleMock.Verify(r => r.Apply(3), Times.Once); - Use
It.Is<T>for Conditional Setup: This lets you define exactly when a rule should trigger, which is perfect for FizzBuzz’s divisibility checks.
内容的提问来源于stack exchange,提问作者user1

