Лучшая практика для отладки утверждений во время модульного тестирования

Не мешает ли интенсивное использование модульных тестов использовать отладочные утверждения? Похоже, что запуск отладочного утверждения в тестируемом коде подразумевает, что модульный тест не должен существовать или отладочное утверждение не должно существовать. «Может быть только один» кажется разумным принципом. Это обычная практика? Или вы отключаете отладочные утверждения при модульном тестировании, чтобы они были доступны для интеграционного тестирования?

Изменить: я обновил «Assert» для отладки assert, чтобы отличать assert в тестируемом коде от строк модульного теста, которые проверяют состояние после запуска теста.

Также вот пример, который, как мне кажется, показывает дилемму: модульный тест передает недопустимые входные данные для защищенной функции, которая утверждает, что входные данные действительны. Должен ли модульный тест не существовать? Это не публичная функция. Возможно, проверка входных данных убьет перфоманс? Или утверждения не должно существовать? Функция защищена, а не является частной, поэтому она должна проверять свои входы на безопасность.


person Steve Steiner    schedule 03.01.2009    source источник
comment
Уточните, к какому языку относится этот вопрос. Утверждения, скажем, в NUnit или JUnit - это утверждения, необходимые для проверки модульных тестов, поэтому с этой точки зрения это сбивает с толку.   -  person Jon Limjap    schedule 04.01.2009
comment
Под утверждением я подразумеваю утверждение в тестируемом коде, а не утверждение в конце модульного теста. Я считаю, что это тот момент, который вы хотите прояснить, а не «языком». Если все еще неясно, дайте мне знать.   -  person Steve Steiner    schedule 04.01.2009
comment
Статья do- the-right-things.blogspot.com.au/2014/05/ показывает, что вы можете установить в конфигурации <assert assertuienabled="false" />   -  person Michael Freidgeim    schedule 12.02.2017


Ответы (12)


Это совершенно правильный вопрос.

Во-первых, многие люди предполагают, что вы неправильно используете утверждения. Я думаю, что многие специалисты по отладке не согласятся. Хотя проверка инвариантов с помощью утверждений является хорошей практикой, утверждения не должны ограничиваться инвариантами состояния. Фактически, многие опытные отладчики скажут вам утверждать любые условия, которые могут вызвать исключение, в дополнение к проверке инвариантов.

Например, рассмотрим следующий код:

if (param1 == null)
    throw new ArgumentNullException("param1");

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

Теперь рассмотрим следующее:

if (param1 == null)
{
    Debug.Fail("param1 == null");
    throw new ArgumentNullException("param1");
}

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

Как теперь обрабатывать ваши модульные тесты?

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

Способ решения этой проблемы будет зависеть от того, какие языки и т. Д. Вы используете. Однако у меня есть несколько предложений, если вы используете .NET (на самом деле я этого не пробовал, но в будущем я обновлю сообщение):

  1. Проверьте Trace.Listeners. Найдите любой экземпляр DefaultTraceListener и установите для AssertUiEnabled значение false. Это останавливает появление модального диалога. Вы также можете очистить коллекцию слушателей, но вы не получите никакой трассировки.
  2. Напишите свой собственный TraceListener, который записывает утверждения. Как записывать утверждения, зависит от вас. Запись сообщения об ошибке может быть недостаточно хорошей, поэтому вы можете пройтись по стеку, чтобы найти метод, из которого пришло утверждение, и записать его тоже.
  3. После завершения теста убедитесь, что произошли только те ошибки утверждения, которые вы ожидали. Если возникли другие проблемы, не пройдите тест.

В качестве примера TraceListener, который содержит код для подобного обхода стека, я бы поискал SuperAssertListener SUPERASSERT.NET и проверил его код. (Также стоит интегрировать SUPERASSERT.NET, если вы действительно серьезно относитесь к отладке с использованием утверждений).

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

ОБНОВИТЬ:

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

ПРИМЕЧАНИЕ. Это во многом обязано SUPERASSERT.NET Джона Роббинса.

/// <summary>
/// TraceListener used for trapping assertion failures during unit tests.
/// </summary>
public class DebugAssertUnitTestTraceListener : DefaultTraceListener
{
    /// <summary>
    /// Defines an assertion by the method it failed in and the messages it
    /// provided.
    /// </summary>
    public class Assertion
    {
        /// <summary>
        /// Gets the message provided by the assertion.
        /// </summary>
        public String Message { get; private set; }

        /// <summary>
        /// Gets the detailed message provided by the assertion.
        /// </summary>
        public String DetailedMessage { get; private set; }

        /// <summary>
        /// Gets the name of the method the assertion failed in.
        /// </summary>
        public String MethodName { get; private set; }

        /// <summary>
        /// Creates a new Assertion definition.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="detailedMessage"></param>
        /// <param name="methodName"></param>
        public Assertion(String message, String detailedMessage, String methodName)
        {
            if (methodName == null)
            {
                throw new ArgumentNullException("methodName");
            }

            Message = message;
            DetailedMessage = detailedMessage;
            MethodName = methodName;
        }

        /// <summary>
        /// Gets a string representation of this instance.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return String.Format("Message: {0}{1}Detail: {2}{1}Method: {3}{1}",
                Message ?? "<No Message>",
                Environment.NewLine,
                DetailedMessage ?? "<No Detail>",
                MethodName);
        }

        /// <summary>
        /// Tests this object and another object for equality.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            var other = obj as Assertion;
            
            if (other == null)
            {
                return false;
            }

            return
                this.Message == other.Message &&
                this.DetailedMessage == other.DetailedMessage &&
                this.MethodName == other.MethodName;
        }

        /// <summary>
        /// Gets a hash code for this instance.
        /// Calculated as recommended at http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return
                MethodName.GetHashCode() ^
                (DetailedMessage == null ? 0 : DetailedMessage.GetHashCode()) ^
                (Message == null ? 0 : Message.GetHashCode());
        }
    }

    /// <summary>
    /// Records the assertions that failed.
    /// </summary>
    private readonly List<Assertion> assertionFailures;

    /// <summary>
    /// Gets the assertions that failed since the last call to Clear().
    /// </summary>
    public ReadOnlyCollection<Assertion> AssertionFailures { get { return new ReadOnlyCollection<Assertion>(assertionFailures); } }

    /// <summary>
    /// Gets the assertions that are allowed to fail.
    /// </summary>
    public List<Assertion> AllowedFailures { get; private set; }

    /// <summary>
    /// Creates a new instance of this trace listener with the default name
    /// DebugAssertUnitTestTraceListener.
    /// </summary>
    public DebugAssertUnitTestTraceListener() : this("DebugAssertUnitTestListener") { }

    /// <summary>
    /// Creates a new instance of this trace listener with the specified name.
    /// </summary>
    /// <param name="name"></param>
    public DebugAssertUnitTestTraceListener(String name) : base()
    {
        AssertUiEnabled = false;
        Name = name;
        AllowedFailures = new List<Assertion>();
        assertionFailures = new List<Assertion>();
    }

    /// <summary>
    /// Records assertion failures.
    /// </summary>
    /// <param name="message"></param>
    /// <param name="detailMessage"></param>
    public override void Fail(string message, string detailMessage)
    {
        var failure = new Assertion(message, detailMessage, GetAssertionMethodName());

        if (!AllowedFailures.Contains(failure))
        {
            assertionFailures.Add(failure);
        }
    }

    /// <summary>
    /// Records assertion failures.
    /// </summary>
    /// <param name="message"></param>
    public override void Fail(string message)
    {
        Fail(message, null);
    }

    /// <summary>
    /// Gets rid of any assertions that have been recorded.
    /// </summary>
    public void ClearAssertions()
    {
        assertionFailures.Clear();
    }

    /// <summary>
    /// Gets the full name of the method that causes the assertion failure.
    /// 
    /// Credit goes to John Robbins of Wintellect for the code in this method,
    /// which was taken from his excellent SuperAssertTraceListener.
    /// </summary>
    /// <returns></returns>
    private String GetAssertionMethodName()
    {
        
        StackTrace stk = new StackTrace();
        int i = 0;
        for (; i < stk.FrameCount; i++)
        {
            StackFrame frame = stk.GetFrame(i);
            MethodBase method = frame.GetMethod();
            if (null != method)
            {
                if(method.ReflectedType.ToString().Equals("System.Diagnostics.Debug"))
                {
                    if (method.Name.Equals("Assert") || method.Name.Equals("Fail"))
                    {
                        i++;
                        break;
                    }
                }
            }
        }

        // Now walk the stack but only get the real parts.
        stk = new StackTrace(i, true);

        // Get the fully qualified name of the method that made the assertion.
        StackFrame hitFrame = stk.GetFrame(0);
        StringBuilder sbKey = new StringBuilder();
        sbKey.AppendFormat("{0}.{1}",
                             hitFrame.GetMethod().ReflectedType.FullName,
                             hitFrame.GetMethod().Name);
        return sbKey.ToString();
    }
}

Вы можете добавить утверждения в коллекцию AllowedFailures в начале каждого теста для ожидаемых утверждений.

В конце каждого теста (надеюсь, ваша среда модульного тестирования поддерживает метод разборки теста) выполните:

if (DebugAssertListener.AssertionFailures.Count > 0)
{
    // TODO: Create a message for the failure.
    DebugAssertListener.ClearAssertions();
    DebugAssertListener.AllowedFailures.Clear();
    // TODO: Fail the test using the message created above.
}
person Alex Humphrey    schedule 30.06.2010
comment
Вы можете установить для AssertUiEnabled значение false в конфигурации, как описано в ответе Avi - person Michael Freidgeim; 11.02.2017

ИМХО debug. Утверждает рок. Эта отличная статья показывает, как остановить их от прерывания вашего модульного тестирования, добавив app.config в проект модульного тестирования и отключив диалоговое окно:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.diagnostics>
    <assert assertuienabled="false"/>
</system.diagnostics>
person Avi    schedule 24.08.2014
comment
Отличный подход, простой и легкий. - person yoyo; 08.03.2016
comment
Согласен - это очень чисто. Мне просто нужно было убедиться, что у моего решения есть отдельный проект для модульных тестов, чтобы убедиться, что файл App.config применим только к модульному тестированию. - person Matthew Kraus; 24.05.2016
comment
Кажется, это предотвращает пометку моих тестов как пройденных или неудачных при срабатывании утверждений (очевидно, я бы предпочел, чтобы они помечались как неудачные). Это то, что вы испытали? Есть идеи, как заставить тесты терпеть неудачу? - person Kyle Strand; 12.05.2017
comment
... на самом деле, похоже, это вообще не действует (диалог все еще появляется). - person Kyle Strand; 12.05.2017

Как уже упоминалось, утверждения отладки предназначены для вещей, которые всегда должны быть истинными. (Замечательный термин для этого - инварианты).

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

  • Если тестируемая функция предполагается работает с поддельными данными, то очевидно, что этого assert там быть не должно.
  • Если функция не оснащена для работы с данными такого типа (как указано в утверждении), то зачем вам ее модульное тестирование?

Второй момент - это тот, в который, похоже, попадают довольно многие разработчики. Модульное тестирование, черт возьми, из всех вещей, для которых ваш код создан, и утверждать или генерировать исключения для всего остального - в конце концов, если ваш код НЕ создан для работы с этими ситуациями, и вы заставляете их происходить, что делать вы ожидаете, что это произойдет?
Вы знаете те части документации C / C ++, в которых говорится о «неопределенном поведении»? Это оно. Залог и залог тяжело.


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

person Orion Edwards    schedule 07.04.2010
comment
Если ваш модульный тест передает поддельные данные, это приводит к срабатыванию утверждения, тогда вы должны задать себе вопрос - почему это происходит? Это происходит потому, что вы можете тестировать, что Assert действительно срабатывает при неверных входных данных. Однажды я неправильно закодировал логику в своем заявлении Assert, поэтому он не запускался. К счастью, (проходящий) модульный тест (который, как я ожидал, провалится) подсказал мне. Всегда полезно проверить, действительно ли вы правильно закодировали инварианты своих объектов! - person fourpastmidnight; 11.09.2017
comment
Весь смысл Debug.Assert состоит в том, чтобы во время разработки обнаружить, что вы сделали это неправильно. Утверждения должны срабатывать во время модульных тестов по двум причинам: 1) Если в вашем тесте передаются недопустимые данные, значит, тест не работает. Утверждение должно сработать. 2) если вызываемая вами функция передает недопустимые данные другой внутренней функции, она должна запускать assert. Вот почему мы тестируем эти интерфейсы. В противном случае, в чем смысл Debug.Assert, чтобы вы чувствовали себя хорошо? :) - person tekHedd; 09.01.2018

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

Уловить разницу? Утверждения программы помогают сделать ее правильной, модульные тесты помогают развить у кого-то уверенность в правильности кода.

person Charlie Martin    schedule 04.01.2009
comment
они оба могут документировать требования. Это убедило меня в том, что использование Debug.Assert в C # (и аналогичная конструкция на другом языке программирования) может использоваться в качестве исполняемой документации, особенно во время рефакторинга кода. - person OnesimusUnbound; 24.02.2012

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

В наших библиотеках низкоуровневые функции отладки, такие как TTY / ASSERTS, имеют вызываемые обработчики. Обработчик по умолчанию будет printf / break, но клиентский код может устанавливать собственные обработчики для другого поведения.

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

Вы также можете включить тестирование assert в свой модульный тест - например,

CHECK_ASSERT (someList.getAt (someList.size () + 1); // проверка пройдена, если возникает утверждение

person Andrew Grant    schedule 03.01.2009

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

public static class TraceListenerCollectionEx
{
    /// <summary>
    /// This is a helper class that allows us to suspend asserts / all trace listeners
    /// </summary>
    public class SuspendTrackerDisposable : IDisposable
    {
        private readonly TraceListenerCollection _traceListenerCollection;
        private readonly TraceListener[] _suspendedListeners;

        public SuspendTrackerDisposable(TraceListenerCollection traceListenerCollection)
        {
            _traceListenerCollection = traceListenerCollection;

            var numListeners = traceListenerCollection.Count;
            _suspendedListeners = new TraceListener[numListeners];
            for( int index = 0; index < numListeners; index += 1 )
                _suspendedListeners[index] = traceListenerCollection[index];

            traceListenerCollection.Clear();
        }

        public void Dispose()
        {
            _traceListenerCollection.AddRange(_suspendedListeners);
        }
    }

    public static SuspendTrackerDisposable AssertSuspend(this TraceListenerCollection traceListenerCollection) => new SuspendTrackerDisposable(traceListenerCollection);
}

Вот пример использования в тесте:

    [TestMethod]
    public void EnumDefaultTest()
    {
        using(Trace.Listeners.AssertSuspend()) {
            Enum<CarClass>.DefaultValue.ShouldBe(CarClass.Unknown);  
        }
    }

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

person Tod Cunningham    schedule 07.08.2020

Вы имеете в виду утверждения C ++ / Java для утверждений «программирование по контракту» или утверждения CppUnit / JUnit? Последний вопрос заставляет меня думать, что это первое.

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

Я бы сказал, что они должны быть оставлены в вашем коде, когда вы его тестируете. Вы пишете тесты, чтобы убедиться, что предварительные условия выполняются должным образом. Тест должен быть «черным ящиком»; при тестировании вы должны действовать как клиент класса. Если вы случайно отключите их в процессе производства, это не сделает тесты недействительными.

person duffymo    schedule 03.01.2009
comment
Под утверждением в исходном вопросе я имел в виду тот вид, который вы называете программированием по контракту. Я попытался прояснить проблему в вопросах. - person Steve Steiner; 04.01.2009

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

Поскольку вы тестируете закрытые функции, каков риск вызова функции с недопустимым аргументом? Разве ваши модульные тесты не покрывают этот риск? Если вы пишете свой код в соответствии с техникой TDD (разработка через тестирование), они должны.

Если вам действительно нужны / нужны эти утверждения типа Dbc в вашем коде, вы можете удалить модульные тесты, которые передают недопустимые аргументы в методы, имеющие эти утверждения.

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

person philant    schedule 04.01.2009

Вы должны сохранить свои отладочные утверждения даже при наличии модульных тестов.

Проблема здесь не в различении ошибок и проблем.

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

Если функции передаются правильные данные, но она не может работать должным образом из-за нехватки памяти во время выполнения, код должен выдать отладочное утверждение из-за этой проблемы. Это пример фундаментальных предположений, которые, если они не выполняются, «все ставки сняты», поэтому вы должны прекратить игру.

В вашем случае напишите модульный тест, который предоставляет ошибочные значения в качестве аргументов. Он должен ожидать возвращаемое значение ошибки (или подобное). Получение утверждения? - реорганизуйте код, чтобы вместо этого выдать ошибку.

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

Обратите внимание, что причина «отладочных» -asserts - это компромисс между прилежанием / безопасностью и быстротой / малостью.

person Faisal Memon    schedule 25.08.2013

Как уже упоминалось, утверждения Debug.Assert всегда должны быть истинными, даже если аргументы неверны, утверждение должно быть истинным, чтобы приложение не перешло в недопустимое состояние и т. Д.

Debug.Assert(_counter == somethingElse, "Erk! Out of wack!");

Вы не должны иметь возможность тестировать это (и, вероятно, не хотите, потому что на самом деле вы ничего не можете сделать!)

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

if (param1 == null)
  throw new ArgumentNullException("param1", "message to user")

Подобное «утверждение» в вашем коде все еще можно проверить.

PK :-)

person Paul Kohler    schedule 07.04.2010
comment
Вы не должны иметь возможность тестировать это - уважительно не согласны, вы действительно хотите иметь возможность проверить, обрабатывает ли ваш API неверный ввод, как ожидалось, даже если ожидаемым ответом является Debug.Assert. - person yoyo; 08.03.2016

Прошло некоторое время с тех пор, как этот вопрос был задан, но я думаю, что у меня есть другой способ проверки вызовов Debug.Assert () из модульного теста с использованием кода C #. Обратите внимание на блок #if DEBUG ... #endif, который необходим для пропуска теста, когда он не запущен в конфигурации отладки (в этом случае Debug.Assert () все равно не будет запущен).

[TestClass]
[ExcludeFromCodeCoverage]
public class Test
{
    #region Variables              |

    private UnitTestTraceListener _traceListener;
    private TraceListenerCollection _originalTraceListeners;

    #endregion

    #region TestInitialize         |

    [TestInitialize]
    public void TestInitialize() {
        // Save and clear original trace listeners, add custom unit test trace listener.
        _traceListener = new UnitTestTraceListener();
        _originalTraceListeners = Trace.Listeners;
        Trace.Listeners.Clear();
        Trace.Listeners.Add(_traceListener);

        // ... Further test setup
    }

    #endregion
    #region TestCleanup            |

    [TestCleanup]
    public void TestCleanup() {
        Trace.Listeners.Clear();
        Trace.Listeners.AddRange(_originalTraceListeners);
    }

    #endregion

    [TestMethod]
    public void TheTestItself() {
        // Arrange
        // ...

        // Act
        // ...
        Debug.Assert(false, "Assert failed");



    // Assert

#if DEBUG        
    // NOTE This syntax comes with using the FluentAssertions NuGet package.
    _traceListener.GetWriteLines().Should().HaveCount(1).And.Contain("Fail: Assert failed");
#endif

    }
}

Класс UnitTestTraceListener выглядит следующим образом:

[ExcludeFromCodeCoverage]
public class UnitTestTraceListener : TraceListener
{
    private readonly List<string> _writes = new List<string>();
    private readonly List<string> _writeLines = new List<string>();

    // Override methods
    public override void Write(string message)
    {
        _writes.Add(message);
    }

    public override void WriteLine(string message)
    {
        _writeLines.Add(message);
    }

    // Public methods
    public IEnumerable<string> GetWrites()
    {
        return _writes.AsReadOnly();
    }

    public IEnumerable<string> GetWriteLines()
    {
        return _writeLines.AsReadOnly();
    }

    public void Clear()
    {
        _writes.Clear();
        _writeLines.Clear();
    }
}
person DotBert    schedule 20.07.2017

Не мешает ли интенсивное использование модульных тестов использовать отладочные утверждения?

Нет. Напротив. Модульное тестирование делает Debug asserts гораздо более ценными за счет двойной проверки внутреннего состояния при выполнении написанных вами тестов белого ящика. Включение Debug.Assert во время модульного тестирования важно, потому что вы редко отправляете код с поддержкой DEBUG (если только производительность не важна вообще). Код DEBUG запускается только два раза, когда вы: 1) выполняете тот крошечный этап интеграционного тестирования, который вы действительно проводите, не говоря уже о добрых намерениях, и 2) выполняете модульные тесты.

С помощью тестов Debug.Assert легко инструментировать код для проверки инвариантов по мере его написания. Эти проверки служат в качестве проверок работоспособности при запуске модульных тестов.

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

Это увеличивает ценность модульных тестов.

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

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

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

«Может быть только один» кажется разумным принципом. Это обычная практика? Или вы отключаете отладочные утверждения при модульном тестировании, чтобы они были доступны для интеграционного тестирования?

Избыточность не вредит ничего, кроме времени выполнения ваших модульных тестов. Если у вас действительно 100% покрытие, время выполнения может быть проблемой. В остальном нет, я категорически не согласен. Нет ничего плохого в автоматической проверке вашего предположения в середине теста. Это практически определение «тестирования».

Также вот пример, который, как мне кажется, показывает дилемму: модульный тест передает недопустимые входные данные для защищенной функции, которая утверждает, что входные данные действительны. Должен ли модульный тест не существовать? Это не публичная функция. Возможно, проверка входных данных убьет перфоманс? Или утверждения не должно существовать? Функция защищена, а не является частной, поэтому она должна проверять свои входы на безопасность.

Обычно цель фреймворка модульного тестирования не состоит в том, чтобы проверить поведение вашего кода, когда инвариантные предположения были нарушены. Другими словами, если в написанной вами документации сказано: «Если вы передадите null в качестве параметра, результаты будут неопределенными», вам не нужно проверять, действительно ли результаты непредсказуемы. Если результаты сбоя четко определены, они не являются неопределенными, и 1) это не должно быть Debug.Assert, 2) вы должны точно определить, каковы результаты, и 3) проверить этот результат. Если вам нужно провести модульное тестирование качества ваших внутренних отладочных утверждений, то 1) подход Эндрю Гранта по превращению фреймворков Assertion в тестируемый актив, вероятно, следует проверить в качестве ответа и 2) вау, у вас потрясающее покрытие тестами! И я думаю, что это во многом личное решение, основанное на требованиях проекта. Но я по-прежнему считаю отладочные утверждения важными и ценными.

Другими словами: Debug.Assert () значительно увеличивает ценность модульных тестов, а избыточность - это особенность.

person tekHedd    schedule 09.01.2018