关于c#:测试业务逻辑 – MOQ – Visual Studio – MVC

Testing business logic - MOQ - Visual Studio - MVC

我正在努力学习如何进行测试,使用Moq测试来专门测试服务层中的业务逻辑/规则。

这是我项目的一部分:

实体:

1
2
3
4
5
public class Client
{        
    public int Id { get; set; }
    public string Name { get; set; }        
}

存储库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class ClientRepository : IClientRepository
{

    private MyContext context;

    public ClientRepository(MyContext context)
    {
        this.context = context;
    }

    public void Save(Client client)
    {
        try
        {
            context.Clients.Add(client);
            context.SaveChanges();                
        }
        catch (Exception)
        {
            throw new Exception("Error saving");
        }
    }

    public void Delete(Client client)
    {
        Client cl = context.Clients.Where(x => x.Id == client.Id).FirstOrDefault();
        if (cl != null)
        {
            context.Clients.Remove(client);
            context.SaveChanges();
        }
        else
        {
            throw new Exception("Error deleting");
        }
    }

    public List<Client> List()
    {
        return context.Clients.ToList();
    }        

    public Client GetById(int Id)
    {
        return context.Clients.Where(x => x.Id == Id).FirstOrDefault();
    }        
}

存储库接口:

1
2
3
4
5
6
7
public interface IClientRepository
{
    void Save(Client client);
    void Delete(Client client);      
    List<Client> List();
    Client GetById(int Id);
}

服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class ClientService
{        
    private ClientRepository rep;        

    public ClientService(MyContext ctx)
    {
        this.rep = new ClientRepository(ctx);
    }

    public void Save(Client client)
    {
        try
        {
            Validate(client);
            rep.Save(client);
        }
        catch (Exception ex) {
            Debug.WriteLine(ex.Message);
            throw new Exception("Error Saving");
        }                
    }

    public void Delete(Client client)
    {
        try
        {
            if (client.Name.StartsWith("A"))
            {
                throw new Exception("Can't delete client with name
                                     starting with A"
);
            }
            rep.Delete(client);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            throw new Exception("Error deleting");
        }
    }

    public List<Client> List()
    {
        try
        {
            return rep.List();
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            throw new Exception("Error list");
        }
    }

    public void Validate(Client client)
    {
        if (client.Name.Length < 2)
        {
            throw new Exception("nome deve ser maior que 2");
        }            
    }

    public Client GetById(int Id)
    {
        return rep.GetById(Id);
    }
}

我的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[TestMethod]
    public void CantSaveClientWithNameLengthLessThanTwo()
    {
        Client client = new Client() { Id = 4, Name ="a" };

        var mockMyContext = new Mock<MyContext>();

        mockMyContext.Setup(c => c.Clients.Add(client)).Throws<InvalidOperationException>();

        var service = new ClientService(mockMyContext.Object);

        service.Save(client);

        int listCount = service.List().Count();

        Assert.AreEqual(0, listCount);
    }

在此测试中,我想测试业务逻辑,该逻辑阻止我保存名称少于2个字符的客户端。当然,这个测试工作不正确,我最终得到一个例外。

我想知道如何实现测试以测试这两个业务需求,并且只在我的项目中:

  • 无法保存名称中少于2个字符的客户端。
  • 无法删除名称以"A"开头的客户端。
  • 我正在使用Microsoft Visual Studio 2010,Entity Framework 4.0和Moq。

    我感谢我能得到的任何帮助,以及我在项目中应该做出的改变的建议,只有在我不能用我的实际项目模式完成我想要的时候。


    首先,在ClientService中,您将通过硬编码创建数据访问层的实例ClientRepository。这将使其难以测试。因此,更好的方法是将IRepository实现注入ClientService。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ClientService
    {        
        private IClientRepository repo;        

        public ClientService(IClientRepository repo)
        {
          this.repo = repo;
          // now use this.repo in your methods
        }
    }

    现在,对于您的测试,当Name属性值少于2个字符时,您的Validate方法会抛出异常。要对此进行测试,可以使用ExpectedException属性修饰测试方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public void ValidateShouldThrowException()
    {
      var moqRepo = new Mock<IRepository>();
      var cs = new ClientService(moqRepo.Object);
      var client = new Client { Name ="S" };

      cs.Save(client);
    }

    我的建议是不抛出常规异常,但如果需要,可以使用某些特定的异常,如ArgumentException等或您的自定义异常。

    对于要传递具有2个以上字符的Name属性值的第二个测试,它不应该从Validate中抛出异常。在这个测试中,我们将模拟Save行为,以便它实际上不会尝试保存到db(它不应该)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [TestMethod]
    public void SaveShouldWork()
    {
      var moqRepo = new Mock<IRepository>();
      moqRepo.Setup(s=>s.Save(It.IsAny<Client>)).Verifiable();
      var cs = new ClientService(moqRepo.Object);
      var client = new Client { Name ="S" };

      cs.Save(client);
      //Test passed :)
    }

    我的最后一个建议是将验证代码提取到新的类/接口组合,并将Validator实现注入ClientService。使用此方法,您可以根据需要注入另一个版本的验证。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ClientService
    {        
        private IClientRepository repo;        
        private IValidator validator;
        public ClientService(IClientRepository repo,IValidator validator)
        {
          this.repo = repo;
          this.validator = validator
        }
    }