c# - C# 单元测试验证 Activator 实例化的类的非抽象方法
问题描述
首先,让我介绍一下我的项目。
我们正在开发一个应用程序,用户可以在其中使用程序。作为程序,我的意思是保密使用的说明列表。
有不同类型的程序都继承自 Program 抽象基类。
由于用户可以创建不同类型的程序,我们开发了一个程序管理器,它可以按类型实例化任何类型的程序。我们不需要实例化抽象类,但是所有的具体类(它都可以工作),但是作为具体的 Program 具有相同的方法(AddNewChannel,Save,...),我们像 Programs 一样处理它们。
这是一个代码示例:
public Program CreateProgram(Type type)
{
Program program = Activator.CreateInstance(type) as Program;
program.Directory = ProgramsPath;
int nbChannels = 2; //not 2 but a long line searching the correct number where it is.
for (int i = 1; i <= nbChannels; i++)
{
program.AddNewChannel(i);
}
program.Save();
return program;
}
我现在要做的是测试这个函数,我不想复制我已经为不同的 Program 类制作的 unitTests。
例如,这是我的一个测试函数(用于该Save
方法),它是 init。我将需要测试的类型存储在 xml 文件中。
[TestInitialize]
public void TestInitialize()
{
if (!TestContext.TestName.StartsWith("GetKnownTypes"))
type = UnitTestsInitialization.applicationHMIAssembly.GetType((string)TestContext.DataRow["Data"]);
}
[TestMethod]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.XML",
"|DataDirectory|\\" + DATA_FILE, "Row",
DataAccessMethod.Sequential)]
public void SavedProgramCreatesFile()
{
Program program = Activator.CreateInstance(type) as Program;
program.Name = "SavedProgramCreatesFile";
program.Directory = DIRECTORY;
program.Save();
string savedProgramFileName = program.GetFilePath();
bool result = File.Exists(savedProgramFileName);
Assert.IsTrue(result);
}
我所有的具体程序类都经过了单独测试。
program.AddNewChannel
因此,我想测试是否program.Save
调用了以下方法。
我看了一下 Moq,但第一个问题是该方法Save
不是抽象的。
此外,使用 Activator 不允许我制作Mock<Program>
.
我在单元测试中尝试了以下操作,以尝试实例化模拟并像程序一样使用它:
[TestMethod]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.XML",
"|DataDirectory|\\" + DATA_FILE, "Row",
DataAccessMethod.Sequential)]
public void CreateProgram_CallsProgramSaveMethod()
{
Mock<Program> mock = new Mock<Program>();
mock.Setup(p => p.AddNewChannel(It.IsAny<int>()));
Program program = pm.CreateProgram(mock.Object.GetType());
mock.Verify(p => p.Save());
mock.Verify(p => p.GetFilePath(It.IsAny<string>()));
mock.Verify(p => p.AddNewChannel(It.IsAny<int>()), Times.Exactly(ProgramManager.NB_MACHINE_CHANNELS));
Assert.IsNotNull(program);
program.DeleteFile();
}
受到这个问题的启发:How to mock An Abstract Base Class
它一直有效,直到它到达program.AddNewChannel(i);
for 循环中的行。错误如下:
System.NotImplementedException:'这是一个 DynamicProxy2 错误:拦截器试图为抽象的方法'Void AddNewChannel(Int32)''继续'。调用抽象方法时,没有“继续”的实现,拦截器有责任模仿实现(设置返回值、输出参数等)
似乎设置不起作用,但我可能明白为什么。(我尝试实例化一个不实现验证方法的代理子类型)
我还尝试在我的程序类上使用代理,该代理将实现一个包含我需要的方法的接口,但这里的问题又是激活器。
谁能建议我测试这些方法调用的任何方法?(即使我需要改变我的方法CreateProgram
)
我在这里看了一下:如何模拟非虚拟方法?但我不确定这是否适用于我的问题。
我使用 MSTests 进行单元测试。
注意
其他一切正常。我所有的其他测试都顺利通过,我的代码似乎可以工作(手工测试)。
提前致谢。
解决方案
问题的根本原因是您使用类型作为参数,然后您使用它来创建此类型的实例。但是,您传递的是抽象类的类型,它不是专门用于实例化的。您需要直接使用具体类。
因此,我想测试是否调用了以下方法 program.AddNewChannel 和 program.Save。
这还不足以作为测试。您想测试这些方法是否按预期工作,而不仅仅是调用它们然后假设它们工作。
您所描述的是(非常基本的)集成测试,而不是单元测试。
我不想复制我已经为不同的程序类制作的单元测试
这是一个非常危险的决定。单元测试背后的部分想法是您为不同的(具体)对象创建单独的测试。测试需要尽可能合理地隔离。您正在尝试重用测试逻辑,这是一件好事,但需要以不损害您的测试隔离的方式完成。
但是有一些方法可以在不影响测试隔离的情况下做到这一点。我只有 NUnit 的测试经验,但我认为类似的方法也适用于其他框架。
假设如下:
public abstract class Program
{
public bool BaseMethod() {}
}
public class Foo : Program
{
public bool CustomFooMethod() {}
}
public class Bar : Program
{
public bool CustomBarMethod() {}
}
创建一个抽象类测试方法:
[TestFixture]
[Ignore]
public class ProgramTests
{
public virtual Program GetConcrete()
{
throw new NotImplementedException();
}
[Test]
public void BaseMethodTestReturnsFalse()
{
var result = GetConcrete().BaseMethod();
Assert.IsFalse(result);
}
}
[Ignore]
确保ProgramTests
类不会自行测试。
然后你从这个类继承,具体的类将在其中进行测试:
[TestFixture]
public class FooTests
{
private readonly Foo Foo;
public FooTests()
{
this.Foo = new Foo();
}
public overrides Program GetConcrete()
{
return this.Foo;
}
[Test]
public void CustomFooMethodTestReturnsFalse()
{
var result = this.Foo.CustomFooMethod();
Assert.IsFalse(result);
}
}
BarTests
类似地实现。
NUnit(可能还有其他测试框架)将发现所有继承的测试并为派生类运行这些测试。因此,派生自的每个类ProgramTests
都将始终包含BaseMethodTestReturnsTrue
测试。
这样,您的基类的测试是可重用的,但每个具体类仍将单独测试。这可以保持您的测试分离,同时还可以防止您必须为每个具体类复制/粘贴测试逻辑。
我也注意到了这一点:
Mock<Program> mock = new Mock<Program>();
mock.Setup(p => p.AddNewChannel(It.IsAny<int>()));
Program program = pm.CreateProgram(mock.Object.GetType());
我不明白这段代码的目的。它与简单地做有什么不同:
Program program = pm.CreateProgram(typeof(Program).GetType());
据我所知,模拟和它的设置都是无关紧要的,因为你只是在查看它的类型,然后CreateProgram()
无论如何都要为你创建一个新对象。
其次,这参考了我测试具体类的示例,您不应该Program
直接测试,而应该测试派生程序类(Foo
和Bar
)。
这是问题的根本原因。您使用类型作为参数,然后使用它来创建此类型的实例。但是,您传递的是抽象类的类型,它不是专门用于实例化的。您需要直接使用具体类。
推荐阅读
- java - 当我想使用 jaxb2 生成 xsd 文件时,如何为所有 java 类指定相同的命名空间?
- excel - 组合框正在改变我在表格中的数字序列的顺序
- video - ffmpeg:不断编码并将base64数据块附加到输出文件中
- java - 为什么在声明浮点变量时需要在十进制数的末尾放置一个“f”?
- angular - 修改角函数以修改多个对象键而不是单个键
- flutter - 理解 Dart 中的异步和同步
- sql - 教义:以键作为关联实体值的结果
- python-3.x - 如何解决 Yolov5 train,py in yaml 中的错误
- rsocket - C# net RSocket 客户端与 Java RSocket 服务器无效的 mime 类型“二进制”
- javascript - 您是否需要前一个节点指针来实现队列?