Изучаю NUnit. Часть 2
В прошлый раз разобрали самые простые варианты
реализации тестов с использованием фреймворка NUnit.
Сегодня остановлюсь на несколько более сложных примерах с использованием
стандартных атрибутов NUnit:
SetUp,
ExpectedException и
Ignore. В существующий
проект добавим класс Account
: [code lang=CSharp] public class Account {
private decimal balance; public void Deposit(decimal amount) { balance +=
amount; } public void Withdraw(decimal amount) { balance -= amount; } public
void TransferFunds(Account destination, decimal amount) { } public decimal
Balance { get { return balance; } } } [/code] Напишем первый тест. Для этого
добавим класс AccountTest
: [code lang=CSharp] [TestFixture] public class
AccountTest { [Test] public void TransferFunds() { Account source = new
Account(); source.Deposit(200m); Account destination = new Account();
destination.Deposit(150m); source.TransferFunds(destination, 100m);
Assert.AreEqual(250m, destination.Balance); Assert.AreEqual(100m,
source.Balance); } } [/code] Атрибут TestFixture
означает, что класс
содержит тесты. Класс AccountTest
должен быть public и содержать конструктор
по-умолчанию. Единственный метод класса TransferFunds
помечен атрибутом
Test
. Данным атрибутом помечаются все тестовые случаи. Тестовый метод должен
иметь тип void и не должен принимать параметры. В нашем примере мы
инициализируем тестовые объекты, выполняем бизнес-логику и проверяем состояние
тестового объекта. Класс Assert
содержит множество методов для проверки
результатов работы. В нашем тесте воспользуемся методом AreEqual
для того,
чтобы проверить, что счета, которые участвовали в транзакции имеют корректный
баланс. Сохраним проект. Далее необходимо выполнить вновь созданный тест в
меню Test | Run | All Tests либо комбинацией Ctrl + R + A
. Внезапно получили
сообщение: [code lang=CSharp] TransferFunds : expected <250> but was
<150> [/code] Это говорит о том, что в код закралась ошибка. Очевидно,
проблема лишь в том, что метод TransferFunds
не реализован. Исправим это и
запустим тесты еще раз: [code lang=CSharp] public void TransferFunds(Account
destination, decimal amount) { destination.Deposit(amount); Withdraw(amount);
} [/code] На этот раз тест проходит успешно. Добавим в класс Account
чуть
больше логики: пусть у каждого счета есть минимальный возможный баланс. [code
lang=CSharp] private decimal minimumBalance = 10m; public decimal
MinimumBalance { get{ return minimumBalance; } } [/code] Создадим исключение,
которое будет выдаваться, если на счете не достаточно средств, чтобы совершить
транзакцию: [code lang=CSharp] public class InsufficientFundsException :
ApplicationException { } [/code] Добавим новый тест в наш класс AccountTest
:
[code lang=CSharp] [Test]
[ExpectedException(typeof(InsufficientFundsException))] public void
TransferWithInsufficientFunds() { Account source = new Account();
source.Deposit(200m); Account destination = new Account();
destination.Deposit(150m); source.TransferFunds(destination, 300m); } [/code]
Атрибут ExpectedException
, которым помечен новый тест, говорит о том, что
этот тест будет считаться пройденным успешно, если в ходе его выполнения будет
сгенерировано исключение типа InsufficientFundsException
. В очередной раз
сохраним проект и выполним тесты. Новый тест завершился с ошибкой
TransferWithInsufficentFunds : InsufficientFundsException was expected
-
исключения не было. Исправим метод TransferFunds
для того, чтобы учитывались
новые требования про минимально допустимый баланс: [code lang=CSharp] public
void TransferFunds(Account destination, decimal amount) {
destination.Deposit(amount); if(balance - amount < minimumBalance) throw
new InsufficientFundsException(); Withdraw(amount); } [/code] Еще раз запустим
наши тесты. В этот раз все должно завершиться удачно. Если нет, то, вероятно,
вы где-то совершили ошибку. Если присмотреться внимательнее к коду выше, то
можно заметить, что на каждой транзакции банк будет терять деньги, если она
завершится неудачей. Напишем тест, который проверит данное утверждение: [code
lang=CSharp] [Test] public void TransferWithInsufficientFundsAtomicity() {
Account source = new Account(); source.Deposit(200m); Account destination =
new Account(); destination.Deposit(150m); try {
source.TransferFunds(destination, 300m); } catch(InsufficientFundsException
expected) { } Assert.AreEqual(200m, source.Balance); Assert.AreEqual(150m,
destination.Balance); } [/code] Как и ожидали, тест упал на проверке
Assert.AreEqual(150m, destination.Balance);
с сообщением о том, что
фактическое значение destination.Balance
- 450, а ожидаемое - 150. Исправим
метод так, чтобы данный тест проходил. Для этого необходимо проверять баланс
счетов до выполнения транзакции: [code lang=CSharp] public void
TransferFunds(Account destination, decimal amount) { if(balance - amount <
minimumBalance) throw new InsufficientFundsException();
destination.Deposit(amount); Withdraw(amount); } [/code] Теперь все тесты
проходят успешно. Однако, стоит задуматься, о методе Withdraw
. Что будет,
если этот метод выдаст какое-то другое исключение? Об этом будем думать позже,
а сейчас добавим заглушку для тестирования метода Withdraw
: [code
lang=CSharp] [Test] [Ignore(“Decide how to implement transaction management”)]
// пропустить тест public void TransferWithInsufficientFundsAtomicity() { //
будут написаны тысячи строк кода здесь } [/code] Атрибут Ignore
говорит о
том, что данный тест будет игнорироваться и не будет помечаться в Test
Explorer как пройденный или проваленный - в списке тестов будет отображаться
сообщение, которое указано параметром к этому методу.