Вопрос Программисту - как заменить этот синглтон на его мок?
Вопрос специально к юзеру Программист.
Ты утверждаешь, что сделав сеттер точки доступа к синглтону 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); } }
Ниже вот тебе рабочий код синглтона, чтобы не придирался.
Придраться все равно есть к чему :)
А в этой теме ты мне, пожалуйста, покажи, как ты с 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 - открываем ворота для всех подряд в сборке, мол, меняйте объект, когда хотите.
Ну с таким же успехом можно сказать, что дурацкое решение - это использование статических объектов в общем и синглтонов в частности.
Если тебе надо "закрыть ворота", то просто положи этот класс в отдельную сборку. Ну или извращайся с рефлекшенами или с хот-патчами.
Твой код синглтона - говно, потому что он не потокобезопасный. Именно вот на эту говённую конструкцию Джон Скит дал свою рецензию:
Bad code! Do not use!
Если ты не знаешь, кто такой Джон Скит, не читал его "C# in depth" или скажешь, что он неправ или тебе плевать на его мнение, то я тебя поставлю в игнор и нам не о чем больше говорить.
Код из моего примера - максимально нетестируемый, это верно. Но не потому что я пишу такой код. Это потому что я показываю, каким говённым может быть древний лигаси-код из огромного продукта, где боятся лишний байт подвинуть.
Ты присобачил к синглтону интерфейс и открыл сеттер. Это не решение.
Объясняю.
Во-первых, это нарушение паттерна. Теперь любой джуниор может заменить в коде объект на что угодно. Что там в определении синглтона написано?
Синглтон - порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса
Ты нарушаешь контракт, данный всем клиентам, ради очень сомнительной цели "протестить чего-то там, что юзает этот синглтон".
Во-вторых, ты просто не решил поставленную задачу. Из моего примера видно, что я хочу протестить метод синглтона, а ты мне предлагаешь этот метод заменить на заглушку.
Я воздержусь от оценок твоих навыков и умений ("в отличие от тебя"). Код твоего синглтона и правда говно, но это объективно. Но я всем сердцем надеюсь, что в моей команде не будет таких "программистов", как ты.
Твой код синглтона - говно, потому что он не потокобезопасный.
И это говорит человек, который запостил неработающий код :D
Для того, чтобы мой пример был потокобезопасным нужно добавить 4 строчки (две из которых будут скобками).
Это потому что я показываю, каким говённым может быть древний лигаси-код из огромного продукта, где боятся лишний байт подвинуть.
Мне это можешь не рассказывать, я, как и многие, в этом живу :) Однако это не значит, что легаси код нельзя изменять.
Ты присобачил к синглтону интерфейс и открыл сеттер. Это не решение.
Сеттер стал видимым только для одной сборки. Интернел - это не открытый сеттер. Те же самые MS очень часто используют internel.
Ты нарушаешь контракт, данный всем клиентам, ради очень сомнительной цели "протестить чего-то там, что юзает этот синглтон".
Во-первых, тестирование - это не то, чтобы "очень сомнительная" цель.
Во-вторых, инжект нужен для того, чтобы тестировать те части, которые этот синглтон используют.
Во-вторых, ты просто не решил поставленную задачу. Из моего примера видно, что я хочу протестить метод синглтона, а ты мне предлагаешь этот метод заменить на заглушку.В твоем примере, метод синглтона называется Instance.
Надо четко различать, что именно ты хочешь тестировать. Если ты хочешь тестировать синглтон (паттерн), то это один тест. И личто я не вижу смысла в этом тесте.
Если ты хочешь тестировать компоненты, которые этот синглтон используют - надо делать возможность для инжекта.
Если ты хочешь тестировать возвращаемую синглтоном реацизацию (в данном случае ConfigRepository), то надо делать возможность для этого теста.