Это совершенно правильный вопрос.
Прежде всего, многие люди предполагают, что вы неправильно используете утверждения. Я думаю, что многие эксперты по отладке не согласятся. Хотя рекомендуется проверять инварианты с помощью утверждений, утверждения не следует ограничивать инвариантами состояния. Фактически, многие опытные отладчики советуют вам утверждать любые условия, которые могут вызвать исключение, в дополнение к проверке инвариантов.
Например, рассмотрим следующий код:
if (param1 == null)
throw new ArgumentNullException("param1");
Отлично. Но когда выдается исключение, стек разматывается до тех пор, пока что-то не обработает исключение (возможно, какой-то обработчик по умолчанию верхнего уровня). Если в этот момент выполнение приостанавливается (у вас может быть модальное диалоговое окно исключений в приложении Windows), у вас есть возможность подключить отладчик, но вы, вероятно, потеряли много информации, которая могла бы помочь вам решить проблему, поскольку большая часть стека была размотана.
Теперь рассмотрим следующее:
if (param1 == null)
{
Debug.Fail("param1 == null");
throw new ArgumentNullException("param1");
}
Теперь, если проблема возникает, появляется диалоговое окно модального утверждения. Исполнение приостанавливается мгновенно. Вы можете присоединить выбранный вами отладчик и точно исследовать, что находится в стеке, и все состояние системы в точном месте сбоя. В сборке релиза вы по-прежнему получаете исключение.
Теперь, как мы справляемся с вашими юнит-тестами?
Рассмотрим модульный тест, который проверяет приведенный выше код, включающий утверждение. Вы хотите проверить, что исключение выдается, когда param1 равен нулю. Вы ожидаете, что это конкретное утверждение потерпит неудачу, но любые другие ошибки подтверждения указывают на то, что что-то не так. Вы хотите разрешить определенные ошибки подтверждений для определенных тестов.
То, как вы это решите, будет зависеть от того, какие языки и т. Д. Вы используете. Тем не менее, у меня есть некоторые предложения, если вы используете .NET (я на самом деле не пробовал это, но я буду в будущем и обновлю пост):
- Проверьте Trace.Listeners. Найдите любой экземпляр DefaultTraceListener и установите для AssertUiEnabled значение false. Это останавливает модальное диалоговое окно от появления. Вы также можете очистить коллекцию слушателей, но вы не получите никакой трассировки.
- Напишите свой собственный TraceListener, который записывает утверждения. Как вы записываете утверждения, зависит от вас. Запись сообщения об ошибке может быть недостаточно хорошей, поэтому вы можете пройтись по стеку, чтобы найти метод, из которого получено утверждение, и записать его тоже.
- Как только тест закончится, убедитесь, что единственными ошибками утверждений были те, которые вы ожидали. Если возникли какие-либо другие, не пройдите тест.
В качестве примера TraceListener, который содержит код для такого обхода стека, я бы поискал SuperAssertListener SUPERASSERT.NET и проверил его код. (Также стоит интегрировать SUPERASSERT.NET, если вы действительно серьезно относитесь к отладке с использованием утверждений).
Большинство каркасных модульных тестов поддерживают методы тестирования / настройки. Возможно, вы захотите добавить код, чтобы сбросить прослушиватель трассировки и утверждать, что в этих областях нет неожиданных ошибок подтверждений, чтобы минимизировать дублирование и предотвратить ошибки.
UPDATE:
Вот пример 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.
}