У меня есть класс, содержащий метод, который возвращает Объект результата, который содержит свойство типа Func.
class Result {
public Func<Result> NextAction { get; set; }
}
Как я пишу утверждение модульного теста относительно содержания этого Func? Следующее, очевидно, не работает, потому что компилятор генерирует два различных метода для лямбды:
// Arrange
ListController controller = new ListController(domain);
// Act
Result actual = controller.DefaultAction();
// Assert
Func<Result> expected = () => new ProductsController(domain).ListAction();
Assert.That(actual.NextAction, Is.EqualTo(expected));
Я предполагаю, что мог сделать это при помощи деревьев выражений вместо этого, но... являюсь там способом постараться не делать так? Я использую NUnit 2.5.
Править: В Объекте результата нет никаких других полей идентификации. Это предназначается, чтобы быть способом вызвать следующий объект/метод на основе решения, принятого в текущем объекте/методе.
Что ж, похоже, что модульное тестирование содержимого Func
выходит за рамки обычного диапазона модульного тестирования. Функция Func
представляет скомпилированный код и поэтому не может быть дополнительно проверена без обращения к синтаксическому анализу MSIL. Поэтому в этой ситуации необходимо прибегнуть к делегатам и экземплярам типов (как предлагает Натан Баулч) или вместо этого использовать деревья выражений.
Мой эквивалент дерева выражений ниже:
class Result {
public Expression<Func<Result>> NextAction { get; set; }
}
со следующим модульным тестированием:
// Arrange
ListController controller = new ListController(domain);
// Act
Result actual = controller.DefaultAction();
// Assert
MethodCallExpression methodExpr = (MethodCallExpression)actual.NextAction.Body;
NewExpression newExpr = (NewExpression)methodExpr.Object;
Assert.That(newExpr.Type, Is.EqualTo(typeof(ProductsController)));
Assert.That(methodExpr.Method.Name, Is.EqualTo("ListAction"));
Обратите внимание, что этому тесту присуща некоторая хрупкость, поскольку он подразумевает структуру выражения, а также его поведение.
Почему бы не вызвать Func
и не сравнить возвращенные значения?
var actualValue = actual.NextAction();
var expectedValue = expected();
Assert.That(actualValue, Is.EqualTo(expectedValue));
РЕДАКТИРОВАТЬ: Я вижу, что класс Result не имеет личность. Я предполагаю, что у вас есть некоторые другие поля в классе Result, которые определяют идентичность Result и могут использоваться для определения равенства двух результатов.
Я не знаю простого способа заглянуть внутрь лямбда (кроме использования деревьев выражений, как вы сказали), но можно сравнить делегаты, если вместо этого им назначена группа методов .
var result1 = new Result {
NextAction = new ProductsController(domain).ListAction };
var result2 = new Result {
NextAction = new ProductsController(domain).ListAction };
//objects are different
Assert.That(result1, Is.Not.EqualTo(result2));
//delegates are different
Assert.That(result1.NextAction, Is.Not.EqualTo(result2.NextAction));
//methods are the same
Assert.That(result1.NextAction.Method, Is.EqualTo(result2.NextAction.Method));
Приведенный выше пример не работает, если вы используете лямбды, поскольку они скомпилированы для разных методов.
Если вы Func
всегда возвращаете один и тот же результат, вы можете проверить, какой объект возвращается функцией.
Если я правильно понимаю проблему, NextAction может иметь или не иметь другую реализацию лямбда-выражения, что и требует тестирования.
В приведенном ниже примере я сравниваю байты IL методов. Используя отражение, получите информацию о методе и байты IL из тела в массиве. Если байтовые массивы совпадают, лямбда одинаковы.
Есть много ситуаций, с которыми эта функция не справляется, но если это просто вопрос сравнения двух лямбда-выражений, которые должны быть совершенно одинаковыми, это сработает. Извините, это в MSTest :)
using System.Reflection;
....
[TestClass]
public class Testing
{
[TestMethod]
public void Results_lambdas_match( )
{
// Arrange
ListController testClass = new ListController( );
Func<Result> expected = ( ) => new ProductsController( ).ListAction( );
Result actual;
byte[ ] actualMethodBytes;
byte[ ] expectedMethodBytes;
// Act
actual = testClass.DefaultAction( );
// Assert
actualMethodBytes = actual.NextAction.
Method.GetMethodBody( ).GetILAsByteArray( );
expectedMethodBytes = expected.
Method.GetMethodBody( ).GetILAsByteArray( );
// Test that the arrays are the same, more rigorous check really should
// be done .. but this is an example :)
for ( int count=0; count < actualMethodBytes.Length; count++ )
{
if ( actualMethodBytes[ count ] != expectedMethodBytes[ count ] )
throw new AssertFailedException(
"Method implementations are not the same" );
}
}
[TestMethod]
public void Results_lambdas_do_not_match( )
{
// Arrange
ListController testClass = new ListController( );
Func<Result> expected = ( ) => new OtherController( ).ListAction( );
Result actual;
byte[ ] actualMethodBytes;
byte[ ] expectedMethodBytes;
int count=0;
// Act
actual = testClass.DefaultAction( );
// Assert
actualMethodBytes = actual.NextAction.
Method.GetMethodBody( ).GetILAsByteArray( );
expectedMethodBytes = expected.
Method.GetMethodBody( ).GetILAsByteArray( );
// Test that the arrays aren't the same, more checking really should
// be done .. but this is an example :)
for ( ; count < actualMethodBytes.Length; count++ )
{
if ( actualMethodBytes[ count ] != expectedMethodBytes[ count ] )
break;
}
if ( ( count + 1 == actualMethodBytes.Length )
&& ( actualMethodBytes.Length == expectedMethodBytes.Length ) )
throw new AssertFailedException(
"Method implementations are the same, they should NOT be." );
}
public class Result
{
public Func<Result> NextAction { get; set; }
}
public class ListController
{
public Result DefaultAction( )
{
Result result = new Result( );
result.NextAction = ( ) => new ProductsController( ).ListAction( );
return result;
}
}
public class ProductsController
{
public Result ListAction( ) { return null; }
}
public class OtherController
{
public Result ListAction( ) { return null; }
}
}