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