我设计了一个使用存储库模式的应用程序,然后设计了一个单独的服务层,例如:
public class RegistrationService: IRegistrationService
{
public void Register(User user)
{
IRepository<User> userRepository = new UserRepository();
// add user, etc
}
}
public class RegistrationService: IRegistrationService
{
public void Register(User user)
{
IRepository<User> userRepository = new UserRepository();
// add user, etc
}
}
如您所见,我在Register方法中实例化我的存储库。现在,因为我想编写一些单元测试,我真的无法实现并用虚假的存储库替换它吗?
我不想将存储库添加为类变量(并通过构造函数设置),因为我认为这会使我的代码“臭”(并非所有方法都需要所有存储库,而且我不需要需要调用层来了解存储库等。)。
建议?
答案 0 :(得分:16)
您需要使用依赖注入。 UserRepository是RegistrationService类的依赖项。为了使您的类适当地单元可测试(即隔离它们的依赖项),您需要“反转”控制依赖项创建的内容。目前,您可以直接控制,并在内部创建它们。只需反转该控件,并允许外部(例如像Castle Windsor这样的IoC容器)注入它们:
public class RegistrationService: IRegistrationService
{
public RegistrationService(IRepository<User> userRepo)
{
m_userRepo = userRepo;
}
private IRepository<User> m_userRepo;
public void Register(User user)
{
// add user, etc with m_userRepo
}
}
我想我忘了添加。一旦允许注入依赖项,就可以打开它们可模拟的可能性。由于您的RegistrationService现在将其依赖用户存储库作为其构造函数的输入,因此您可以创建一个模拟存储库并将其传递,而不是实际的存储库。
答案 1 :(得分:9)
依赖注入对于这些事情来说非常方便:
public class RegistrationService: IRegistrationService
{
public void Register(User user)
{
this(user, new UserRepository());
}
public void Register(User user, IRepository<User> userRepository)
{
// add user, etc
}
}
通过这种方式,您可以将UserRepository用作默认设置,并为您需要的任何其他内容传入自定义(Mock或Stub进行测试)IRepository。您可能还想更改第二个寄存器方法的范围,以防您不想将其暴露给所有内容。
更好的替代方案是使用IoC容器,它会为您购买比上述示例更加分离的语法。你可以得到这样的东西:
public class RegistrationService: IRegistrationService
{
public void Register(User user)
{
this(user, IoC.Resolve<IRepository<User>>());
}
public void Register(User user, IRepository<User> userRepository)
{
// add user, etc
}
}
有许多IoC容器,如其他人的答案所列,或者你可以roll your own。
答案 2 :(得分:2)
您应该做的是使用Inversion of Control或Dependency injection。
您的类不应该绑定到另一个类的实现,特别是在这样的层之间,而应该将表示用户存储库的接口作为构造函数或属性。然后当它运行时,您提供具体的实现,并使用它。您可以构建假货,然后实现存储库接口并在测试时提供它们。
有很多IOC容器,Unity,Ninject,Castle Windsor和StructureMap等等。
我只是想明确一点,在你的单元测试中,我不知道你真的想要使用 IOC容器。集成测试,当然,但对于单元测试,只需新建您正在测试的对象并在该过程中提供假的。 IOC容器至少是IMO,可以帮助解决应用程序或集成测试级别,并使配置更容易。
您的单元测试应该保持“纯粹”并且只是使用您要测试的对象。至少这对我有用。
答案 3 :(得分:1)
只是为每个人所说的添加一些清晰度(或许没有),IOC基本上工作的方式是你告诉它用Y替换X.所以你要做的就是接受IRepository(正如其他人所说)作为一个参数然后在IOC中,你会告诉它用特定类的MockedRepository替换IRepository(例如)。
如果你使用允许web.config初始化的IoC,它会使得从dev到prod的转换非常顺利。您只需在配置文件中修改相应的参数,即可开始使用 - 无需触摸代码。之后,如果您决定转移到其他数据源 - 您只需将其切换出来即可。
IoC是一些有趣的东西。
另一方面,如果你想做的只是单元测试,你可以在没有IOC注射的情况下完成。你可以通过重载构造函数来接受一个IRepository,然后以这种方式传递你的模拟repo来做到这一点。
答案 4 :(得分:1)
我正在做一些事情,好吧,此刻几乎是逐字逐句。我正在使用MOQ来模拟我的IUserDataRepository的代理实例。
从测试角度来看:
//arrange
var mockrepository = new Mock<IUserDataRepository>();
// InsertUser method takes a MembershipUser and a string Role
mockrepository.Setup( repos => repos.InsertUser( It.IsAny<MembershipUser>(), It.IsAny<string>() ) ).AtMostOnce();
//act
var service = new UserService( mockrepository.Object );
var createResult = service.CreateUser( new MembershipUser(), "WebUser" );
//assert
Assert.AreEqual( MembershipCreateUserResult.Success, createResult );
mockrepository.VerifyAll();
MOQ也可用于抛出特定的异常,以便在这些情况下测试我的UserService。
然后像其他人一样提到IoC将处理所需的依赖项。在这种情况下,为UserService类创建一个真正的存储库。
答案 5 :(得分:0)
您可以使用IOC容器,然后注册其他存储库。
答案 6 :(得分:0)
我的建议仍然是将存储库移动到类参数,并在需要时使用IoC容器注入存储库。这样,代码简单且可测试。