Планируемые статьи:
- Основы unit тестирования.
- Классическая модель (Конструкция Assert с множеством статических методов)
- Constraint модель (Конструкция Assert.That)
- Атрибуты( [SetUp],[Test] и другие)
Не буду вникать в подробности подключения и запуска тестов NUnit. Об этом можно почитать в приложении [2], [3] и [5]. Далее по тексту предполагается что Вы подключили NUnit и он заработал.
Дабы не тестировать класс Class1 тестом Test1 в качестве примера возьмем класс для управления двигателем. Опишем исходные условия:
У двигателя можно читать текущее состояние направления вращения (left, right, stopped) и скорость вращения (0 – 10), останавливать и запускать двигатель с указанием направления вращения, а так же увеличивать и уменьшать скорость. Если не остановить двигатель и сменить ему направление вращения то создается исключение. Исходные условия описаны приступим к программированию. Создадим консольный проект MotorExample. В проекте MotorExample добавляем два класса MotorTest и Motor. Оба класса помечаем модификатором public. Что бы не задумываться об области видимости классов. Для описания направления вращения создадим перечисление. Создадим так же интерфейс IMotor, который позволит одинаково управлять различными двигателями и наследуем от него наш класс Motor. Visual Studio попросим для интерфейса IMotor создать заглушки. Класс MotorTest помечаем атрибутом TestFixture. Описание атрибутов Nunit отложим. Скажу лишь что сейчас мы задействуем еще два атрибута Test и SetUp.
Для простоты примера класс с тестами MotorTest и разрабатываемый класс Motor находятся в одном проекте. Обычно я их разношу в разные проекты что бы файла тестов и файлы проекта не путались, а в проект тестов добавляю ссылку на тестируемый проект.
В результате вот что у нас должно получиться:public enum DirectionOfRotation { Stopped, Right, Left }
interface IMotor { int Speed { get; } DirectionOfRotation CurrentDirection { get; } void Start(DirectionOfRotation senseOfRotation); void Stop(); void SpeedUp(int increment); void SpeedDown(int increment); }
public class Motor:IMotor { public int Speed { get { throw new NotImplementedException(); } } public DirectionOfRotation CurrentDirection { get { throw new NotImplementedException(); } } public void Start(DirectionOfRotation senseOfRotation) { throw new NotImplementedException(); } public void Stop() { throw new NotImplementedException(); } public void SpeedUp(int increment) { throw new NotImplementedException(); } public void SpeedDown(int increment) { throw new NotImplementedException(); } }
[TestFixture] public class MotorTest { }
NUnit поддерживает две модели проверок на основе Assert и на основе Constraint. Пока не будем вдаваться в подробности скажу лишь что разработчики рекомендуют пользоваться Constraint моделью из-за большей гибкости. И начиная с версии 2.4 тесты основанные на классической модели проверок, являются лишь оберткой Constraint модели для совместимости со старыми версиями. Для тестирования конструктора приведу синтаксис классической и constraint модели, далее будем использовать только новую constraint модель.
Motor motor; [SetUp] public void Init() { motor = new Motor(); } [Test] public void TestConstructorClassic() { Assert.AreEqual(0, motor.Speed); Assert.IsInstanceOf<DirectionOfRotation>(motor.CurrentDirection); Assert.AreEqual(DirectionOfRotation.Stopped, motor.CurrentDirection); } [Test] public void TestConstructorConstraint() { Assert.That(motor.Speed, Is.EqualTo(0)); Assert.That(motor.CurrentDirection, Is.TypeOf<DirectionOfRotation>()); Assert.That(motor.CurrentDirection, Is.EqualTo(DirectionOfRotation.Stopped)); }
MotorTest.TestConstructorClassic : Failed
System.NotImplementedException : Метод или операция не реализована.
Тесты провалились потому что мы еще ничего не реализовали в нашем классе Motor. Для этого давайте создадим конструктор, две private переменных и проинициализируем их. А так же реализуем свойства Speed и CurrentDirection.System.NotImplementedException : Метод или операция не реализована.
private int _currentSpeed; private DirectionOfRotation directionOfRotation; public Motor () { _currentSpeed = 0; directionOfRotation = DirectionOfRotation.Stopped; } public int Speed { get { return _currentSpeed; } } public DirectionOfRotation CurrentDirection { get { return directionOfRotation; } }
TestConstructorClassic, Success
TestConstructorConstraint, Success
Тесты конструктора проходят, теперь давайте подумаем как должен работать тест метода Start. Метод Start задает направление вращения и минимальную скорость, а так же создает исключение InvalidOperationException(), если двигатель был не остановлен. Опишем это в тесте:TestConstructorConstraint, Success
[Test] public void TestStart() { motor.Start(DirectionOfRotation.Right); //Проверка min скорости Assert.That(motor.Speed, Is.Not.EqualTo(0)); //Проверка на генерацию исключения Assert.That(()=>motor.Start(DirectionOfRotation.Left), Throws.InvalidOperationException); }
public void Start(DirectionOfRotation senseOfRotation) { directionOfRotation = senseOfRotation; _currentSpeed = 1; }
MotorTest.TestStart : Failed
Expected: <System.InvalidOperationException>
But was: no exception thrown
Тест не проходит из-за того что не реализовано формирование исключения. Реализуем формирование исключения.Expected: <System.InvalidOperationException>
But was: no exception thrown
public void Start(DirectionOfRotation senseOfRotation) { if (directionOfRotation == DirectionOfRotation.Stopped) { directionOfRotation = senseOfRotation; _currentSpeed = 1; } else { throw new InvalidOperationException(); } }
TestConstructorClassic, Success
TestConstructorConstraint, Success
TestStart, Success
Аналогично реализуем тесты и методы, оставшиеся еще не реализованными. Посмотреть код можно в прилагаемом проекте.TestConstructorConstraint, Success
TestStart, Success
Ссылка на проект с тестами.