hey guys
general consensus
I've done quite a lot of reading up on the subject of testing complex classes and private methods.
The general consensus seems to be:
- "if you need to test private methods then you're class is badly designed"
- "if your class is complex, then you need to separate it out"
So, I need your help.
the problem Class
So I have a relatively simple class whose long running job it is to:
- poll a datasource
- do some very simple mapping of the data
- send that data somewhere else
Aditionally:
- it needs to be able to be quite fault tolerant by being able to retry various tasks in case of certain errors.
the testing problem
The point of the class is to abstract a lot of the fault tolerance and threading... basically by using a simple Timer Class and some internal lists to keep track of errors etc.
Because of the Timer, certain methods are called on different threads asynchronously... additionally a bunch of the methods rely on global private fields.
How should I test this class... particularly because so many methods are private?
cheers guys
UPDATE:
would love your input, here's the class, cheers!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Timers;
using AutoMapper;
using Castle.Core.Logging;
using NServiceBus;
using VioAd.Metadata.SyncService.Messages;
using Viomedia.Common.Extensions;
using Viomedia.Common.Extensions.FluentnHibernate;
using Viomedia.Core.Services;
using Viomedia.Messages.DTO;
using Timer = System.Timers.Timer;
namespace Viomedia.DataSync.Server
{
public class Retry
{
public Exception error { get; set; }
public int? PageNumber { get; set; }
}
public class VioadPollingUserPublisher
{
private readonly IBus _bus;
private readonly ILogger _logger;
private readonly IUserService _userService;
private readonly string _targetQueue;
private Timer _timer;
public bool processing { get; private set; }
public DateTime currentProcessDate { get; private set; }
public int retryTimes { get; set;}
public TimeSpan timeoutWait { get; set; }
public int PageSize { get; set; }
public VioadPollingUserPublisher(IBus bus, ILogger logger, IUserService userService, string targetQueue)
{
_bus = bus;
_logger = logger;
_userService = userService;
_targetQueue = targetQueue;
processing = false;
PageSize = 500;
retryTimes = 10;
timeoutWait = TimeSpan.FromMinutes(1);
logger.Info("{0} created:{1}target queue: {2}{1}page size: {3}{1}retry times: {4}{1}timeout error pause: {5} in seconds.",
GetType().Name,
Environment.NewLine,
_targetQueue,
PageSize,
retryTimes,
timeoutWait.TotalSeconds);
}
public void StartPolling(DateTime fromDate, TimeSpan Interval)
{
try
{
currentProcessDate = fromDate;
_timer = new Timer { Interval = Interval.TotalMilliseconds };
_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
_timer.Start();
_logger.Info("Started Polling at every {0} seconds. First Date: {1}", Interval.TotalSeconds, fromDate.ToString());
}
catch (Exception ex)
{
_logger.Fatal("Failed to start Poller.",ex);
StopPolling();
throw;
}
}
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (!processing)
{
processing = true;
int totalUsers = (int)_userService.GetTotalCount();
DateTime? newdate;
List<Retry> retries = GetAndSendUsers(null, out newdate);
for (int i = 1; i < retryTimes; i++)
{
retries = GetAndSendUsers(retries, out newdate);
if (retries.Count == 0)
{
break;
}
}
foreach (Retry retry in retries)
{
_logger.Fatal(string.Format("Retry failed for date {0} page {1}", currentProcessDate, retry.PageNumber), retry.error);
}
_bus.Send(_targetQueue, new DataMessage()
{
TotalCount = totalUsers,
Users = new List<CmsUser>()
});
if (newdate.HasValue)
{
currentProcessDate = newdate.Value;
}
processing = false;
}
}
private List<Retry> GetAndSendUsers(List<Retry> toRetry, out DateTime? lastDate)
{
var newRetries = new List<Retry>();
lastDate = currentProcessDate;
int Retried = 0;
try
{
bool isRetry = (toRetry != null && toRetry.Count > 0);
var pagedQueryModel = new PagedQueryModel
{
SortOrder = "asc",
PageSize = PageSize,
PageNumber = 1,
SortColumn = "Updated"
};
do
{
try
{
if((isRetry && (toRetry.Where(r=>r.PageNumber == pagedQueryModel.PageNumber).Count() > 0)) || !isRetry)
{
if(isRetry)
{
Retried++;
}
var users = _userService.ListPaged(currentProcessDate, pagedQueryModel);
if (users.Items.Count == 0)
{
if(!isRetry || (isRetry && Retried > toRetry.Count))
{
break;
}
}
else
{
_bus.Send(_targetQueue, new DataMessage()
{
TotalCount = 0,
Users = Mapper.Map<List<UserDTO>, List<CmsUser>>(users.Items.ToList())
});
lastDate = users.Items.Last().Updated;
}
}
}
catch (Exception ex)
{
_logger.Error(string.Format("Error while getting/sending users for date {0} page {1}. Is Retry {2}", currentProcessDate, pagedQueryModel.PageNumber, isRetry), ex);
newRetries.Add(new Retry() {error = ex, PageNumber = pagedQueryModel.PageNumber });
LookForTimeoutsAndWait(ex);
}
pagedQueryModel.PageNumber++;
} while (true);
return newRetries;
}
catch (Exception ex)
{
_logger.Fatal(string.Format("Failed poll/send for update date {0}", currentProcessDate), ex);
throw;
}
}
private void LookForTimeoutsAndWait(Exception possibleTimeout)
{
Exception timeout = possibleTimeout.ContainsAnywhere("timeout");
_logger.Warn(string.Format("Timeout error found, pausing for {0} seconds",timeoutWait.TotalSeconds),timeout);
if(timeout != null)
{
Thread.Sleep(timeoutWait);
}
}
public void StopPolling()
{
_timer.Stop();
processing = false;
}
}
}