当代码验证它收到的类型时,如何使用模拟

发布于 2024-12-07 00:13:41 字数 2914 浏览 0 评论 0原文

我想测试以下代码:

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;
}

基本上,测试的目标是验证“Facts”IEnumerable 中存在的所有对象是否都会调用其 Validate 方法。由于我对测试这些对象中的代码不感兴趣,已经有很多测试可以做到这一点,我想注入一个虚假事实列表。我正在使用最小起订量来制造假货。

所以我的单元测试看起来像这样:

[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();
    }
}

绕过此验证的最佳方法是什么?

谢谢。

I want to test the following code:

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;
}

Basically the test's objective is to verify that all objects that exist within the "Facts" IEnumerable will call their Validate method. Since I'm not interested to test the code within those objects, there are already lots of tests that do that, I want to inject a list of fake facts. I'm using MOQ to create the fakes.

So my unit test looks like this:

[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();
} 

Now I'm getting an exception because the code is actually validating the kind of Facts that can be injected to the DataWarehouseFacts class, like so:

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();
    }
}

What would be the best way to get around this validation?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

末蓝 2024-12-14 00:13:41

我想到的两个明显的方法是:

  1. Fact 添加到允许的类型列表中。
  2. 起订量您允许的事实类型之一,而不是基 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 即可。

The two obvious methods that leap to mind are:

  1. Add Fact to your list of allowed types.
  2. Moq one of your allowed fact types rather than the base Fact class itself. (I presume that your Validate() method is overrideable.)

Another slightly more complicated option would be to inject your list of allowed types at test time, assuming you have control over the DataWarehouseFacts class. That might look something like this:

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;
    }
    ...
}

Then just delete that var allowedTypes = new [] bit and use this.allowedFacts instead.

你如我软肋 2024-12-14 00:13:41

我会利用 Type.IsAssignableFrom

例如,而不是说

allowedTypes.Contains(v.GetType())

我会说

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

这样你就可以传递正确的子类以及精确匹配的类型。 也许,也许,这就是您所追求的类型列表本身?

I would leverage Type.IsAssignableFrom

E.g. instead of saying

allowedTypes.Contains(v.GetType())

I'd say

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

That way you can pass proper subclasses just as well as the exact matching types. Perhaps, maybe, that was what you were after with the typelist itself?

没企图 2024-12-14 00:13:41

首先,我要感谢拉登奇(我确实给了他的回答+1)和塞赫的回答。尽管这并不完全是我想要的,但它们是值得记住的有趣想法。

我不能只是将 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());
}

First of all I want to thank both ladenedge (I did gave a +1 to his answer) and sehe for their answers. Even though it was not exactly what I was looking for they are interesting ideas to keep in mind.

I couldn't just add the Fact class to the list of allowed types since that would have opened the door for lots of classes that should not be allowed; there are about 30 classes inheriting from it.

So what I ended up doing was to extract the code from the set part of the Facts property in their own methods and made one of them protected virtual, like so:

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();
        }

That way I was able to create a DataWarehouseFactsForTest and override the ValidateReceived method so it wouldn't do anything:

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

That way I was able to to use Moq to create the Facts and verify the code within the private GetInvalidFacts method. For example:

[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());
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文