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

Necromancing. Для тех, кому еще нужно поддерживать .NET 2.0 или тех, которые хотят это сделать без LINQ:

public static object GetAttribute(System.Reflection.MemberInfo mi, System.Type t)
{
    object[] objs = mi.GetCustomAttributes(t, true);

    if (objs == null || objs.Length < 1)
        return null;

    return objs[0];
}



public static T GetAttribute<T>(System.Reflection.MemberInfo mi)
{
    return (T)GetAttribute(mi, typeof(T));
}


public delegate TResult GetValue_t<in T, out TResult>(T arg1);

public static TValue GetAttributValue<TAttribute, TValue>(System.Reflection.MemberInfo mi, GetValue_t<TAttribute, TValue> value) where TAttribute : System.Attribute
{
    TAttribute[] objAtts = (TAttribute[])mi.GetCustomAttributes(typeof(TAttribute), true);
    TAttribute att = (objAtts == null || objAtts.Length < 1) ? default(TAttribute) : objAtts[0];
    // TAttribute att = (TAttribute)GetAttribute(mi, typeof(TAttribute));

    if (att != null)
    {
        return value(att);
    }
    return default(TValue);
}

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

System.Reflection.FieldInfo fi = t.GetField("PrintBackground");
wkHtmlOptionNameAttribute att = GetAttribute<wkHtmlOptionNameAttribute>(fi);
string name = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, delegate(wkHtmlOptionNameAttribute a){ return a.Name;});

или просто

string aname = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, a => a.Name );
43
задан raven 4 January 2009 в 15:25
поделиться

7 ответов

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

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

7
ответ дан Charlie Martin 4 January 2009 в 15:25
поделиться

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

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

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

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

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

2
ответ дан Andrew Grant 4 January 2009 в 15:25
поделиться

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

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

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

1
ответ дан duffymo 4 January 2009 в 15:25
поделиться

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

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

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

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

1
ответ дан philant 4 January 2009 в 15:25
поделиться
  • 1
    Я часто задавался вопросом, каково различие было. Спасибо Aaronaught – Gustavo Mori 2 June 2011 в 18:02

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

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

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

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

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

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

PK: -)

0
ответ дан 26 November 2019 в 20:33
поделиться

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

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

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

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


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

7
ответ дан 26 November 2019 в 20:33
поделиться

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

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

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

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.
}
37
ответ дан 26 November 2019 в 20:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: