这只是电子邮件发送应用程序的一小部分,但我正在尝试为应用程序实施单元测试。到目前为止,我已经阅读了一些关于单元测试的内容,我知道你应该只测试单个函数而不是整个类。我该如何测试这段代码?
private void Send(IEnumerable<Customer> customers, string body)
{
string titleOfEmail = "Welcome as a new customer at Company!";
string ourEmailAddress = "info@company.com";
int NumberOfRetriesOnError = 2;
int DelayOnError = 10;
foreach (var customer in customers)
{
for (int i = 0; i <= NumberOfRetriesOnError; ++i)
{
try
{
Sender.Send(ourEmailAddress, customer.Email, titleOfEmail);
return;
}
catch (SmtpException e)
{
if (i < NumberOfRetriesOnError)
Thread.Sleep((i + 1) * DelayOnError);
else
Errors.Add(e.Message + customer); // Include customeremail
}
}
}
编辑:其余部分可能不需要其他信息
public interface IMailSender
{
void Send(string from, string to, string body);
}
sealed class NullMailSender : IMailSender
{
void IMailSender.Send(string from, string to, string body)
{
}
}
sealed class SmtpMailSender : IMailSender
{
void IMailSender.Send(string from, string to, string body)
{
System.Net.Mail.MailMessage mail = new System.Net.Mail.MailMessage();
mail.From = new System.Net.Mail.MailAddress(from);
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("yoursmtphost");
mail.To.Add(to);
mail.Body = body;
smtp.Send(mail);
}
}
这部分是顶级函数的其余部分,全部是
public class SendingMail
{
public List<string> Errors { get; } = new List<string>();
public IEnumerable<Customer> Customers { get; set; }
public IEnumerable<Order> Orders { get; set; }
public IMailSender Sender { get; set; }
public void SendWelcomeEmails()
{
var template = Resources.WelcomeEmailTemplate;
Send(GetNewCustomers(), Resources.WelcomeEmailTemplate);
}
public void SendComeBackEmail()
{
var template = Resources.WelcomeEmailTemplate;
var emailContent = String.Format(template);
Send(GetCustomersWithoutRecentOrders(), Resources.ComeBackEmailTemplate);
}
private IEnumerable<Customer> GetNewCustomers()
{
var yesterday = DateTime.Now.Date.AddDays(-1);
return Customers.Where(x => x.CreatedDateTime >= yesterday);
}
private IEnumerable<Customer> GetCustomersWithoutRecentOrders()
{
var oneMonthAgo = DateTime.Now.Date.AddMonths(-1);
return Customers.Where(c => {
var latestOrder = Orders
.Where(o => o.CustomerEmail == c.Email)
.OrderByDescending(o => o.OrderDatetime)
.FirstOrDefault();
return latestOrder != null
&& latestOrder.OrderDatetime < oneMonthAgo;
});
}
答案 0 :(得分:2)
好吧,我认为可能会让你失望的是你没有按照SRP(单一责任原则)来解决你的职能。
这样说:你目前负责的是什么?
...所以马上,你的单元测试必须尝试在调用函数时处理所有这些单独的东西。单元测试讨厌那个。
但是如果你以不同的方式编写你的函数怎么办?
// needs System.Net.Mail for its MailMessage class - but you could write your own
private void Send(IEnumerable<Customer> customers, string body)
{
foreach(Customer customer in customers)
Send(customer, body);
}
private void Send(Customer customer, string body)
{
MailMessage message = GenerateMessage(customer, body);
// your code for sending/retrying; omitted to keep the code short
}
private MailMessage GenerateMessage(Customer customer, string body)
{
string subject = "...";
string from = "...";
string to = customer.Email;
MailMessage retVal = new MailMessage(from, to, subject, body);
return retVal;
}
好的,现在看看单元测试图片。突然间,您可以在不发送电子邮件的情况下测试电子邮件的生成。只需创建一个虚拟客户,将其传递给GenerateMessage函数,并验证它传回的结果(甚至不发送电子邮件。)
至于电子邮件本身?那有点难了。你有两个选择。选项#1只是生成一个虚拟客户,其中包含您的个人/组的电子邮件地址,并让它实际上继续并向您发送电子邮件。不理想,但它会确保电子邮件代码有效。但另一种选择是修改Sender类(或者如果你不控制它,则包装它) - 类似于:
interface ISender
{
void Send(/* the args you've got for your send function */);
}
class Sender : ISender
{
void Send(/* args */) { /* code */ }
}
class DummySender : ISender
{
void Send(/* args */)
{
// code to validate the unit tests of Send() are passing in correctly (and you won't have it actually send anything.)
}
}
...然后,不是直接调用'Sender.Send',而是传递您计划用于执行发送的ISender。常规代码将传入Sender的实例;你的单元测试将传递给DummySender的一个实例。
编辑(帮助提供新信息)
那部分与IMailSender?这实际上是完美的。
首先,您不是直接挂接到'Sender',而是将IMailSender对象作为附加参数传递给Send()函数。然后你可以写下这样的东西:
public class UnitTestDummySender : IMailSender
{
public string fromAddressIShouldGet;
public string toAddressIShouldGet;
public string bodyIShouldGet;
public void Send(string from, string to, string body)
{
// check to make sure the arguments passed in match those properties
}
}
看看这是如何工作的?而不是只调用Send(),你可以这样做:
// your regular code:
Send(custList, body, myNormalSenderClass);
// your unit test code:
UnitTestDummySender faker = new UnitTestDummySender();
// lines to set the faker properties to be what the email should work out to be
Send(myFakeCustList, body, faker);
希望有所帮助! : - )