Рефакторинг сложного конструктора, который вызывает «базовые» определенные уровни глубоко - PullRequest
1 голос
/ 29 февраля 2020

Наконец, я много читаю о коде Legacy и о том, как с ним обращаться. У меня есть пример проекта, который содержит плохо написанный код, и я хотел бы реорганизовать его.

ПРИМЕЧАНИЕ. Тестов не существует, поэтому я хотел бы сначала добавить несколько тестов, чтобы убедиться, что я не что-то сломать с помощью рефакторинга, но я не знаю, как это проверить.

Учитывая следующую диаграмму ULM:

enter image description here

I хотите написать UT для методов в классе Manager (ПРИМЕЧАНИЕ: они не отображаются в ULM). Проблема в том, что для класса Manager требуется объект User, который наследует несколько уровней глубины.

Для простоты я не полностью нарисовал весь график, но давайте предположим, что конструктор объекта 'User' нуждается в несколько аргументов, и все эти аргументы представляют объекты с такой цепочкой наследования. Мне не разрешено использовать null, поскольку эти объекты используются где-то на уровне наследования, и при использовании «null» будет выдано «исключение Null».

Я хочу попрактиковаться в TDD для добавления изменение класса Manager, но, следовательно, мне нужно сначала создать класс «Manager». Рефакторинг всего остального, чтобы просто сделать Manager, неприемлем.

Я знаю одну технику, которая изменяет аргумент «Пользователь» на интерфейс, но кажется неправильным использовать интерфейс для «Пользователь».

Любой совет?

Ответы [ 3 ]

0 голосов
/ 01 марта 2020

Я думаю, что самое простое решение - Proxy Design Pattern. Вам нужно будет создать ProxyUser, который содержит только необходимые свойства и методы от пользователя (я имею в виду только то, что использует Manager). Затем в модульном тесте вы можете смоделировать этого ProxyUser.

public class Manager
{
    public User User {get;set;}
    public void DoSomething()
    {
        Console.WriteLine(User.Name);
    }
}

public abstract class BaseUser
{
    public virtual string Name {get;}
}

public class ProxyUser : BaseUser
{
    private User _realUser;
    public override string Name => _realUser.Name;
}

public class User : BaseUser
{
    public override string Name {get;set;}
}

А затем, в модульном тесте, вы смоделируете BaseUser

0 голосов
/ 01 марта 2020

Я бы предложил рефакторинг Inversion of Control. Это позволит вам использовать интерфейс, который использует их в «шаблонах», которые были опробованы другими. (простите, если это будет покровительственно, я не знаю ваш уровень опыта)

Итак, вы в основном:

[Fact]
    public void ManagerTest()
    {
        // arrange
        MockedUser mockedUser = new MockedUser();

        // act
        Manager manager = new Manager(mockedUser);

        // assert 
        Assert.Equal("Firstname", manager.GetUser().GetFirstName());
    }


public class Manager
    {
        private IUser _user; 

        public Manager(IUser user)
        {
            _user = user;
        }

        public IUser GetUser()
        {
            return _user;
        }
    }


public class MockedUser : IUser
    {
        public string GetFirstName()
        {
            return "Firstname";
        }

        public string GetFullName()
        {
            return "Firstname Middlename Lastname";
        }

        public string GetLastName()
        {
            return "Lastname";
        }

        public string GetMiddleNames()
        {
            return "Middlename";
        }

        public void SetFirstName(string firstname)
        {
            return;
        }

        public void SetFullName(List<string> fullName)
        {
            return;
        }

        public void SetLastName(string lastname)
        {
            return;
        }

        public void SetMiddleNames(List<string> middleNames)
        {
            return;
        }
    }

public interface IUser : INames
    {
        void SetFirstName(string firstname);

        void SetLastName(string lastname);

        void SetMiddleNames(List<string> middleNames);

        void SetFullName(List<string> fullName);

        string GetFullName();

        string GetFirstName();

        string GetLastName();

        string GetMiddleNames();
    }

Теперь это технически не так далеко, что внутри пользователя. Он заменен интерфейсом. (Вероятно, у вас будут разные методы :))

Технически не имеет значения, что делает "настоящий" объект IUser, ваш макет может просто вернуть жестко запрограммированный мусор.

А затем, когда вы реализуете Io C, вы можете сначала выполнить модульный тест для интерфейса. А потом рефакторинг.

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

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

0 голосов
/ 29 февраля 2020

Кажется, что класс User нарушает принцип единой ответственности и, исходя из именования, ведет себя как POCO и сервис.

Если это так, то создание интерфейса действительно будет правильным решением для термина сортировки, пока вы не реорганизуете также User и не разбиваете его на несколько классов. POCO обычно не обращаются к интерфейсам. Сервисы почти всегда обращены к интерфейсам и слабо связаны.

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

Если сейчас это слишком много, вы можете создать класс Fake. Расширьте пользователя следующим образом:

public class FakeUser: User {

}

И предоставьте поддельный объект, который будет управлять выполнением вашего UserService

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