Я должен сделать модульный тест для методов, которые используют строки подключения. Можно ли использовать Entity Framework, чтобы помочь мне проверить соединение - PullRequest
1 голос
/ 28 октября 2019

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

public IActionResult FamilyDoctor()
{
    List<Patient> GetPatientData()
    {
        List<Patient> PatientDataArray = new List<Patient>();

        Connection conn = new Connection();
        MySqlConnection connection = null;
        try
        {
            connection = new MySqlConnection(conn.getConnectionString());
            connection.Open();
            MySqlCommand cmd = new MySqlCommand();
            cmd.Connection = connection;
            cmd.CommandText = "SELECT * FROM measurement;";
            cmd.Prepare();

            MySqlDataReader reader = cmd.ExecuteReader();

            while (reader.Read())
            {

                int ID = reader.GetInt32("id");
                decimal bloodsugar = reader.GetDecimal("bloodsugar");
                decimal bloodsugardesired = reader.GetDecimal("bloodsugardesired");
                string description = reader.GetString("description");
                DateTime time = reader.GetDateTime("time");
                //int status = reader.GetInt16("status");

                PatientDataArray.Add(new Patient(ID, bloodsugar, bloodsugardesired, description, time));

            }

        }
        finally
        {
            if (connection != null)
                connection.Close();
            }

            return PatientDataArray;
        }

        ViewData["Patient"] = GetPatientData();

        return View();
    }
}

1 Ответ

3 голосов
/ 28 октября 2019

Код, подобный этому, не может быть проверен модулем, только тестирование интеграции с известным состоянием данных. EF не поможет тебе. Чтобы написать код, проверяемый модулем, вам необходимо начать принудительное разделение проблем и инверсию управления. Например, шаблон репозитория может помочь, обернув поиск данных так, чтобы бизнес-логика могла получить зависимость от репозитория, которую может смоделировать модульный тест. Независимо от того, использует ли этот репозиторий EF или ADO, не имеет значения для тестируемого кода.

При этом вышеприведенный код не имеет никакой бизнес-логики, он просто возвращает все данные о пациентах из базы данных. ,Модульные тесты направлены на тестирование бизнес-логики, которая будет что-то делать на основе извлеченных данных. Однако то, что вы могли бы использовать в качестве наиболее простого примера вместо приведенного выше кода:

public class PatientRepository : IPatientRepository
{
    public IEnumerable<Patient> GetAllPatients()
    {
        List<Patient> PatientDataArray = new List<Patient>();

        Connection conn = new Connection();
        MySqlConnection connection = null;
        try
        {
            connection = new MySqlConnection(conn.getConnectionString());
            connection.Open();
            MySqlCommand cmd = new MySqlCommand();
            cmd.Connection = connection;
            cmd.CommandText = "SELECT * FROM measurement;";
            cmd.Prepare();

            MySqlDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                int ID = reader.GetInt32("id");
                decimal bloodsugar = reader.GetDecimal("bloodsugar");
                decimal bloodsugardesired = reader.GetDecimal("bloodsugardesired");
                string description = reader.GetString("description");
                DateTime time = reader.GetDateTime("time");

                PatientDataArray.Add(new Patient(ID, bloodsugar, bloodsugardesired, description, time));
           }
       }
       finally
       {
           if (connection != null)
               connection.Close();
       }

       return PatientDataArray;
}

, затем в контроллере:

public PatientController(IPatientRepository patientRepository)
{
    this.PatientRepository = patientRepository;
}

public IActionResult FamilyDoctor()
{
     ViewData["Patient"] = PatientRepository.GetAllPatients();
     return View();
} 

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

Это может помочь сделать код тестируемым, но с репозиторием или без него не особенно эффективно. Ваш метод доступа к данным делает SELECT * из базы данных для заполнения модели. Entity Framework и шаблон единицы работы могут помочь сделать это намного более эффективным, но в то же время простым в тестировании.

Например, вы определяете сущность, отражающую полное измерение и связанные записи из вашей системы. Используя шаблон репозитория, вы возвращаете IQueryable ваших сущностей, которые может использовать ваш тестируемый код вызова.

Контроллер определяет единицу работы, которая служит контейнером для DbContext. (Думайте о DbContext как о Connection)

public PatientController(IUnitOfWorkFactory unitOfWorkFactory, IPatientRepository patientRepository)
{
    this.UnitOfWorkFactory = unitOfWorkFactory;
    this.PatientRepository = patientRepository;
}

public IActionResult FamilyDoctor()
{
    using(var unitOfWork = UnitOfWorkFactory.Create())
    {
        var measurements = PatientRepository.GetAllMeasurements(unitOfWork);
        var viewModels = measurements.Select(x => new MeasurementViewModel
        {
            ID = x.Id,
            BloodSugar = x.BloodSugar,
            BloodSugarDesired = x.BloodSugarDesired,
            Description = x.Description,
            Time = x.Time
        }).ToList();

     ViewData["Patient"] = viewModels;
     return View();
}    

Теперь приведенная выше фабричная единица работы / образец - просто пример заглушки. Вы можете искать различные примеры того, где / как может быть реализован шаблон единицы работы. Цель UoW состоит в том, чтобы обернуть DbContext. Он служит фасадом вокруг DBContext EF, облегчая замену имитацией для тестирования.

Там, где хранилище измерений с использованием EF больше похоже на:

public IQueryable<Measurement> GetAllMeasurements(IUnitOfWork unitOfWork)
{
    return unitOfWork.Context.Measurements.AsQueryable();
}

Примечание: AsQueryable()необходим, если вы просто хотите вернуть IQueryable для DbSet в целом. Любая фильтрация, к которой вы применяете условия Where, автоматически возвращает IQueryable. Во многих случаях у вас будут правила низкого уровня для ваших данных, такие как, например, состояние IsActive, чтобы отражать измерения для активных, а не удаленных / скрытых пациентов. Хранилище может применять эти правила базового уровня, так что вам не нужно помнить, чтобы проверять их повсюду:

public IQueryable<Patient> GetAllPatients(IUnitOfWork unitOfWork)
{
    return unitOfWork.Context.Measurements.Where(x => x.IsActive && x.Patient.IsActive);
}

Это вернет только активные измерения для активных пациентов. Для более специфичных для сценария критериев вы можете добавить методы или аргументы к вызовам, чтобы хранилище применило дополнительную фильтрацию, но я предпочитаю просто позволить потребителю применять определенные критерии по мере необходимости. Это делает хранилища более простыми, легкими и легкими для имитации.

Что это дает нам?

Возвращая IQueryable<Measurement>, мы позволяем потребителям хранилища выбирать, какие данные они хотятили что они хотят делать с этими данными. В приведенном выше случае, выбрав только 5 полей, выполняемый SQL просто выберет эти 5 столбцов, а не SELECT *. Любые Where условия, которые мы применяем, будут переведены в SQL, что приведет к более быстрым запросам. Если мы просто хотим Count или проверку с помощью Any, это приведет к гораздо лучшему выполнению запросов, чем отбор всех данных обратно для проверки на них.

Код гораздо проще для модульного тестирования, так как наше тестовое устройство может создать макет IUnitOfWorkFactory и Репозитория, где макет репозитория просто возвращает список или массив подготовленных объектов Patient как .AsQueryable() для логикипотреблять.

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

Следующим шагом будет посмотреть, как вы возвращаете данные. Модульные тесты хорошо подходят для утверждения результатов поведения путем проверки возвращаемых значений и утверждения имитаций. Использование определенных структур ASP.Net, таких как ViewData, затрудняет тестирование. Возврат View(viewModels) будет немного проще. Обычно я бы рекомендовал либо проектировать страницы для асинхронной загрузки данных, когда методы «View» не принимают заполненную модель, а вместо этого визуализировать пустой и запускать загрузку данных с помощью вызовов GET, которые возвращают JSon() в качестве ActionResult, или передавать вручнуюслужбе, которая возвращает коллекцию применимых данных, где контроллер просто упаковывает эти данные. В последнем случае вы тестируете сервисы, а не контроллер.

Итак, имея в виду, можете ли вы взять существующий код и протестировать его? Нет, не совсем. Но это должно дать вам некоторые боеприпасы, чтобы вернуться к остальной части команды и сказать, что если они хотят, чтобы код охватывался модульными тестами, это тот тип рефакторинга, который они должны применить, чтобы это произошло. Модульное тестирование - это то, что должно быть выполнено заранее. Это может быть трудно внедрить на поздней стадии процесса разработки, если в проекте не используются шаблоны, удобные для тестирования, такие как IoC / DI.

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