繁体   English   中英

使用存储库对基本c#应用程序进行单元测试

[英]Unit Testing a Basic c# application with repository

我有一个基本的.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
    }

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

重构您的方法以使用实例变量而不是方法中的直接实例化。 将它们作为参数添加到构造函数中。 在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来触发,则可以实例化food.IsPassed = true对象,如下所示:

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

如果您无法在食物库中找到食物,或者在饮料库中找到饮料,则可以为其他各种测试用例中的每个用例创建测试方法,如果没有达到最低分,则等于分数。

另一个要注意的是,当您需要知道某个方法没有被调用时,您只需要担心Verifiable模拟。 对于这些测试,我可能不会验证方法是否被调用(在测试与实现之间以及行为之间建立更紧密的耦合)。 您只想在服务代码中的某些内容真正取决于知道其被调用的情况下,才能验证该方法是否被调用。 例如,如果您使用的是Entity Framework,并且想要确保您不会忘记调用SaveChanges()

确实,如果没有初步的重构,就无法对这些代码进行“正常”的单元测试。 但是您仍然有一个选择(有点脏):MS Fakes Library的Shims机制。

它允许您用任意代码替换任何类型的任何方法或属性(包括静态,非公共和系统)。 在您的情况下,您可以在测试方法中创建ShimsContext ,并为FoodRepository.GetFood()DrinkRepository.GetDrink()方法提供一些虚假行为,例如,空体什么都不做。 因此,当测试运行时,将执行存根代码,而不是存储库类的实际代码。 因此,您将仅测试服务代码而不执行存储库代码。

您可以查看本文以快速了解该库。

并且请记住,Shims并不是良好的单元测试方法,它只是处理此类不可测试代码的工具,以防万一您绝对需要以某种方式对其进行单元测试而无需更改代码本身。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM