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