單元測試-常用的測試情境Stub、Mock用法和Try Catch 測試

單元測試

參數化測試和物件比對物件

比對物件 Nuget  FluentAssertiions

感謝網友Gao Li Jyu

在<Art of Unit Testing>一書, 作者Roy Osherove有定義Stub/Mock

https://www.youtube.com/watch?v=fAb_OnooCsQ......

26:38中用UT套件建立的物件皆是Fake, 這些物件如果被verify(assert)為Mock, 否則為Stub

    [TestFixture]
    public class AddTest
    {
        [Test]
        [TestCase(1, 2)]
        [TestCase(2, 1)]
        public void TestAdd(int a, int b)
        {
            Assert.AreEqual(3, Add(a, b));
        }

        [Test]
        public void ObjectEqualObject() 
        {
            List<User> expected = new List<User> { new User { Id = 1,Name ="AAA" },
                                                new User{Id=2,Name="BBB"} };
            List<User> result = new List<User> { new User { Id = 1,Name ="AAA" },
                                                new User{Id=2,Name="BBB"} };
            expected.Should().BeEquivalentTo(result);
        }
        public int Add(int a, int b)
        {
            return a + b;
        }

        
    }

 

筆者分別記錄常用的Stub和Mock和Try Catch的情境測試技巧做筆記

Stub 假設我要進行做會員登入成功和失敗,必須做隔離測試,如何做呢?
Model定義:

using System;

namespace Model
{
    public class Member
    {
        public string UserId { get; set; }
        public string  Password { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}
Define Repository Layer Interface and implement

using System;
using System.Collections.Generic;
using System.Text;
using Model;
namespace Repository.Interface
{
    public interface IMemberRepository
    {
        Member GetByMember(string userid, string password);
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using Model;
using Repository.Interface;

namespace Repository
{
    public class MemberRepository : IMemberRepository
    {
        public List<Member> members = new List<Member>() {
            new Member{ UserId = "test001",
                        Password = "test002",
                        Name ="Eddie",
                        Email = "aaa@gmail.com"}
        };
        public Member GetByMember(string userid, string password)
               => members.Where(m => m.UserId == userid && m.Password == password).FirstOrDefault();

    }
}

Define Service Layer Interface and implement

using System;
using System.Collections.Generic;
using System.Text;

namespace Service.Interface
{
    public interface IMemberService
    {
         bool Login(string userId, string password);
    }
}
using Repository.Interface;
using Service.Interface;
using System;

namespace Service
{
    public class MemberService : IMemberService
    {
        public readonly IMemberRepository _memberRepository;
        public MemberService(IMemberRepository memberRepository)
        {
            _memberRepository = memberRepository;
        }
        public bool Login(string userid, string password)
        {
            var member = _memberRepository.GetByMember(userid, password);
            if (member == null)
                return false;
        
            return true;
        }
    }
}

Create Unit Test Login Success and Login Fail

using NUnit.Framework;
using NSubstitute;
using Repository.Interface;
using Model;
using Service.Interface;
using Service;

namespace Tests
{
    [TestFixture]
    public class MemberTests
    {
        private IMemberRepository _memberRepository;
        private IMemberService _memberService;
        private Member _setReturnMember;
        [SetUp]
        public void Setup()
        {
            _memberRepository = Substitute.For<IMemberRepository>();
            _memberService = new MemberService(_memberRepository);
            _setReturnMember = new Member()
            {
                UserId = "test001",
                Password = "test001",
                Name = "test",
                Email = "abc@gmail.com"
            };
        }

        [Test]
        public void Login_ReturnSuccess()
        {
            string userid = "test001";
            string password = "test001";
            _memberRepository.GetByMember(userid,password).Returns(_setReturnMember);
            //任一參數並回傳多值做法
            //_memberRepository.GetByMember(Arg.Any<string>(), Arg.Any<string>()).ReturnsForAnyArgs
            //                                (_setReturnMember1,
            //                                 _setReturnMember2);
            bool result = _memberService.Login(userid, password);
            Assert.IsTrue(result);
        }

        [Test]
        public void Login_ReturnFail()
        {
            string userid = "test001";
            string password = "test002";
            _memberRepository.GetByMember(userid, password).Returns(m => null);
            bool result = _memberService.Login(userid, password);
            Assert.IsFalse(result);
        }
    }
}

筆者有時候會忘記Mock的用法,稍微筆記一下,分別定義IEmailService、IUserRepository、User 

    public interface IUserRepository
    {
        void Add(User user);
    }
    
    public interface IEmailService
    {
        string Send(string message);
    }
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

最後寫一個基本的UserService層

    public class UserService
    {
        private IUserRepository _userRepository;
        private IEmailService _emailService;
        public UserService(IUserRepository userRepository,IEmailService emailService) 
        {
            _userRepository = userRepository;
            _emailService = emailService;
        }

        public bool AddUser(User user ,string message) 
        {
            _userRepository.Add(user);
            _emailService.Send(message);
            return true;
        }
    }

最後還是寫測試專案來驗證自己的Mock 測試新增的方法Pass

using MockDemo;
using NSubstitute;
using NUnit.Framework;

namespace MockDemoTest
{
    [TestFixture]
    public class UserServiceTest
    {
      
        [Test]
        public void UserService_Add_Test()
        {
           var emailService =  Substitute.For<IEmailService>();
           var userRepository = Substitute.For<IUserRepository>();
           var UserService = new UserService(userRepository,emailService);
            //呼叫該method
            Assert.IsTrue(UserService.AddUser(new User { Id = 1, Name = "張三" }, "送出寄信"));
            //驗證呼叫有寄信和呼叫寫入Add的method
            emailService.Received().Send(Arg.Is<string>(s=>s.Contains("送出寄信")));
            userRepository.Received().Add(Arg.Is<User>(u=>u.Name=="張三"));
         
        }
    }
}

最後要測試Try Catch 情境測試狀況下,在原有程式追加Try Cath

    public class UserService
    {
        private IUserRepository _userRepository;
        private IEmailService _emailService;
        public UserService(IUserRepository userRepository,IEmailService emailService) 
        {
            _userRepository = userRepository;
            _emailService = emailService;
        }

        public bool AddUser(User user ,string message) 
        {
            try
            {
                _userRepository.Add(user);
                _emailService.Send(message);
                return true;
            }
            catch (Exception ex)
            {
                _emailService.Send(ex.Message);
                return false;
            }
           
        }
using MockDemo;
using NSubstitute;
using NUnit.Framework;
using System;

namespace MockDemoTest
{
    [TestFixture]
    public class UserServiceTest
    {
      
        [Test]
        public void UserService_Add_Test()
        {
           var emailService =  Substitute.For<IEmailService>();
           var userRepository = Substitute.For<IUserRepository>();
           var UserService = new UserService(userRepository,emailService);
            //呼叫該method
            Assert.IsTrue(UserService.AddUser(new User { Id = 1, Name = "張三" }, "送出寄信"));
            //驗證呼叫有寄信和呼叫寫入Add的method
            emailService.Received().Send(Arg.Is<string>(s=>s.Contains("送出寄信")));
            userRepository.Received().Add(Arg.Is<User>(u=>u.Name=="張三"));
         
        }
        [Test]
        public void UserService_Add_TryCatch()
        {
            var emailService = Substitute.For<IEmailService>();
            var userRepository = Substitute.For<IUserRepository>();
            var userService = new UserService(userRepository, emailService);   
            //在寄信的地方拋出例外動作
            emailService.When(e => e.Send("送出寄信")).Do(info => { throw new Exception("測試例外"); });
            Assert.False(userService.AddUser(new User { Id = 1, Name = "張三" }, "送出寄信"));
            emailService.Received().Send("測試例外");
        }
    }
}

下中斷點的時候會跑到

經測試後會變成兩個綠燈

元哥的筆記