CoreCamp "Automated testing basics for developers"
1. Automated testing basics for
developers
Bohdan Pashkovskyi
Senior .NET Software Engineer at Perfectial
2. Agenda
Ivano Frankivsk 2018
Що таке тестування і яким воно буває?
Unit tests vs Integration tests
Dependency Injection
Inversion of Control
Якості хороших unit тестів
Fakes (stubs + mocks)
TDD + BDD
3. Що таке тестування?
2 + 2 = ?
Ivano Frankivsk 2018
Тестування програмного забезпечення — процес перевірки на
відповідність між реальною та очікуваною поведінкою програми …
5. Яким буває тестування?
Ivano Frankivsk 2018
Нефункціональне
тестування
Тестування пов’язане зі змінамиФункціональне тестування
Модульне
Інтеграційне
Системне
Приймальне
Тестування
продуктивності
Відмовостійкості
Установки
Зручності
Регресійне
На дим
Санітарне
6. Agenda
Ivano Frankivsk 2018
Що таке тестування і яким воно буває?
Unit tests vs Integration tests
Dependency Injection
Inversion of Control
Якості хороших unit тестів
Fakes (stubs + mocks)
TDD + BDD
7. Unit test (модульний тест)
Це код (зазвичай, метод), який викликає інший код (production) і
після цього перевіряє правильність деяких припущень
Ivano Frankivsk 2018
8. Unit тести
• Тестують один модуль
• Виконуються виключно в пам’яті
• Не вимагають конфігурації
• Не вимагають DB, FS, AD, Net
• Завжди
Повторювано проходять
Або повторювано не проходять
Тому що не залежать від змінних факторів
Ivano Frankivsk 2018
9. Інтеграційні тести
Тестують модулі разом
Можуть мати різну поведінку, в залежності від
• Середовища (FS, DB, AD, OS, .config)
• Порядку виконання
• Кількості виконання
• Багато поточності
• Повного місяця
Ivano Frankivsk 2018
11. Приклад
модульного
тесту
Ivano Frankivsk 2018
[TestClass]
public class OrderTests
{
[TestMethod]
public void OrderApproval_ShouldChangeOrderStatusToApproved()
{
var order = new Order();
order.Approve();
Assert.AreEqual(OrderStatus.Approved, order.Status);
}
}
12. A A A
Ivano Frankivsk 2018
[TestClass]
public class OrderTests
{
[TestMethod]
public void OrderApproval_ShouldChangeOrderStatusToApproved()
{
// Arrange
var order = new Order();
// Act
order.Approve();
// Assert
Assert.AreEqual(OrderStatus.Approved, order.Status);
}
}
13. Unit tests frameworks
• Виконання тестів
• Інтеграція з IDE
• API для написання тестів
• Автоматизація
• Перегляд результатів
NUnit, MS Test, Xunit, MBUnit, DBUnit
Test runners:
Visual Studio, NUnit GUI/Console apps, ReSharper
Ivano Frankivsk 2018
15. Agenda
Ivano Frankivsk 2018
Що таке тестування і яким воно буває?
Unit tests vs Integration tests
Dependency Injection
Inversion of Control
Якості хороших unit тестів
Fakes (stubs + mocks)
TDD + BDD
16. Dependency
Ivano Frankivsk 2018
public class OrderManager
{
public void ApproveOrder(Order orderToApprove)
{
orderToApprove.Approve();
// dependency
var mailSender = new MailSender();
mailSender.Send("[email protected]", "Order was approved");
}
}
17. Dependency injection
DI – це процес надання зовнішньої залежності програмному
компоненту. Є специфічною формою «інверсії керування»
(Inversion of Control, IoC)
Розрізняють дві основні форми DI (взагалі-то три )
- ін’єкція у конструктор (constructor injection)
- ін’єкція у властивість (setter/property injection)
Ivano Frankivsk 2018
18. DI крок 1
абстракція
Ivano Frankivsk 2018
public interface IMailSender
{
void Send(string address, string message);
}
public class MailSender : IMailSender
{
public void Send(string email, string message)
{ ... }
}
19. Constructor
injection
Ivano Frankivsk 2018
class OrderManager
{
private readonly IMailSender _mailSender;
public OrderManager(IMailSender mailSender)
{
_mailSender = mailSender;
}
public void ApproveOrder(Order orderToApprove)
{
orderToApprove.Approve();
_mailSender.Send("[email protected]", "Order was approved");
}
}
21. Setter
injection
Ivano Frankivsk 2018
class OrderManager
{
public IMailSender MailSender { get; set; }
public void ApproveOrder(Order orderToApprove)
{
orderToApprove.Approve();
MailSender.Send("[email protected]", "Order was approved");
}
}
22. Setter
injection
Ivano Frankivsk 2018
class OrderManager
{
public IMailSender MailSender { get; set; }
public void ApproveOrder(Order orderToApprove)
{
orderToApprove.Approve();
MailSender.Send("[email protected]", "Order was approved");
}
}
23. Setter
injection
Ivano Frankivsk 2018
var mailSender = new MailSender();
var orderManager = new OrderManager();
orderManager.MailSender = mailSender;
orderManager.ApproveOrder(orderToApprove);
24. Agenda
Ivano Frankivsk 2018
Що таке тестування і яким воно буває?
Unit tests vs Integration tests
Dependency Injection
Inversion of Control
Якості хороших unit тестів
Fakes (stubs + mocks)
TDD + BDD
25. Inversion of Control
• Модулі вищого рівня не повинні залежати від модулів нижчого
рівня. Обидва типи модулів повинні залежати від абстракцій.
• Абстракції не повинні залежати від деталей реалізації. Деталі
реалізації повинні залежати від абстракцій.
Ivano Frankivsk 2018
27. IoC
Containers
Ivano Frankivsk 2018
public class IOCConfig
{
public static Container Container;
public static void Initialize()
{
Container = new Container(x =>
{
// constructor
x.For<IMailSender>().Use<MailSender>();
// setter
x.Policies.FillAllPropertiesOfType<IMailSender>().Use<MailSender>();
});
}
}
public class Test
{
public IMailSender MailSender = IOCConfig.Container.GetInstance<IMailSender>();
}
28. Agenda
Ivano Frankivsk 2018
Що таке тестування і яким воно буває?
Unit tests vs Integration tests
Dependency Injection
Inversion of Control
Якості хороших unit тестів
Fakes (stubs + mocks)
TDD + BDD
29. Stubs (заглушка)
Стаби (заглушки) використовуються для того, щоб не
використовувати реальні залежності (файлову систему, БД,
поштові серери, тощо).
Це дозволяє тестувати тільки необхідний компонент
Стабів у тесті може бути багато.
Ivano Frankivsk 2018
30. Dependency
Ivano Frankivsk 2018
public class OrderManager
{
public void ApproveOrder(Order orderToApprove)
{
orderToApprove.Approve();
// dependency
var mailSender = new MailSender();
mailSender.Send("[email protected]", "Order was approved");
}
}
31. DI крок 1
абстракція
Ivano Frankivsk 2018
public interface IMailSender
{
void Send(string address, string message);
}
public class MailSender : IMailSender
{
public void Send(string email, string message)
{ ... }
}
32. Stubs
Ivano Frankivsk 2018
public class StubMailSender : IMailSender
{
public void Send(string email, string message)
{
// do nothing
}
}
33. Stubs
Ivano Frankivsk 2018
[TestMethod]
public void OrderApproveMethod_ShouldChangeOrderStatusToApproved()
{
// Arrange
var order = new Order();
var orderManager = new OrderManager(new StubMailSender());
// Act
orderManager.ApproveOrder(order);
// Assert
Assert.AreEqual(OrderStatus.Approved, order.Status);
}
34. Isolation
frameworks
(Moq, RhinoMocks, etc)
Ivano Frankivsk 2018
[TestMethod]
public void OrderApproveMethod_ShouldChangeOrderStatusToApproved()
{
// Arrange
var order = new Order();
var stubMailSender = new Mock<IMailSender>();
var orderManager = new
OrderManager(stubMailSender.Object);
// Act
orderManager.ApproveOrder(order);
// Assert, все ще тестуємо стан
Assert.AreEqual(OrderStatus.Approved, order.Status);
}
35. Mocks
Mock використовуються для того, щоб протестувати взаємодію
залежностей.
Mock може бути лише один у тесті.
Один Assert = Один Verify
Ivano Frankivsk 2018
36. Mocks
Ivano Frankivsk 2018
[TestMethod]
public void OrderApproveMethod_ShouldChangeOrderStatusToApproved()
{
// Arrange
var order = new Order();
var mockMailSender = new Mock<IMailSender>();
mockMailSender.Setup(ms => ms.Send(
"[email protected]",
It.IsAny<string>()))
.Verifiable();
var orderManager = new OrderManager(mockMailSender.Object);
// Act
orderManager.ApproveOrder(order);
// Assert
mockMailSender.Verify();
}
37. Stubs +
mocks
(Один тест – один mock, але декілька stubs)
Ivano Frankivsk 2018
Fakes
Stubs 0..* Mocks 0..1
38. Agenda
Ivano Frankivsk 2018
Що таке тестування і яким воно буває?
Unit tests vs Integration tests
Dependency Injection
Inversion of Control
Якості хороших unit тестів
Fakes (stubs + mocks)
TDD + BDD
42. Readable
• Що відбувається в тесті
• Який код тестується
• Які передумови
• Які припущення перевіряються
• Що тестує тест
• Простий код тесту
Ivano Frankivsk 2018
43. Maintainable
• Тести легко реагують на зміни
• Не вимагають конфігурації
• Не залежать від інших тестів
• Простий код тесту
Ivano Frankivsk 2018
44. Trustworthy
• Релевантні до помилок
• Стабільно (не) проходять
• Немає конфліктуючих тестів
• Справді тестують
Ivano Frankivsk 2018
45. Agenda
Ivano Frankivsk 2018
Що таке тестування і яким воно буває?
Unit tests vs Integration tests
Dependency Injection
Inversion of Control
Якості хороших unit тестів
Fakes (stubs + mocks)
TDD + BDD
49. Speckflow
Feature example
Ivano Frankivsk 2018
Feature: Calculator_Add
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Background:
Given I have a calculator
Scenario: Add two numbers
Given I enter 50 into the calculator
And I press plus
And I enter 70 into the calculator
When I press enter
Then the result should be 120 be displayed
50. Speckflow
Bindings
Ivano Frankivsk 2018[Binding]
public class Calculator_SharedSteps
{
[Given(@"I have a calculator")]
public void GivenIHaveACalculator()
{
ScenarioContext.Current.Set<Calculator>(new Calculator());
}
[Given(@"I enter (.*) into the calculator")]
public void GivenIEnterIntoTheCalculator(int p0)
{
ScenarioContext.Current.Get<Calculator>().Number(p0);
}
[When(@"I press enter")]
public void WhenIPressEnter()
{
ScenarioContext.Current.Get<Calculator>().Enter();
}
[Then(@"the result should be (.*) be displayed")]
public void ThenTheResultShouldBeBeDisplayed(int p0)
{
Assert.AreEqual(Convert.ToString(p0),
ScenarioContext.Current.Get<Calculator>().Display);
}
}
51. Speckflow
Web example
Ivano Frankivsk 2018
Feature: Login page should give ability to Login or Register for
Background:
Given I am not logged in
And I am on "/account/login" page
Scenario: When I enter empty pass in Login form, I should see
error message
When I have filled out the form as follows
| Id | Value |
| Login_Password | |
And I press input type "submit" with value „Login"
Then I should see error „Required" for Login_Password
Scenario: If I am logged In a am redirected to account details
When I log in as user
And I go to "/account/login" page
Then I should be on page "/account/details"