Подтвердить что ты не робот

Неправильно ли добавлять код только для модульного тестирования?

Я пишу класс, чтобы помочь мне unit test мой код. Это выглядит так:

/// <summary>
/// Wrapper for the LogManager class to allow us to stub the logger
/// </summary>
public class Logger
{
    private static ILogger _logger = null;

    /// <summary>
    /// This should be called to get a valid logger.
    /// </summary>
    /// <returns>A valid logger to log issues to file.</returns>
    public static ILogger GetLogger()
    {
        if (_logger == null)
          _logger = LogManager.GetLogger("logger");

        return _logger;
    }

    /// <summary>
    /// This is used by unit tests to allow a stub to be used as a logger.
    /// </summary>
    /// <param name="logger"></param>
    /// <returns></returns>
    public static ILogger GetLogger(ILogger logger)
    {
        _logger = logger;
        return _logger;
    }
}

Второй метод предназначен только для модульного тестирования. Я никогда не собираюсь его вызвать в моем производственном коде.

Это плохая практика? Должен ли я найти другой способ, который этого не делает?

4b9b3361

Ответ 1

Вы могли бы подумать об этом:

/// <summary>
/// Wrapper for the LogManager class to allow us to stub the logger
/// </summary>
public class Logger
{
    private static ILogger _logger = null;

    /// <summary>
    /// This should be called to get a valid logger.
    /// </summary>
    /// <returns>A valid logger to log issues to file.</returns>
    public static ILogger Logger
    {
        get
        {
            if (_logger == null)
            {
                _logger = LogManager.GetLogger("logger");
            }
            return _logger
        }
        set
        {
            _logger = value;
        }
    }
}

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

Ответ 2

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

Ответ 3

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

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

Эти классы должны требовать ILogger в своих конструкторах, делая очевидным, что им нужно для работы, и сделать их легко проверенными.

Ответ 4

ИМО, да, это плохой код. Две причины:

  • Logger знает о LogManager. Это тесно соединяет Logger с LogManager и представляет проблему, с которой вы сталкиваетесь; вы должны использовать LogManager для получения объекта ILogger и не можете заглушить или издеваться над LogManager.
  • Вы добавили код в производственный класс с единственной целью модульного тестирования. Это будет путать разработчиков по дороге, которые видят тестовый крюк и думают, что могут использовать его в производственном коде.

Я бы порекомендовал одно или несколько из следующих:

  • Определите интерфейс ILogManager, который предоставляет GetLogger() и реализует его в LogManager. Затем создайте свойство в Logger типа ILogManager (или требуется передать его при создании экземпляра Logger) и дайте Logger LogManager из-за пределов области Logger (через структуру IoC или просто из любых экземпляров Logger). Это свободное соединение; теперь вы можете предоставить любой класс, который реализует ILogManager, включая MockLogManager, и Logger не знает разницы.
  • В целях вашего unit test выведите "прокси" из Logger и внедрите там любые методы тестирования. Я использую этот метод главным образом для unit test защищенных методов класса, но если вы не можете отделить LogManager от Logger, вы можете как минимум скрыть этот производный прокси и его крючки в своих тестовых сборках, где производственный код не должен быть могут ссылаться на них.

Ответ 5

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

Ответ 6

Да, дизайн не идеален:(GetLogger метод фактически устанавливает регистратор! Используйте метод setter - это выглядит более подходящим.

Кстати, что вы испытываете? Если вы используете стандартные логеры .NET - вы можете добавлять или удалять прослушиватели (как я помню), и если вам не нужны тесты, чтобы что-то где-то писать, настройте подходящие прослушиватели журналов.

И видели библиотеку Мока? http://code.google.com/p/moq/ Мне понравилось это для насмешек.

Ответ 7

У меня нет каких-либо странных конструкторов. Вместо этого у меня есть приватная переменная в представлении:

private readonly ILog _log = LogManager.GetLogger(typeof(MyViewClassName));

который затем может использоваться как таковой, в этом случае он вызывается внутри блока catch:

_log.Error("SomePage.aspx.cs Page_Load failed", ex);

Нет обертки для log4net. ILog является частью log4net. Мне любопытно, почему вы тоже не используете Mocks. Это имеет смысл, и тогда вам не нужно будет делать то, что вы делаете.

Ответ 8

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

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

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

Ответ 9

Я бы не сказал, что этот фрагмент кода является проблемой как таковой. До тех пор, пока вы укажете в своих комментариях xmldoc, что метод предназначены только для модульного тестирования, пользователь no (sane!) Не будет использовать его в производственном коде.

Хотя, если это возможно, старайтесь хранить такие вещи в библиотеке тестирования модулей. Как утверждает Джоэл Эфиртон, в любом случае это раздувание кода...

Ответ 10

Что здесь не так, что код, который получает регистратор, должен знать, какой он нужен. На самом деле у него нет причин заботиться.

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

Ответ 11

Нет, это совсем не плохо.

Хороший код должен отвечать всем требованиям и ничего не делать.

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

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

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