單元測試
參數化測試和物件比對物件
比對物件 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("測試例外");
}
}
}
下中斷點的時候會跑到
經測試後會變成兩個綠燈
元哥的筆記