首页 > 解决方案 > 使用存储库对基本 c# 应用程序进行单元测试

问题描述

我有一个基本的 .NET 应用程序,我被要求为其编写单元测试,但单元测试总是让我感到困惑。

此应用程序有两个存储库(FoodRepository 和 DrinkRepository),它们从硬编码列表中返回数据。

这是 Program.cs:

public static void Main(string[] args)
    {
        var foodSvc = new FoodService();
        var foodId = 12;
        var grade = 98.2d;
        foodSvc.UpdateFoodGrade(foodId, grade);
    }

调用:

public void UpdateFoodGrade(int foodId, double grade)
    {
        var foodRepo = new FoodRepository();
        var food = foodRepo.GetFood(foodId);

        food.Grade = grade;

        if (!food.IsPassed)
        {
            var drinkRepository = new DrinkRepository();
            var drink = drinkRepository.GetDrink(foodId);

            if (grade >= drink.MinimumPassingGrade)
            {
                food.IsPassed = true;
            }
            else
            {
                food.IsPassed = false;
            }
        }
    }

我的问题是,通常有人会为此做哪些单元测试?而且,我可以举一些例子吗?

一直在谷歌上搜索和研究,但这个概念仍然让我无法理解。

我以前在测试环境中使用过完整的集成测试,并没有真正做过单元测试。

如果有人需要更多代码来帮助解决这个问题,请告诉我。我超级卡住了。

谢谢

更新:

由于下面的原因,我得到了很多进一步的帮助,但我仍然坚持测试的其余部分。这是我更新后的服务的样子:

public class FoodService
{
    private readonly FoodRepository _foodRepo;
    private readonly DrinkRepository _drinkRepository;

    public FoodService(FoodRepository foodRepo, DrinkRepository drinkRepository)
    {
        _foodRepo = foodRepo;
        _drinkRepository = drinkRepository;
    }

    public void UpdateFoodGrade(int foodId, double grade)
    {
        var food = _foodRepo.GetFood(foodId);

        food.Grade = grade;

        if (!food.IsPassed)
        {
            var drink = _drinkRepository.GetDrink(foodId);

            if (grade >= drink.MinimumPassingGrade)
            {
                food.IsPassed = true;
            }
            else
            {
                food.IsPassed = false;
            }
        }
    }
}

更新的主要内容:

public class Program
{
    public static void Main(string[] args)
    {
        var foodRepository = new FoodRepository();
        var drinkRepository = new DrinkRepository();
        var foodSvc = new FoodService(foodRepository, drinkRepository);

        var foodId = 12;
        var grade = 98.2d;

        foodSvc.UpdateFoodGrade(foodId, grade);
    }
}

测试到目前为止(我不知道下一步该做什么)

[TestMethod]
    public void UpdateFoodGrade_Test()
    {
        //Arrange
        var foodId = 12;
        var grade = 98.2d;           
        var expected = true;

        var food = new Food() { FoodId = foodId };
        var drink = new Drink() { DrinkId = foodId };

        var foodRepositoryMock = new Mock<FoodRepository>();
        foodRepositoryMock.Setup(m => m.GetFood(foodId)).Returns(food).Verifiable();

        var drinkRepositoryMock = new Mock<DrinkRepository>();
        drinkRepositoryMock.Setup(m => m.GetDrink(foodId)).Returns(drink).Verifiable();

        var foodService = new FoodService(foodRepositoryMock.Object, drinkRepositoryMock.Object);

        //Act
        var actual = foodService.UpdateFoodGrade(foodId, grade);

        //Assert
        foodRepositoryMock.Verify();
        drinkRepositoryMock.Verify();
        Assert.AreEqual(expected, actual);
    }
}

编辑2:

我继续在接口等方面进行了重构。这是它如何摆脱的:

[TestMethod]
    public void UpdateLessonGrade_IsPassingGrade()
    {
        //Arrange
        var lessonId = 12;

        var lesson = new Lesson() { LessonId = lessonId };
        var module = new Module() { ModuleId = lessonId };

        var lessonRepositoryMock = new Mock<ILessonRepository>();
        lessonRepositoryMock.Setup(x => x.GetLesson(lessonId)).Returns(lesson);

        var moduleRepositoryMock = new Mock<IModuleRepository>();
        moduleRepositoryMock.Setup(x => x.GetModule(lessonId)).Returns(module);

        var lessonService = new LessonService(lessonRepositoryMock.Object, moduleRepositoryMock.Object);

        //Act
        lessonService.UpdateLessonGrade(12, 98.2d);

        //Assert
        Assert.IsTrue(lesson.IsPassed); // assuming it should pass in this condition
        Assert.AreEqual(98.2d, lesson.Grade); // expected Lesson Grade should be what you expected the grade to be after you call UpdateLessonGrade
    }

标签: c#asp.netunit-testing

解决方案


我现在正在使用移动设备,我可以在本周末晚些时候尝试更新答案,但这应该可以帮助您入门。

重构您的方法以使用实例变量而不是方法中的直接实例化。将它们作为参数添加到您的构造函数中。在 main 方法中创建您的存储库实例并将它们传递给服务构造函数。

现在,您可以使用 Moq 或实体框架的内存提供程序之类的东西。

至于测试什么,基本上测试每一条分支逻辑。至少,每个 if 语句和 else 条件。您还应该测试当您的存储库对象没有找到您要查找的内容时会发生什么(例如返回 null)。顺便说一句,我至少计算了六个测试。

更新: 太棒了!查看问题中更新的代码,一切都在正确的轨道上。

在您的测试方法中,您需要添加:

var foodService = new FoodService(foodRepositoryMock.Object, drinkRepositoryMock.Object);

这将使用模拟对象初始化您的服务。

然后,您需要使用以下测试参数调用您的服务:

foodService.UpdateFoodGrade(12, 98.2d);

最后一部分是使用以下断言检查您的食物对象:

Assert.IsTrue(food.IsPassed) // assuming it should pass in this condition
Assert.Equals(98.2d, food.Grade); // expectedFoodGrade should be what you expected the grade to be after you call UpdateFoodGrade

看起来您还需要进一步充实您的 Drink 对象的实例。您需要指定一个值,MinimumPassingGrade因为它用于驱动 if 语句中的决策逻辑,例如,如果您想要food.IsPassed = true触发,您可以像这样实例化饮料对象:

var drink = new Drink() { DrinkId = foodId, MinimumPassingGrade = 50.0d };

您将为其他各种测试用例中的每一个创建测试方法,最低失败,当它等于分数时,如果您在食品回购中找不到食物,或者在饮料回购中找不到饮料等。

Verifiable另一个注意事项,当您需要知道某个方法被调用/未被调用时,您只需要担心模拟。对于这些测试,我可能不会验证是否调用了方法(在您的测试和实现与行为之间创建更紧密的耦合)。只有当服务代码中的某些内容真正依赖于知道它被调用时,您才会想要验证这些方法是否被调用。例如,如果您正在使用实体框架并且您想确保您没有忘记调用SaveChanges().


推荐阅读