Как я могу использовать макет, когда код проверяет типы, которые он получает - PullRequest
2 голосов
/ 28 сентября 2011

Я хочу протестировать следующий код:

public IEnumerable<KeyValuePair<Fact, Exception>> ValidateAll()
{
    //...do something
    var invalidFacts = GetInvalidFacts();
    //...do something

    return duplicateFacts.Concat(invalidFacts);
}

private IEnumerable<KeyValuePair<Fact, Exception>> GetInvalidFacts()
{
    var invalidFacts = Facts.Select(fact =>
    {
        try
        {
            fact.Validate();
            return new KeyValuePair<Fact, Exception>(fact, null);
        }
        catch (FormatException e)
        {
            return new KeyValuePair<Fact, Exception>(fact, e);
        }
        catch (Exception e)
        {
            return new KeyValuePair<Fact, Exception>(fact, e);
        }
    }).Where(kv => kv.Value != null).ToList();

    return invalidFacts;
}

По сути, целью теста является проверка того, что все объекты, которые существуют в IEnumerable «Facts», будут вызывать их метод Validate.Поскольку мне не интересно тестировать код в этих объектах, уже есть много тестов, которые делают это, я хочу добавить список поддельных фактов.Я использую MOQ для создания подделок.

Итак, мой модульный тест выглядит следующим образом:

[TestMethod]
public void ValidateAll_ValidateMethodIsInvokedOnAllFacts_WhenCalled()
{
    var anyFactOne = new Mock<Fact>(); //Fact is an abstract class.

    anyFactOne.Setup(f => f.Validate());

    var dataWarehouseFacts = new DataWarehouseFacts { Facts = new Fact[] { anyFactOne.Object, FactGenerationHelper.GenerateRandomFact<SourceDetails>() } };

    dataWarehouseFacts.ValidateAll();
} 

Теперь я получаю исключение, потому что код фактически проверяет вид фактов.которые могут быть внедрены в класс DataWarehouseFacts, например, так:

public IEnumerable<Fact> Facts
{
    get
    {
        .....
    }
    set
    {
        var allowedTypes = new [] 
        { 
            typeof(ProductUnitFact), 
            typeof(FailureFact), 
            typeof(DefectFact), 
            typeof(ProcessRunFact), 
            typeof(CustomerFact),
            typeof(ProductUnitReturnFact),
            typeof(ShipmentFact),
            typeof(EventFact), 
            typeof(ComponentUnitFact),
            typeof(SourceDetails) 
        };

    if(!value.All(rootFact => allowedTypes.Contains(rootFact.GetType())))
       throw new Exception ("DataWarehouseFacts can only be set with root facts");

    ProductUnitFacts = value.OfType<ProductUnitFact>().ToList();
    FailureFacts = value.OfType<FailureFact>().ToList();
    DefectFacts = value.OfType<DefectFact>().ToList();
    ProcessRunFacts = value.OfType<ProcessRunFact>().ToList();
    CustomerFacts = value.OfType<CustomerFact>().ToList();
    ProductUnitReturnFacts = value.OfType<ProductUnitReturnFact>().ToList();
    ShipmentFacts = value.OfType<ShipmentFact>().ToList();
    EventFacts = value.OfType<EventFact>().ToList();
    ComponentUnitFacts = value.OfType<ComponentUnitFact>().ToList();
    SourceDetails = value.OfType<SourceDetails>().Single();
    }
}

Каков наилучший способ обойти эту проверку?

Спасибо.

Ответы [ 3 ]

2 голосов
/ 28 сентября 2011

Два очевидных метода, которые приходят на ум:

  1. Добавьте Fact в список разрешенных типов.
  2. Moq один из допустимых типов фактов, а не сам базовый класс Fact. (Я предполагаю, что ваш Validate() метод является перезаписываемым.)

Еще один немного более сложный вариант - добавить список разрешенных типов во время тестирования, предполагая, что у вас есть контроль над классом DataWarehouseFacts. Это может выглядеть примерно так:

class DWF
{
    static IEnumerable<Fact> defaultAllowedFacts = new Fact[] { ... }
    IEnumerable<Fact> allowedFacts;

    public DWF() : this(defaultAllowedFacts) { ... }
    internal DWF(IEnumerable<Fact> allowed)
    {
        // for testing only, perhaps
        this.allowedFacts = allowed;
    }
    ...
}

Затем просто удалите этот бит var allowedTypes = new [] и используйте вместо него this.allowedFacts.

1 голос
/ 28 сентября 2011

Я бы использовал Type.IsAssignableFrom

например. вместо того, чтобы сказать

allowedTypes.Contains(v.GetType())

Я бы сказал

allowedTypes.Any(t => t.IsAssignableFrom(v.GetType()))

Таким образом, вы можете передавать правильные подклассы так же, как и точные типы соответствия. Возможно, может быть, это было то, что вы искали с самим набором символов?

0 голосов
/ 29 сентября 2011

Прежде всего я хочу поблагодарить и ladenedge (я дал +1 к его ответу) и sehe за их ответы.Хотя это было не совсем то, что я искал, это интересные идеи, которые нужно иметь в виду.

Я не мог просто добавить класс Fact в список разрешенных типов, поскольку это открыло бы двери для лотов.классов, которые не должны быть разрешены;от него унаследовано около 30 классов.

Итак, в итоге я извлек код из заданной части свойства Facts в свои собственные методы и сделал один из них защищенным виртуальным, например, так:

public IEnumerable<Fact> Facts
        {
            get
            {
                ...
            }
            set
            {
                ValidateReceived(value);
                ExtractFactTypesFrom(value.ToList());
            }
        }

        protected virtual void ValidateReceived(IEnumerable<Fact> factTypes)
        {
            if (factTypes == null) throw new ArgumentNullException("factTypes");

            var allowedTypes = GetAllowedFactTypes();
            if (!factTypes.All(rootFact => allowedTypes.Contains(rootFact.GetType()))) throw new Exception("DataWarehouseFacts can only be set with root facts");
        }

        private IEnumerable<Type> GetAllowedFactTypes()
        {
            var allowedTypes = new[]
            {
                typeof (ProductUnitFact), 
                typeof (SequenceRunFact), 
                typeof (FailureFact), 
                typeof (DefectFact),
                typeof (ProcessRunFact), 
                typeof (CustomerFact), 
                typeof (ProductUnitReturnFact),
                typeof (ShipmentFact), 
                typeof (EventFact), 
                typeof (ComponentUnitFact), 
                typeof (SourceDetails)
            };

            return allowedTypes;
        }

        private void ExtractFactTypesFrom(List<Fact> value)
        {
            ProductUnitFacts = value.OfType<ProductUnitFact>().ToList();
            FailureFacts = value.OfType<FailureFact>().ToList();
            DefectFacts = value.OfType<DefectFact>().ToList();
            ProcessRunFacts = value.OfType<ProcessRunFact>().ToList();
            SequenceRunFacts = value.OfType<SequenceRunFact>().ToList();
            CustomerFacts = value.OfType<CustomerFact>().ToList();
            ProductUnitReturnFacts = value.OfType<ProductUnitReturnFact>().ToList();
            ShipmentFacts = value.OfType<ShipmentFact>().ToList();
            EventFacts = value.OfType<EventFact>().ToList();
            ComponentUnitFacts = value.OfType<ComponentUnitFact>().ToList();
            SourceDetails = value.OfType<SourceDetails>().Single();
        }

Таким образом я смог создать DataWarehouseFactsForTest и переопределить метод ValidateReceived, чтобы он ничего не делал:

public class DataWarehouseFactsForTests : DataWarehouseFacts
{
    protected override void ValidateReceived(IEnumerable<Fact> factTypes)
    {}
}

Таким образом, я смог использовать Moq для созданияФакты и проверьте код в частном методе GetInvalidFacts.Например:

[TestMethod]
public void ValidateAll_ReturnsADictionaryWithAFormatException_WhenOneOfTheFactsValidationThrowsAFormatException()
{
    var anyFactOne = new Mock<ProductUnitFact>();
    var anyFactTwo = new Mock<SequenceRunFact>();
    var anyFactThree = new Mock<SourceDetails>();

    anyFactOne.Setup(f => f.Validate()).Throws(new FormatException());

    var dataWarehouseFacts = new DataWarehouseFactsForTests { Facts = new Fact[] { anyFactOne.Object, anyFactTwo.Object, anyFactThree.Object } };

    var result = dataWarehouseFacts.ValidateAll().ToList();

    anyFactOne.Verify(f => f.Validate(), Times.Exactly(1));
    anyFactTwo.Verify(f => f.Validate(), Times.Exactly(1));
    anyFactThree.Verify(f => f.Validate(), Times.Exactly(1));

    Assert.AreEqual(1, result.Count());
    Assert.AreEqual(typeof(FormatException), result.First().Value.GetType());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...