如何正确测试?(C#,NUnit,Moq)

amn*_*sia 2 c# nunit unit-testing moq

我希望这不是太模糊,但我只是学习使用NUnit和Moq进行单位测试.我理解这些概念,并且我可以毫不费力地为执行简单任务的逻辑方法编写测试,例如操纵某些值或调用伪造服务.但我正在试图弄清楚如何测试如下所示的东西,这是一种需要进行数据调用并具有多个依赖关系的方法.

这是我必须根据数据库对用户进行身份验证的类:

class Authenticator: IAuthenticator
{

    private IConnectionHelper _connHelper;

    public Authenticator(IConnectionHelper connHelper)
    {
        _connHelper = connHelper;
    }

    public string UserId { get; set; }
    public string Password { get; set; }
    public UserModel User { get; private set; }

    public bool ValidateUser()
    {

            if (string.IsNullOrEmpty(UserId))
                return false;

            if (string.IsNullOrEmpty(Password))
                return false;

            using (Entities db = new Entities(_connHelper))
            {
                MD5 md5 = MD5.Create();
                byte[] inputBytes = Encoding.ASCII.GetBytes(this.Password);
                byte[] hash = md5.ComputeHash(inputBytes);
                string md5Hash = BitConverter.ToString(hash).ToUpper().Replace("-", string.Empty);

                var query = from u in db.Users
                            where u.UserId.ToUpper().Trim() == this.UserId.ToUpper()
                            where u.CPassword.Trim() == md5Hash
                            select u;

                this.User = query.FirstOrDefault();
            }



        if (this.User == null)
        {
            Log.LogLine(TraceLevel.Verbose, string.Format("Authentication failed for user '{0}'", this.UserId));
            return false;
        }
        else
        {
            Log.LogLine(TraceLevel.Verbose, string.Format("Successfully authenticated user '{0}'", this.UserId));
            return true;
        }
    }


}
Run Code Online (Sandbox Code Playgroud)

我想为此创建一个测试夹具,但我不确定如何处理它.

我可以模拟IConnectionHelper,并测试如果UserId或Password为null/empty,该方法将失败,但这是我能得到的.如果IConnectionHelper是假的,那么我显然无法测试数据库的东西,我不确定我应该做什么.有人可以在这里阐明最佳做法吗?

编辑

StuartLc接受的答案肯定让我朝着正确的方向前进.我确实需要为Entity Framework做一些额外的设置工作,基本上使用它作为参考:http: //msdn.microsoft.com/en-us/data/dn314429.aspx

Stu*_*tLC 5

根据Ryan的评论,您的代码与依赖关系紧密耦合,Entities并且Log易于单元测试(无需借助MolesFakes).

作为一般规则,使用new创建任何实质依赖或使用static类似的方法Log通常是使用类似框架阻止隔离单元测试的罪魁祸首Moq.

我建议的是重构代码如下:

  1. 通过接口将对Entities类的创建(可能是像EF DbSet或Linq 这样的ORM工件)的关注分离DataContext到工厂中.
  2. IConnectionHelper相关性可以移动到工厂.
  3. 静态记录器也应该被剥离到一个接口中,一个实例也应该注入你Authenticator的生命周期,也由你的IoC容器管理.该LoggerEntitiesFactory可以同时注入

你应该结束如下的事情:

  public interface IEntitiesFactory
  {
     Entities Create();
  }

  public interface ILog
  {
     void LogLine(TraceLevel level, string message);
  }

  class Authenticator : IAuthenticator
  {
     private readonly IEntitiesFactory _entitiesFactory;
     private readonly ILog _log;

     public Authenticator(IEntitiesFactory entitiesFactory, ILog log)
     {
        _entitiesFactory = entitiesFactory;
        _log = log;
     }

     public string UserId { get; set; }
     public string Password { get; set; }
     public UserModel User { get; private set; }

     public bool ValidateUser()
     {
        if (string.IsNullOrEmpty(UserId))
           return false;

        if (string.IsNullOrEmpty(Password))
           return false;

        using (var db = _entitiesFactory.Create())
        {
           MD5 md5 = MD5.Create();
           byte[] inputBytes = Encoding.ASCII.GetBytes(this.Password);
           byte[] hash = md5.ComputeHash(inputBytes);
           string md5Hash = BitConverter.ToString(hash).ToUpper().Replace("-", string.Empty);

           var query = from u in db.Users
                       where u.UserId.ToUpper().Trim() == this.UserId.ToUpper()
                       where u.CPassword.Trim() == md5Hash
                       select u;

           this.User = query.FirstOrDefault();
        }

        if (this.User == null)
        {
           _log.LogLine(TraceLevel.Verbose, string.Format("Authentication failed for user '{0}'", this.UserId));
           return false;
        }
        else
        {
           _log.LogLine(TraceLevel.Verbose, string.Format("Successfully authenticated user '{0}'", this.UserId));
           return true;
        }
     }
Run Code Online (Sandbox Code Playgroud)

,您现在Mock可以在实体工厂和记录器中,现在为Users找到/未找到的方案提供虚假数据,并验证是否发送了正确的东西logger,等等,即

 [Test]
 public void SomeTest()
 {
    var mockFactory = new Mock<IEntitiesFactory>();
    var mockEntities = new Mock<Entities>();
    var fakeUsers = new List<UserModel>
       {
          new UserModel
             {
                UserId = "Bob",
                CPassword = "TheHashOfSecret"
             }
       };
    mockEntities.SetupGet(_ => _.Users).Returns(fakeUsers.AsQueryable());
    mockFactory.Setup(_ => _.Create()).Returns(mockEntities.Object);
    var mockLog = new Mock<ILog>();
    var sut = new Authenticator(mockFactory.Object, mockLog.Object)
       {
          UserId = "Bob",
          Password = "Secret"
       };
    Assert.DoesNotThrow(() => sut.ValidateUser());
    Assert.IsNotNull(sut.User);
    mockLog.Verify(_ => _.LogLine(), Times.Once); ...
 }
Run Code Online (Sandbox Code Playgroud)

  • 这非常有意义,我非常感谢您花时间提供如此详细的回复! (2认同)