5. TDD? ЧТО И ОТКУДА?
• Пошло из XP: test-first
• Реже используешь отладчик
6. TDD? ЧТО И ОТКУДА?
• Пошло из XP: test-first
• Реже используешь отладчик
• Дизайн еще до реализации
7. TDD? ЧТО И ОТКУДА?
• Пошло из XP: test-first
• Реже используешь отладчик
• Дизайн еще до реализации
• Код менее связанный
8. TDD? ЧТО И ОТКУДА?
• Пошло из XP: test-first
• Реже используешь отладчик
• Дизайн еще до реализации
• Код менее связанный
• Нет сложной инициализации
9. TDD? ЧТО И ОТКУДА?
• Пошло из XP: test-first
• Реже используешь отладчик
• Дизайн еще до реализации
• Код менее связанный
• Нет сложной инициализации
• Интерфейсы четкие и небольшие
10. TDD? ЧТО И ОТКУДА?
• Пошло из XP: test-first
• Реже используешь отладчик
• Дизайн еще до реализации
• Код менее связанный
• Нет сложной инициализации
• Интерфейсы четкие и небольшие
• Способствует модульному мышлению
!
12. TDD? ЧТО И ОТКУДА?
• Нужно привыкнуть
• На старте может потребовать больше времени
13. TDD? ЧТО И ОТКУДА?
• Нужно привыкнуть
• На старте может потребовать больше времени
• Может возникнуть ложное чувство безопасности
14. TDD? ЧТО И ОТКУДА?
• Нужно привыкнуть
• На старте может потребовать больше времени
• Может возникнуть ложное чувство безопасности
• Плохо написанный тест - источник накладных расходов!
15. ПЛАН
• Что не так?
• Как хочется?
• Примеры
• Примеры
• Примеры
• Практика
26. •Медленные
•Хрупкие
•Что еще хуже - Random’но хрупкие
•Дорогие по деньгам
•Что еще хуже - по времени
•Хрен поймешь - что тестируют?
•Писать и разбираться в них - одна боль и
страдание
45. Запросы:
что-то возвращают и ничего не меняют
Команды:
ничего не возвращают и что-то меняют
Избавляйтесь от запросов, которые ведут
себя как команды
i
46. ЧТО И КАК ТЕСТИРОВАТЬ
Запросы Команды
Входящие
Самому себе
Исходящие
48. ВХОДЯЩИЕ ЗАПРОСЫ
private class Aggregator {!
private readonly int[] _args;!
public Aggregator(params int[] args) {!
_args = args;!
}!
public int Sum {!
get { return _args.Sum(); }!
}!
}!
!
49. ВХОДЯЩИЕ ЗАПРОСЫ
private class Aggregator {!
private readonly int[] _args;!
public Aggregator(params int[] args) {!
_args = args;!
}!
public int Sum {!
get { return _args.Sum(); }!
}!
}!
!
[TestFixture]!
public class AggregatorTest {!
[Test]!
public void Sum() {!
var a = new Aggregator(1, 2, 3);!
Assert.AreEqual(6, a.Sum);!
}!
}!
51. ВХОДЯЩИЕ ЗАПРОСЫ
!
private class MyAverage {!
public const int MAGIC_COEF = 2;!
private readonly int[] _args;!
public MyAverage(params int[] args) {!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
private int[] ModifyArgs {!
get { return _args.Select(a => a * MAGIC_COEF).ToArray(); }!
}!
}!
!
52. ВХОДЯЩИЕ ЗАПРОСЫ
!
private class MyAverage {!
public const int MAGIC_COEF = 2;!
private readonly int[] _args;!
public MyAverage(params int[] args) {!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
private int[] ModifyArgs {!
get { return _args.Select(a => a * MAGIC_COEF).ToArray(); }!
}!
}!
!
[TestFixture]!
public class MyAverageTest {!
[Test]!
public void Calc() {!
var avg = new MyAverage(10, 20);!
Assert.AreEqual(30, avg.Calc());!
// Но, там же еще всего так много: MAGIC_COEF, ModifyArgs.!
}!
}!
54. ВХОДЯЩИЕ КОМАНДЫ
!
private class MyAverage2 : MyAverage {!
private int _coef;!
!
public void SetCoef(int value) {!
_coef = value;!
}!
!
public int CurrentMagicCoef { get { return _coef + MAGIC_COEF; } }!
}!
!
55. ВХОДЯЩИЕ КОМАНДЫ
!
private class MyAverage2 : MyAverage {!
private int _coef;!
!
public void SetCoef(int value) {!
_coef = value;!
}!
!
public int CurrentMagicCoef { get { return _coef + MAGIC_COEF; } }!
}!
!
[TestFixture]!
public class MyAverage2Test {!
[Test]!
public void SetCoef() {!
var avg = new MyAverage2();!
avg.SetCoef(1);!
Assert.AreEqual(3, avg.CurrentMagicCoef);!
}!
}!
59. ЗАПРОСЫ К СЕБЕ
!
private class MyAverage4 {!
public const int MAGIC_COEF = 2;!
private readonly int[] _args;!
public MyAverage4(params int[] args) {!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
internal int[] ModifyArgs {!
get { return _args.Select(a => a * MAGIC_COEF).ToArray(); }!
}!
}!
!
!
60. ЗАПРОСЫ К СЕБЕ
!
private class MyAverage4 {!
public const int MAGIC_COEF = 2;!
private readonly int[] _args;!
public MyAverage4(params int[] args) {!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
internal int[] ModifyArgs {!
get { return _args.Select(a => a * MAGIC_COEF).ToArray(); }!
}!
}!
!
!
61. ЗАПРОСЫ К СЕБЕ
!
private class MyAverage4 {!
public const int MAGIC_COEF = 2;!
private readonly int[] _args;!
public MyAverage4(params int[] args) {!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
internal int[] ModifyArgs {!
get { return _args.Select(a => a * MAGIC_COEF).ToArray(); }!
}!
}!
!
[TestFixture]!
public class MyAverage4Test {!
[Test]!
public void ModifyArgs() {!
var avg = new MyAverage4(10);!
Assert.AreEqual(20, avg.ModifyArgs);!
}!
}!
62. ЗАПРОСЫ К СЕБЕ
!
private class MyAverage4 {!
public const int MAGIC_COEF = 2;!
private readonly int[] _args;!
public MyAverage4(params int[] args) {!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
internal int[] ModifyArgs {!
get { return _args.Select(a => a * MAGIC_COEF).ToArray(); }!
}!
}!
!
[TestFixture]!
public class MyAverage4Test {!
[Test]!
public void ModifyArgs() {!
var avg = new MyAverage4(10);!
Assert.AreEqual(20, avg.ModifyArgs);!
}!
}!
ТАК ДЕЛАТЬ НЕ НАДО
63. КОМАНДЫ К СЕБЕ
!
private class MyAverage5 {!
private readonly int[] _args;!
internal Aggregator Aggregator;!
public MyAverage5(params int[] args) {!
_args = args;!
}!
public int Calc() {!
InitAggregator()!
return Aggregator.Sum / _args.Length;!
}!
private void InitAggregator() {!
Aggregator = new Aggregator(_args);!
}!
}!
64. КОМАНДЫ К СЕБЕ
!
private class MyAverage5 {!
private readonly int[] _args;!
internal Aggregator Aggregator;!
public MyAverage5(params int[] args) {!
_args = args;!
}!
public int Calc() {!
InitAggregator()!
return Aggregator.Sum / _args.Length;!
}!
private void InitAggregator() {!
Aggregator = new Aggregator(_args);!
}!
}!
!
[TestFixture]!
public class MyAverage5Test {!
[Test]!
public void Calc() {!
var avg = new MyAverage5(10, 20);!
Assert.AreEqual(15, avg.Calc());!
Assert.IsNotNull(avg.Aggregator);!
}!
}!
65. КОМАНДЫ К СЕБЕ
!
private class MyAverage5 {!
private readonly int[] _args;!
internal Aggregator Aggregator;!
public MyAverage5(params int[] args) {!
_args = args;!
}!
public int Calc() {!
InitAggregator()!
return Aggregator.Sum / _args.Length;!
}!
private void InitAggregator() {!
Aggregator = new Aggregator(_args);!
}!
}!
!
[TestFixture]!
public class MyAverage5Test {!
[Test]!
public void Calc() {!
var avg = new MyAverage5(10, 20);!
Assert.AreEqual(15, avg.Calc());!
Assert.IsNotNull(avg.Aggregator);!
}!
}!
Этому тут не место
66. КОМАНДЫ К СЕБЕ
!
private class MyAverage5 {!
private readonly int[] _args;!
internal Aggregator Aggregator;!
public MyAverage5(params int[] args) {!
_args = args;!
}!
public int Calc() {!
InitAggregator()!
return Aggregator.Sum / _args.Length;!
}!
private void InitAggregator() {!
Aggregator = new Aggregator(_args);!
}!
}!
!
[TestFixture]!
public class MyAverage5Test {!
[Test]!
public void Calc() {!
var avg = new MyAverage5(10, 20);!
Assert.AreEqual(15, avg.Calc());!
Assert.IsNotNull(avg.Aggregator);!
}!
}!
ТАК ДЕЛАТЬ НЕ НАДО
68. ЧТО И КАК ТЕСТИРОВАТЬ
Запросы Команды
Входящие
Проверка
результата
Проверка публичного,
побочного эффекта
Самому себе Игнорируем Игнорируем
Исходящие
70. ИСХОДЯЩИЕ ЗАПРОСЫ
!
public interface IMagicCoef {!
int Value { get; }!
}!
class MagicCoef : IMagicCoef {!
public int Value { !
get { return /* Find Magic Value... */ default(int); } }!
}!
!
private class MyAverage6 {!
private readonly IMagicCoef _magic;!
private readonly int[] _args;!
public MyAverage6(IMagicCoef magic = null, params int[] args) {!
_magic = magic ?? new MagicCoef();!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
private int[] ModifyArgs {!
get { return _args.Select(a => a * _magic.Value).ToArray(); }!
}!
}!
71. ИСХОДЯЩИЕ ЗАПРОСЫ
!
public interface IMagicCoef {!
int Value { get; }!
}!
class MagicCoef : IMagicCoef {!
public int Value { !
get { return /* Find Magic Value... */ default(int); } }!
}!
!
private class MyAverage6 {!
private readonly IMagicCoef _magic;!
private readonly int[] _args;!
public MyAverage6(IMagicCoef magic = null, params int[] args) {!
_magic = magic ?? new MagicCoef();!
_args = args;!
}!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
private int[] ModifyArgs {!
get { return _args.Select(a => a * _magic.Value).ToArray(); }!
}!
}!
72. ИСХОДЯЩИЕ ЗАПРОСЫ
!
class MagicCoef : IMagicCoef {!
public int Value { !
get { return /* Find Magic Value... */ default(int); } }!
}!
!
private class MyAverage6 {!
...!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
private int[] ModifyArgs {!
get { return _args.Select(a => a * _magic.Value).ToArray(); }!
}!
}!
!
[TestFixture]!
public class MyAverage6Test {!
[Test]!
public void CallMagic() {!
var mock = new Mock<IMagicCoef>();!
new MyAverage6(mock.Object, 1, 2).Calc();!
mock.Verify(m => m.Value, Times.Exactly(2));!
}!
}!
!
73. ИСХОДЯЩИЕ ЗАПРОСЫ
!
class MagicCoef : IMagicCoef {!
public int Value { !
get { return /* Find Magic Value... */ default(int); } }!
}!
!
private class MyAverage6 {!
...!
public int Calc() {!
return new Aggregator(ModifyArgs).Sum / _args.Length;!
}!
private int[] ModifyArgs {!
get { return _args.Select(a => a * _magic.Value).ToArray(); }!
}!
}!
!
[TestFixture]!
public class MyAverage6Test {!
[Test]!
public void CallMagic() {!
var mock = new Mock<IMagicCoef>();!
new MyAverage6(mock.Object, 1, 2).Calc();!
mock.Verify(m => m.Value, Times.Exactly(2));!
}!
}!
!
ТАК ДЕЛАТЬ НЕ НАДО
75. ИСХОДЯЩИЕ ЗАПРОСЫ
Правило 5
!
Не тестируйте исходящие запросы
- не проверяйте их результат
- не ожидайте их вызова
R
Проверка исходящих запросов добавляет
стоимости, но не приносит пользы
i
76. ИСХОДЯЩИЕ КОМАНДЫ!
!
private class Aggregator7 {!
private readonly ILogger _logger;!
private readonly int[] _args;!
public Aggregator7(ILogger logger = null, params int[] args) {!
_logger = logger ?? new LoggerByDb();!
_args = args;!
}!
!
public int Sum {!
get {!
var sum = _args.Sum();!
_logger.LogSum(sum);!
return sum;!
}!
}!
}!
!
public interface ILogger {!
void LogSum(int sum);!
}!
!
private class LoggerByDb : ILogger {!
public void LogSum(int sum) {!
// Log sum in data base...!
}!
}!
77. ИСХОДЯЩИЕ КОМАНДЫ!
!
private class Aggregator7 {!
private readonly ILogger _logger;!
private readonly int[] _args;!
public Aggregator7(ILogger logger = null, params int[] args) {!
_logger = logger ?? new LoggerByDb();!
_args = args;!
}!
!
public int Sum {!
get {!
var sum = _args.Sum();!
_logger.LogSum(sum);!
return sum;!
}!
}!
}!
!
public interface ILogger {!
void LogSum(int sum);!
}!
!
private class LoggerByDb : ILogger {!
public void LogSum(int sum) {!
// Log sum in data base...!
}!
}!
78. ИСХОДЯЩИЕ КОМАНДЫ!
[TestFixture]!
public class Aggregator7Test {!
[Test]!
public void LogTest() {!
var sum = new Aggregator7(new LoggerByDb(), 1, 2, 3).Sum;!
var logSum = GetLogsFromDb();!
Assert.AreEqual(sum, logSum);!
}!
!
private int GetLogsFromDb() {!
// select sum from logtable limit 1;!
return default (int);!
}!
}!
79. ИСХОДЯЩИЕ КОМАНДЫ!
[TestFixture]!
public class Aggregator7Test {!
[Test]!
public void LogTest() {!
var sum = new Aggregator7(new LoggerByDb(), 1, 2, 3).Sum;!
var logSum = GetLogsFromDb();!
Assert.AreEqual(sum, logSum);!
}!
!
private int GetLogsFromDb() {!
// select sum from logtable limit 1;!
return default (int);!
}!
}!
Это зависимость от «далеких» эффектов
и конкретной реализации.
Замедляет тесты.
i
80. ИСХОДЯЩИЕ КОМАНДЫ!
[TestFixture]!
public class Aggregator7Test {!
[Test]!
public void LogTest() {!
var sum = new Aggregator7(new LoggerByDb(), 1, 2, 3).Sum;!
var logSum = GetLogsFromDb();!
Assert.AreEqual(sum, logSum);!
}!
!
private int GetLogsFromDb() {!
// select sum from logtable limit 1;!
return default (int);!
}!
}!
Это зависимость от «далеких» эффектов
и конкретной реализации.
Замедляет тесты.
Главное - не имеет отношение к
ответственности класса.
i
81. ИСХОДЯЩИЕ КОМАНДЫ!
[TestFixture]!
public class Aggregator7Test {!
[Test]!
public void LogTest() {!
var sum = new Aggregator7(new LoggerByDb(), 1, 2, 3).Sum;!
var logSum = GetLogsFromDb();!
Assert.AreEqual(sum, logSum);!
}!
!
private int GetLogsFromDb() {!
// select sum from logtable limit 1;!
return default (int);!
}!
}!
Это зависимость от «далеких» эффектов
и конкретной реализации.
Замедляет тесты.
Главное - не имеет отношение к
ответственности класса.
i
ТАК ДЕЛАТЬ НЕ НАДО
82. ИСХОДЯЩИЕ КОМАНДЫ!
[TestFixture]!
public class Aggregator7Test {!
// Anti pattern!
[Test]!
public void BadLogTest() {!
var sum = new Aggregator7(new LoggerByDb(), 1, 2, 3).Sum;!
var logSum = GetLogsFromDb();!
Assert.AreEqual(sum, logSum);!
}!
!
private int GetLogsFromDb() {!
// select sum from logtable limit 1;!
return default (int);!
}!
!
// Good!
[Test]!
public void GoodLogTest() {!
var m = new Mock<ILogger>();!
var res = new Aggregator7(m.Object, 1, 2, 3).Sum;!
m.Verify(e => e.LogSum(6), Times.Once());!
}!
}!
83. ИСХОДЯЩИЕ КОМАНДЫ!
[TestFixture]!
public class Aggregator7Test {!
// Anti pattern!
[Test]!
public void BadLogTest() {!
var sum = new Aggregator7(new LoggerByDb(), 1, 2, 3).Sum;!
var logSum = GetLogsFromDb();!
Assert.AreEqual(sum, logSum);!
}!
!
private int GetLogsFromDb() {!
// select sum from logtable limit 1;!
return default (int);!
}!
!
// Good!
[Test]!
public void GoodLogTest() {!
var m = new Mock<ILogger>();!
var res = new Aggregator7(m.Object, 1, 2, 3).Sum;!
m.Verify(e => e.LogSum(6), Times.Once());!
}!
}!
Ответственность класса - отправить командуi
88. ИСХОДЯЩИЕ КОМАНДЫ
Что будет если интерфейс ILogger захочется
изменить?
БОЛЬ И СТРАДАНИЕ
Создавайте «тоникие» интерфейсы.
Соблюдайте контракты
i
89. ЧТО И КАК ТЕСТИРОВАТЬ
Запросы Команды
Входящие
Проверка
результата
Проверка публичного,
побочного эффекта
Самому себе Игнорируем Игнорируем
Исходящие Игнорируем Ожидание вызова