Модульное тестирование базового приложения на c # с репозиторием - PullRequest
0 голосов
/ 24 ноября 2018

У меня есть базовое приложение .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;
            }
        }
    }
}

Обновленный Main:

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
    }

Ответы [ 2 ]

0 голосов
/ 24 ноября 2018

Действительно, такой код не может быть "нормально" протестирован модулем без предварительного рефакторинга.Но у вас все еще есть один (немного грязный) вариант: механизм Shims библиотеки MS Fakes.

Он позволяет вам заменить любой метод или свойство любого типа (включая статический, непубличный и системный) любым произвольным кодом,В вашем случае вы можете создать ShimsContext в своем методе тестирования и дать некоторое поддельное поведение для методов FoodRepository.GetFood() и DrinkRepository.GetDrink(), например, пустое тело, ничего не делающее.Итак, когда ваш тест будет запущен, ваш код-заглушка будет выполнен вместо реального кода классов репозитория.Таким образом, вы будете тестировать только код службы без выполнения кода репозиториев.

Вы можете проверить эту статью для быстрого ознакомления с библиотекой.

И имейте в виду, что Shimsэто не способ хорошего юнит-тестирования, это всего лишь инструмент для работы с таким не тестируемым кодом, если вам абсолютно необходимо как-то его юнит-тестирование без изменения самого кода.

0 голосов
/ 24 ноября 2018

Я сейчас на мобильном устройстве, я могу попытаться обновить ответ позже в эти выходные, но это должно помочь вам начать.

Рефакторинг вашего метода, чтобы использовать переменные экземпляра вместо прямого создания экземпляра вметод.Добавьте их в качестве параметров в ваш конструктор.В основном методе создайте свои экземпляры репозитория и передайте их конструктору службы.

Теперь вы можете использовать что-то вроде Moq или провайдера памяти для структуры сущностей.

Что касается того, что тестировать,в основном тестировать каждый кусок логики ветвления.Как минимум, каждая часть оператора if и условия else.Вы также должны проверить, что происходит, когда ваши объекты репозитория не находят то, что вы ищете (например, возвращает ноль).Случайно я считаю по меньшей мере шесть тестов.

Обновление: Браво!Глядя на ваш обновленный код в вопросе, все идет по правильному пути.

В вашем методе тестирования вы захотите добавить:

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 вызывал триггер, вы бы создали экземпляр объекта drink следующим образом:

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

Вы могли бы создать методы тестирования для каждого из других различных тестовых случаев, не пройдя минимум, когда он равен счету, если вы не можете найти еду в репо еды или напиток в репо напитка,и т. д.

Еще одно замечание: вам нужно беспокоиться о Verifiable макетах, когда вам нужно знать, что метод был / не был вызван.Для этих тестов я, вероятно, не проверю, были ли вызваны методы (создает более тесную связь между вашим тестом и реализацией, а не поведением).Вы хотите убедиться, что методы были вызваны, только если что-то в вашем сервисном коде действительно зависит от знания того, что он был вызван.например, если вы используете Entity Framework и хотите убедиться, что не забыли позвонить SaveChanges().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...