如何使用反射获取处置的实例名称

How to get the name of the instance disposed by using reflection

我正在为我的数据库存储过程构建一些集成测试。 我已经设置了一个 xUnit 项目并实现了 Fixture 模式。向您展示:

public class MyTableTest : IClassFixture<DatabaseFixture>
{

    public MyTableTest()
    {
        //DO SOMETHING
    }

    [Fact]
    public void Test()
    {
        //DO SOMETHING
    }
}

并且:

public class DatabaseFixture : IDisposable
{
    public void Dispose()
    {
        // ... clean up test data from the database ...
    }
}

这个 DatabaseFixture 将在我的所有测试 类 中共享。为什么?因为我希望在每次测试结束时发生一些通用逻辑,例如清理。

重点是我需要知道要清理哪个 table,在我的示例中是 MyTable。当 Dispose 方法将 运行 对 MyTableTest 的实例进行处置时,我将通过使用反射来检索此类信息。我怎样才能做到这一点?是否有可能(并且正确地)尝试实现这一目标?提前致谢。

您可以在 DatabaseFixture class 中有一个 TableName 属性。然后在测试 classes 的构造函数中接收 class 的实例并设置 TableName 属性。稍后你可以在 dispose 中使用它来做一些清理工作。

public class MyTableTest : IClassFixture<DatabaseFixture>
{
    DatabaseFixture databaseFixture;
    public MyTableTest(DatabaseFixture databaseFixture)
    {
        this.databaseFixture = databaseFixture;
        databaseFixture.TableName = "MyTable";
    }

    [Fact]
    public void Test()
    {
    }
}
public class DatabaseFixture : IDisposable
{
    //...

    public string TableName { get; set; }

    //...
    public void Dispose()
    {
        // Cleanup based on TableName
    }
}

要了解有关在 xUnit 中共享上下文的更多信息,请查看:

您可以使用自定义属性将任意数据附加到派生的 Fixture class。

例如

  1. 您可以这样创建 TableNameAttribute
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class TableNameAttribute : Attribute
{
    public string Name { get; }
    public TableNameAttribute(string name)
    {
        this.Name = name;
    } 
}
  1. 您可以将此属性应用于您的派生夹具 class:
[TableName("MyTable")]
public class MyTableFixture : DatabaseFixture { }
  1. 您可以在测试中使用该夹具 class
public class MyTableTest : IClassFixture<MyTableFixture>
{
    [Fact]
    public void Test()
    {
        //DO SOMETHING
    }
}

最后,这是从 Dispose 方法检索 Name 的方法:

public abstract class DatabaseFixture : IDisposable
{
    ...
    public void Dispose()
    {
        var attribute = this.GetType().GetCustomAttribute(typeof(TableNameAttribute));
        if (attribute is TableNameAttribute tableNameAttr)
            Console.WriteLine(tableNameAttr.Name);
    }
}

Is it even possible (and correct) trying to achieve this?

没有。反射不能告诉类型 T 在什么上下文中使用 T;反射只看到T的声明。
更具体到你的情况,反射不能告诉类型 DatabaseFixture 它在 MyTableTest 的声明中被用作泛型接口 IClassFixture 的类型参数。也就是说,对于这组声明,

class A { }
class B <T> {  }
class C : B<A> { }

A无法通过反射确定它在C的声明中使用,但C可以知道它在A:

中的用法
typeof(C)
    .BaseType // B
    .GetGenericArguments()[0] // A

How can I achieve this?

根据您使用 DatabaseFixture 的方式,您可以使用 StackTrace 获得调用测试 class(如果您真的热衷于使用反射)。这是一个简单的例子:

    public class DisposableObject : System.IDisposable
    {
        public void Dispose()
        {
            var stack = new System.Diagnostics.StackTrace();

            // This will log the name of the class that instantiated and disposed this. 
            System.Console.WriteLine(stack.GetFrame(1).GetMethod().DeclaringType.Name);
            return;
        }
    }

如果你的 DatabaseFixture 不是直接从你的测试 class 调用的,你要么必须知道要传递给 GetFrame(int) 的偏移量,要么你需要搜索每一帧直到找到符合您要求的第一个 DeclaringType(例如,BaseTypeIClassFixture,通用参数 DatabaseFixture),像这样:

System.Type testClassType = new StackTrace()
    .GetFrames()
    .Where(f =>
    {
        System.Type baseType = f.GetMethod().DeclaringType.BaseType;
        return typeof(IClassFixture<DatabaseFixture>).IsAssignableFrom(baseType);
    })
    .FirstOrDefault() // First matching result (assuming you found any)
    ?.GetMethod() // Get the reflected Method
    .DeclaringType; // Get the type (e.g. class) that declares this method.
string tableName = testClassType.Name.Replace("Test", "");

否则,您将需要按照 and 的建议手动设置 table 名称。