我们正在考虑将单元测试添加到我们的C#代码库中。我发现将单元测试添加到简单类很容易,但与其他依赖项交互的类更难。我一直在研究模拟框架,但是想知道最好的编写类的方法,以打破外部依赖,例如文件系统,数据库和消息系统依赖。
举一个例子,例程在套接字上侦听某种格式的消息 - 比如MessageA。这被解码,一些计算完成后,将其重新编码为不同的二进制格式,然后发送结果消息,MessageB。
我目前的测试方法如下。我为所有套接字交互提取一个接口,并创建一个模拟接口。我将接口设置为单例。然后针对硬编码输入运行该类。被测试的类将使用单例中的接口来发送/接收。
我做类似的事情来测试数据库交互。
这似乎不是最灵活的方法,你会如何改进它以使其更容易测试?如果一个模拟框架是答案,我将如何设计类?
示例代码:
[SetUp]
public void init()
{
// set message interface in singleton as mock interface
CommAdapter.Instance.MessageAdapter = new MockMessage();
// build reference message from hard coded test variables
initialiseMessageA();
// set input from mock message socket
((MockMessage) CommAdapter.Instance.MessageAdapter).MessageIn = m_messageA;
}
[Test]
public void test_listenMessage_validOutput()
{
// initialise test class
MessageSocket tS = new MessageSocket();
// read from socket
tS.listenMessage();
// extract mock interface from singleton
MockMessage mm = ((MockMessage) CommAdapter.Instance.MessageAdapter);
// assert sent message is in correct / correstpoinding format
Assert.AreEqual(1000001, mm.SentMessageB.TestField);
}
答案 0 :(得分:7)
使用Dependency Injection和DI library like Ninject,而不是使用Singletons来设置组件实现。这正是他们设计的场景类型。
没有特别推动你进入Ninject,但他们有一个很好的教程:)概念将转移到其他框架(如Unity)。
单独使用DI,代码看起来像这样:
class Samurai {
private IWeapon _weapon;
public Samurai(IWeapon weapon) {
_weapon = weapon;
}
public void Attack(string target) {
_weapon.Hit(target);
}
}
class Shuriken : IWeapon {
public void Hit(string target) {
Console.WriteLine("Pierced {0}'s armor", target);
}
}
class Program {
public static void Main() {
Samurai warrior1 = new Samurai(new Shuriken());
Samurai warrior2 = new Samurai(new Sword());
warrior1.Attack("the evildoers");
warrior2.Attack("the evildoers");
}
}
现在看起来很干净,但要等到你的依赖项有依赖关系,或者进一步:)你可以使用DI库来解决这个问题。
使用库来处理连接,它看起来像:
class Program {
public static void Main() {
using(IKernel kernel = new StandardKernel(new WeaponsModule()))
{
var samurai = kernel.Get<Samurai>();
warrior1.Attack("the evildoers");
}
}
}
// Todo: Duplicate class definitions from above...
public class WarriorModule : NinjectModule {
public override void Load() {
Bind<IWeapon>().To<Sword>();
Bind<Samurai>().ToSelf().InSingletonScope();
}
}
使用这些方法中的任何一种,加上a mock object framework like Moq,您的单元测试看起来像这样:
[Test]
public void HitShouldBeCalledByAttack()
{
// Arrange all our data for testing
const string target = "the evildoers";
var mock = new Mock<IWeapon>();
mock.Setup(w => w.Hit(target))
.AtMostOnce();
IWeapon mockWeapon = mock.Object;
var warrior1 = new Samurai(mockWeapon);
// Act on our code under test
warrior1.Attack(target);
// Assert Hit was called
mock.Verify(w => w.Hit(target));
}
你会注意到你可以直接将模拟实例传递给测试中的代码,而且你不必乱用设置单例。这将帮助您避免需要多次设置状态或在两次调用之间出现的问题。这意味着没有隐藏的依赖关系。
您还会注意到我没有在测试中使用DI容器。如果您的代码是一个很好的因素,它只会测试一个类(并且尽可能经常只测试一个方法),并且您只需要模拟该类的直接依赖关系。
答案 1 :(得分:1)
除了DI容器(我目前使用的是MS Unity 2.0,但有很多可供选择),你需要一个好的模拟框架,我的偏好是MOQ。打破具体依赖关系的常见模式/流程是: