Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

Вопрос Программисту - как заменить этот синглтон на его мок?

762  
dymanoid знакомое лицо08.11.18 21:31
dymanoid
08.11.18 21:31 

Вопрос специально к юзеру Программист.

Ты утверждаешь, что сделав сеттер точки доступа к синглтону internal, можно сделать этот синглтон mockable.

Моя фраза:

Замена модификатора сеттера на internal никаким образом не поможет синглтон сделать mockable.

Твой ответ:

Ошибаешься.

Ну и дальше пошла от тебя какая-то пурга, мерянье пиписьками:

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

Сначала по поводу "привести рабочий код синглтона". Да *б твою ж мать! Ругаться грязно хочется. Меня кто-то просил рабочий код синглтона приводить? Ты вообще понимаешь разницу между схематичным наброском и рабочим кодом? Ты на StackOverflow тоже ко всем вопросам и ответам комменты такие пишешь, что у них там код нерабочий, потому что половины не хватает? Я же написал - это каракули, чтобы продемонстрировать, сколько кривых зависимостей может быть.


А то получается такой диалог:

dymanoid: Вот схема ДТП. Объясните мне, как вообще этот чувак на зелёной тачке мог сюда повернуть?

Программист: Да у тебя зелёная тачка вообще никуда не поедет, ты там колёса не нарисовал.

dymanoid: Да при чём тут колёса? Я вообще на автозаводе работаю и всё могу про колёса рассказать. Ты лучше посмотри на взаимное положение тачек на картинке!

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


Я могу привести тебе как минимум 7 разновидностей рабочего кода синглтонов и рассказать про все их достоинства, недостатки, быстродействие и до фига всего ещё. Но не в этой теме.


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


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


(Ну и, конечно, это абсолютно дурацкое решение делать сеттер в синглтоне internal - открываем ворота для всех подряд в сборке, мол, меняйте объект, когда хотите. Уже предвкушаю ответы: "да никто не будет этого делать", "надо в комментариях написать, что только в тестах менять можно" - прям в каком-то идеальном мире живёшь, или не работал ни разу в командах по 200 человек.)


    class UserRepository
    {
        private static readonly Lazy<UserRepository> instance = new Lazy<UserRepository>();

        public static UserRepository Instance => instance.Value;

        private UserRepository() { }

        public string CurrentUser => "get-Windows-login"; /* Здесь сложный код получения юзера */

        public object GetUserToken(string userName) => null; /* Здесь сложный код получения токена из имени */
    }

    class ConfigRepository
    {
        private static readonly Lazy<ConfigRepository> instance = new Lazy<ConfigRepository>();

        public static ConfigRepository Instance => instance.Value;

        private readonly object userToken;

        private ConfigRepository()
        {
            userToken = UserRepository.Instance.GetUserToken(UserRepository.Instance.CurrentUser);
        }

        private IEnumerable GetConfigItems()
        {
            using (var conn = new SqlConnection(Properties.Resources.ConnectionString))
            {
                using (var dbContext = new MyDbContext(conn, contextOwnsConnection: true))
                {
                    return dbContext.ConfigItems.Where(c => c.UserToken == userToken).ToList();
                }
            }
        }

        public int HowManyConfigItems(DateTime oldest)
        {
            return GetConfigItems().Count(c => c.CreationDate >= oldest);
        }
    }

    [TestClass]
    public class MyTestClass
    {
        [TestMethod]
        public void MyTestMethod()
        {
            // ну и как?
            var configItemsCount = ConfigRepository.Instance.HowManyConfigItems(DateTime.Now);

            Assert.AreEqual(42, configItemsCount);
        }
    }
#1 
Программист коренной житель09.11.18 11:44
NEW 09.11.18 11:44 
в ответ dymanoid 08.11.18 21:31
Ниже вот тебе рабочий код синглтона, чтобы не придирался.

Придраться все равно есть к чему :)


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


А в этой теме ты мне, пожалуйста, покажи, как ты с internal сеттером к точке доступа к синглтону собираешься его заменить на мок в тесте.
  public interface IConfigRepository
  {
    int HowManyConfigItems(DateTime oldest);
  }

  public class ConfigRepository : IConfigRepository
  {
    private static IConfigRepository instance = null;
    public static IConfigRepository Instance
    {
      get
      {
        if (instance == null)
          instance = new ConfigRepository();
        return instance;
      }
      internal set
      {
        instance = value;
      }
    }
    private object userToken;
    private ConfigRepository()
    {
      userToken = UserRepository.Instance.GetUserToken(UserRepository.Instance.CurrentUser);
    }
    private IEnumerable GetConfigItems()
    {
      using (var conn = new SqlConnection(Properties.Resources.ConnectionString))
      {
        using (var dbContext = new MyDbContext(conn, contextOwnsConnection: true))
        {
          return dbContext.ConfigItems.Where(c => c.UserToken == userToken).ToList();
        }
      }
     }
    public virtual int HowManyConfigItems(DateTime oldest)
    {
      return GetConfigItems().Count(c => c.CreationDate >= oldest);
    }


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

Ну с таким же успехом можно сказать, что дурацкое решение - это использование статических объектов в общем и синглтонов в частности.

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

#2 
Simple Nothing is f*cked09.11.18 13:25
Simple
NEW 09.11.18 13:25 
в ответ Программист 09.11.18 11:44

Чисто ради интереса поискал что-то похожее на jmockit из мира .net. Вылезло два продукта, оба коммерческих :)

#3 
Simple Nothing is f*cked09.11.18 13:26
Simple
NEW 09.11.18 13:26 
в ответ dymanoid 08.11.18 21:31

Ты какой-то агрессивный. Дедлайн на носу? ;)

#4 
Программист коренной житель09.11.18 13:57
NEW 09.11.18 13:57 
в ответ Simple 09.11.18 13:25

Ошеров приводил фреймворки, которыми можно сделать то, что хочет dymanoid... правда он же говорит, что делать этого не стоит :D

#5 
Simple Nothing is f*cked09.11.18 14:07
Simple
NEW 09.11.18 14:07 
в ответ Программист 09.11.18 13:57

А я считаю, что стоит ;)

#6 
dymanoid знакомое лицо09.11.18 21:16
dymanoid
NEW 09.11.18 21:16 
в ответ Программист 09.11.18 11:44

Твой код синглтона - говно, потому что он не потокобезопасный. Именно вот на эту говённую конструкцию Джон Скит дал свою рецензию:

Bad code! Do not use!

Если ты не знаешь, кто такой Джон Скит, не читал его "C# in depth" или скажешь, что он неправ или тебе плевать на его мнение, то я тебя поставлю в игнор и нам не о чем больше говорить.


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


Ты присобачил к синглтону интерфейс и открыл сеттер. Это не решение.

Объясняю.

Во-первых, это нарушение паттерна. Теперь любой джуниор может заменить в коде объект на что угодно. Что там в определении синглтона написано?

Синглтон - порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса

Ты нарушаешь контракт, данный всем клиентам, ради очень сомнительной цели "протестить чего-то там, что юзает этот синглтон".


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


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

#7 
dymanoid знакомое лицо09.11.18 21:19
dymanoid
NEW 09.11.18 21:19 
в ответ Simple 09.11.18 13:26

Да не, зарелизились уже давно.

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

#8 
Программист коренной житель12.11.18 15:32
NEW 12.11.18 15:32 
в ответ dymanoid 09.11.18 21:16
Твой код синглтона - говно, потому что он не потокобезопасный.

И это говорит человек, который запостил неработающий код :D

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


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

Мне это можешь не рассказывать, я, как и многие, в этом живу :) Однако это не значит, что легаси код нельзя изменять.


Ты присобачил к синглтону интерфейс и открыл сеттер. Это не решение.

Сеттер стал видимым только для одной сборки. Интернел - это не открытый сеттер. Те же самые MS очень часто используют internel.


Ты нарушаешь контракт, данный всем клиентам, ради очень сомнительной цели "протестить чего-то там, что юзает этот синглтон".

Во-первых, тестирование - это не то, чтобы "очень сомнительная" цель.

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


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

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

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

Если ты хочешь тестировать возвращаемую синглтоном реацизацию (в данном случае ConfigRepository), то надо делать возможность для этого теста.



#9