Ранее я видел хорошее решение проблемы тестирования на интерфейсах, когда вы создаете абстрактный тестовый класс для интерфейса, а затем наследуете его для теста для каждого класса, производного от этого интерфейса, используя * 1001. * метод для предоставления экземпляра данного класса. например,
public interface INamable {string Name{get;}}
[TestClass]
public abstract class INamableTests
{
protected abstract INamable CreateInstance(string name);
[TestMethod]
public void NameCorrectlyAssignedTest()
{
var expected = "this is my name";
var instance = CreateInstance(expected);
Assert.AreEqual(expected, instance.Name);
}
}
Однако, хотя вышеуказанный подход хорошо работает, когда наш класс реализует только один интерфейс, все становится немного сложнее, когда наследуется несколько интерфейсов. Мы можем обойти это, имея несколько тестовых классов, наследуемых от каждого абстрактного тестового класса для каждого интерфейса; но это решение работает только для одного уровня наследования. Т.е. если у нас есть другой подкласс в нашей иерархии наследования, нам нужно повторить себя / не получить преимущества от тестов, написанных для нашего базового класса.
Чтобы продемонстрировать, если мы добавим код в конце этого вопроса к приведенному выше, мы хотели бы проверить:
INamable \
IStartable |- IBatchJob - BatchJob - AutoStopBatchJob
IStoppable /
Но поскольку мы не можем наследовать от нескольких классов, наше тестовое наследование ограничено:
INamableTests - IBatchJobTests - BatchJobTests - AutoStopBatchJobTests
Таким образом, чтобы запустить IStartableTests
и IStoppableTests
для BatchJob
и AutoStopBatchJob
(и любой другой класс, использующий интерфейс IBatchJob
), нам нужно повторить код для вызова этих тестов.
Есть ли лучший подход для этого?
public interface IStartable {void Start();bool IsRunning{get;}}
public interface IStoppable {void Stop();bool IsRunning{get;}}
public interface IBatchJob: INamable, IStartable, IStoppable {}
public class BatchJob: IBatchJob
{
public string Name {get; private set;}
public bool IsRunning {get; private set;}
public BatchJob(string name) {Name = name;}
public void Start() {IsRunning = true;}
public void Stop() {IsRunning = false;}
}
public class AutoStopBatchJob: BatchJob
{
System.Timers.Timer batchTimer;
public AutoStopBatchJob(string name, TimeSpan maxRunTime): base (name)
{
batchTimer = new System.Timers.Timer();
batchTimer.AutoReset = false;
batchTimer.Interval = maxRunTime.TotalMilliseconds;
batchTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
}
public override void Start()
{
base.Start();
batchTimer.Start();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Stop();
}
}
[TestClass]
public abstract class IStartableTests
{
protected abstract IStartable CreateInstance();
[TestMethod]
public void StartSetsRunningToTrue()
{
var instance = CreateInstance();
Assert.AreEqual(false, instance.IsRunning);
instance.Start();
Assert.AreEqual(true, instance.IsRunning);
}
}
[TestClass]
public abstract class IBatchJobTests: INamableTests
{
public abstract IBatchJob CreateInstance(string name);
//I've extended INamablTests, so those will get run.
//However, how should I test IStartable and IStoppable?
}
[TestClass]
public class BatchJobTests: IBatchJobTests
{
public static BatchJob CreateInstance(string name) => new BatchJob(name);
public IBatchJob CreateInstance(string name) => BatchJobTests.CreateInstance(name);
//do I have to implement nested classes for these interfaces for every derived class?
[TestClass]
public class BaseClassIStartableTests : IStartableTests
{
protected IStartable CreateInstance() => BatchJobTests.CreateInstance("Default Name");
}
[TestClass]
public class BaseClassIStoppableTests : IStoppableTests
{
protected IStoppable CreateInstance() => BatchJobTests.CreateInstance("Default Name", initialRunningState:=true);
}
}