Изучаю 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 как пройденный или проваленный - в списке тестов будет отображаться
сообщение, которое указано параметром к этому методу.