是否可以从堆栈跟踪访问调用方对象并设置其属性

时间:2011-07-04 17:23:02

标签: c# unit-testing moq

嗨,我有一个可能的设计缺陷,我需要用扩展方法解决它。 假设我有一个类,它有一个StringCollection属性。示例代码

public class MyProblematicClass
{
    public IDbAccess Db{get;set;}
    public StringCollection Errors{get;set;}

    public MyProblematicClass(IDbAcces db){ Db=db;}

    public int SetItem(Item i)
    {

        var id = Db.Save(i);
        this.Errors = Db.Erros;
        return id;
    }

}

我正在做的是,在我的单元测试类中,我模拟了IDbAccess。此类根据属性验证对象。如果发生任何错误,它不会命中db,它只会填充自己的Errors集合。对于单元测试,我使用另一个只运行验证例程的dbclass,这里有问题我无法获得错误。让我给你一个进一步理解的例子(我知道设计是有问题的,但是现在我想处理它而不改变任何东西)

public static class MyDbExtension
{
   public static Save(Item i)
   {
     Validation v = new Validation();
     var erros =    v.ValidateObject(i);
     //Here is problem i cannot pass it to MyProblematicClass
      if ( errors.Count > 0 )
         return -1;
     else 
        return 1;


    /* what I want to is :
     var stackTrace = new StackTrace(); get stack trace
     var object = stackTrace.GetFrame(1).GetMethod().GetObject() or sth like that. get object
     object.GetProperties()[0].SetValue(object,errors,null);  find property and set it.
   */
   }
}

在我的单元测试中:

public class UnitTest
{
   Mock<IDbAccess> _db ;
   MyProblematicClass _mpc;
   pubic Setup()
   {

       _db.Setup(x=>x.Save(It.IsAny<Item>).Returns(u =>MyDbExtension.Save(u));
      _mpc = new MyProblematicClass(_db.Object);

   }

 public void SetItem_EmptyObject_Contains3Erros()
 {
    Item i = new Item();
    _mpc.SetItem(i);
    //At this point i cannot set _mpc.Errors
 }

我想要实现的是在我的DbExtension类中,我可以访问调用者类并设置其Errors属性吗?我尝试过,但现在还不太可能。如果有人有任何体面的解决方案我会很感激,当然你可以评论设计问题。

修改

我很欣赏Alex的回答他只是说忽略Save方法只是模拟Erros属性,它会没问题。这有道理,但我想知道的是,是否有可能访问Stack Trace并操纵调用者方法对象的属性?

提前致谢。

4 个答案:

答案 0 :(得分:1)

您需要设置_db.Errors的返回值,如下所示:

public class UnitTest
{   
     Mock<IDbAccess> _db ;   
     MyProblematicClass _mpc;   
     StringCollection errors;

     pubic Setup()   
     {       
          _db.Setup(x=>x.Save(It.IsAny<Item>).Returns(u =>MyDbExtension.Save(u));
          _db.Setup(x=>x.Errors).Returns(errors);      
          _mpc = new MyProblematicClass(_db.Object);   
     } 

     public void SetItem_EmptyObject_ContainsError() 
     {    
          errors.Add("Expected Error!");

          Item i = new Item();    
          _mpc.SetItem(i);    

          Assert.AreEqual("Expected Error!", _mpc.Errors[0]);
     }
 }

我必须承认我并没有真正遵循你的设计,你为什么要使用静态方法进行保存?你可以很容易地拥有这条线:

_db.Setup(x=>x.Save(It.IsAny<Item>).Returns(-1);

然后独立测试IDbAccess.Save()。

在'extension'类中,save方法没有返回值,MyProblematicClass在分配错误之前不检查返回值。

答案 1 :(得分:1)

不确定完全理解这个问题,但是您无法从正常程序访问堆栈中的参数。运行时元数据仅与静态信息(方法,属性,常量等)有关。

我相信只有一个调试器(它被认为是一个特殊的野兽)可以在不改变程序/源的情况下做到这一点,这会带来严重的性能损失。作为旁注,这里有一个链接,解释如何构建自己的托管调试器(.NET 4):CLR Managed Debugger (mdbg) Sample 4.0

另一个解决方案是检测代码(自动或使用工具)添加一些跟踪调用,该调用可以捕获每个跟踪方法的参数列表。像PostSharp这样的工具可以做到这一点。这是另一个链接:Non-Invasive Tracing & Logging

答案 2 :(得分:0)

您可以使用非托管调试API来访问调用堆栈并调用堆栈上的对象先前函数。

问题是,堆栈可能不包含您期望的方法。在内联和尾部调用优化的情况下,调用堆栈包含先前调用的方法,这意味着您无法可靠地执行所需的操作。

有关详细信息,请参阅this answer by Eric Lippert

答案 3 :(得分:0)

这不使用调用堆栈,但可能会给你一些里程:

class CalledClass
{
    public static void PokeCaller()
    {
        Program._this.Error = "Error!!!";
    }
}

class Program
{
    public string Error = null;
    [ThreadStatic] public static Program _this;
    public void Run()
    {
        _this = this;
        CalledClass.PokeCaller();
        Console.WriteLine(Error);
        Console.ReadKey();
    }

    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
    }
}

制作错误[ThreadStatic]可能是一种更直接的方式...或者该主题的其他变体。您也可以将它与堆栈跟踪检查结合起来,看看在设置它之前是否实际上被具有“错误”属性的东西调用了......