Статьи

Модульное тестирование 101: инверсия управления

Inversion Of Control является одним из наиболее распространенных и широко используемых методов обработки зависимостей классов при разработке программного обеспечения и может легко стать наиболее важной практикой в ​​модульном тестировании. По сути, он определяет, является ли ваш код модульно-тестируемым или нет. Не только это, но также может помочь значительно улучшить общую структуру и дизайн программного обеспечения. Но о чем это все? Это действительно так важно? Надеюсь, мы очистим их в следующих строках

Выявление Зависимостей Класса

Как мы упоминали ранее, Inversion Of Control — это метод, используемый для эффективной обработки зависимостей классов; но что именно является зависимостью ? В реальной жизни, например, автомобилю нужен двигатель для работы; без этого это, вероятно, не будет работать вообще. В программировании это одно и то же; когда классу нужен другой класс для правильного функционирования, он зависит от него. Это называется зависимостью класса или связью .

Давайте посмотрим на следующий пример кода:

public class UserManager
{
    private Md5PasswordHasher passwordHasher;

    public UserManager()
    {
        this.passwordHasher = new Md5PasswordHasher();
    }

    public void ResetPassword(string userName, string password)
    {
        // Get the user from the database
        User user = DataContext.Users.GetByName(userName);

        string hashedPassword = this.passwordHasher.Hash(password);

        // Set the user new password
        user.Password = hashedPassword;

        // Save the user back to the database.
        DataContext.Users.Update(user);
        DataContext.Commit();
    }

    // More methods...
}

public class Md5PasswordHasher
{
    public string Hash(string plainTextPassword)
    {
        // Hash password using an encryption algorithm...
    }
}

Предыдущий код описывает два класса, UserManagerи PasswordHasher. Мы можем видеть, как UserManagerкласс инициализирует новый экземпляр PasswordHasherкласса в своем конструкторе и сохраняет его как переменную уровня класса, чтобы все методы в классе могли использовать его (строка 3). Метод, на котором мы собираемся сосредоточиться, — это ResetPasswordметод. Как вы уже могли заметить, строка 15 подсвечивается. Эта строка использует PasswordHasherэкземпляр, следовательно, отмечая сильную зависимость класса между UserManagerи PasswordHasher.

Не звоните нам, мы вам позвоним

Когда класс создает экземпляры своих зависимостей, он знает, какую реализацию этой зависимости использует и, вероятно, как она работает. Класс управляет своим поведением. Используя инверсию управления, любой, кто использует этот класс, может указать конкретную реализацию каждой из используемых им зависимостей; на этот раз пользователь класса — тот, кто частично контролирует поведение класса (или то, как он ведет себя в тех частях, где он использует предоставленные зависимости).

В любом случае, все это довольно запутанно. Давайте посмотрим на пример:

public class UserManager
{
    private IPasswordHasher passwordHasher;

    public UserManager(IPasswordHasher passwordHasher)
    {
        this.passwordHasher = passwordHasher;
    }

    public void ResetPassword(string userName, string password)
    {
        // Get the user from the database
        User user = DataContext.Users.GetByName(userName);

        string hashedPassword = this.passwordHasher.Hash(password);

        // Set the user new password
        user.Password = hashedPassword;

        // Save the user back to the database.
        DataContext.Users.Update(user);
        DataContext.Commit();
    }

    // More methods...
}

public interface IPasswordHasher
{
    string Hash(string plainTextPassword);
}

public class Md5PasswordHasher : IPasswordHasher
{
    public string Hash(string plainTextPassword)
    {
        // Hash password using an encryption algorithm...
    }
}

Инверсия управления обычно реализуется путем применения шаблона проектирования, называемого шаблоном стратегии (как определено в книге «Банда четырех» ). Этот шаблон состоит в абстрагировании конкретных реализаций компонентов и алгоритмов от остальных классов путем предоставления только интерфейса, который они могут использовать; таким образом, делая реализации взаимозаменяемыми во время выполнения и инкапсулируя, как эти реализации работают, так как любой класс, использующий их, не должен заботиться о том, как они работают.

Шаблон стратегии

Итак, чтобы достичь этого, нам нужно разобраться в некоторых вещах:

  • Абстрагировать интерфейс от Md5PasswordHasherкласса IPasswordHasher; так что любой может написать пользовательские реализации хэшей паролей (строки 28-31).
  • Отметьте Md5PasswordHasherкласс как реализацию IPasswordHasherинтерфейса (строка 33).
  • Измените тип используемого хэша пароля UserManagerна IPasswordHasher(строка 3).
  • Добавьте новый параметр конструктора типа IPasswordHasherinterface (строка 5), который является экземпляром, который UserManagerкласс будет использовать для хеширования своих паролей. Таким образом, мы делегируем создание зависимостей пользователю класса и позволяем пользователю предоставлять любую реализацию, которую он хочет, позволяя ему контролировать способ хэширования пароля.

В этом и заключается сама суть инверсии управления: минимизировать сцепление классов. Пользователь UserManagerкласса теперь может контролировать, как хэшируются пароли. Управление хэшированием пароля было инвертировано из класса в пользователя. Вот пример того, как мы можем указать единственную зависимость UserManagerкласса:

IPasswordHasher md5PasswordHasher = new Md5PasswordHasher();
UserManager userManager = new UserManager(md5PasswordHasher);

userManager.ResetPassword("luis.aguilar", "12345");

Итак, почему это полезно? Что ж, мы можем сойти с ума и создать собственную реализацию хеширования, которая будет использоваться классом UserManager:

// Plain text password hasher:
public class PlainTextPasswordHasher : IPasswordHasher
{
    public string Hash(string plainTextPassword)
    {
        // Let's disable password hashing by returning
        // the plain text password.
        return plainTextPassword;
    }
}

// Usage:
IPasswordHasher plainTextPasswordHasher = new PlainTextPasswordHasher();
UserManager userManager = new UserManager(plainTextPasswordHasher);

// Resulting password will be: 12345.
userManager.ResetPassword("luis.aguilar", "12345");

Вывод

На этом мы завершаем нашу статью об инверсии контроля. Надеюсь, немного потренировавшись, вы сможете начать применять это к своему коду. Конечно, самое большое преимущество этого метода связано с модульным тестированием. Итак, какое отношение это имеет к юнит-тестированию? Ну, мы увидим это, когда начнем насмехаться над типами . Так что следите за обновлениями!