Jakiś czas temu miałem przyjemność pisać webową aplikację w stylu SPA.
Wybór technologii zakończył się na JavaScript + AngularJs + WebAPI.
Nic nowego, standardowy stos i aplikacja…
Jednakże, dla mnie było to pierwsze spotkanie z testami jednostkowymi w JavaScriptcie.
A dokładniej z frameworkiem: Jasmine
Jasmine zowie się frameworkiem testowym o smaku Behavior-Driven.
Wygląda to mniej więcej tak:
describe("Validator", function () {
var input;
var validator;
beforeEach(function () {
input = {};
validator = new Validator();
});
describe("when email do not exist", function () {
it("validation fails", function () {
expect(validator.validate(input)).toEqual({
isSuccess: false,
errorMessage: 'Missing email'
});
});
});
describe("when email exist", function () {
beforeEach(function () {
input.email = ''
});
describe("when email is empty", function () {
it("validation fails", function () {
expect(validator.validate(input)).toEqual({
isSuccess: false,
errorMessage: 'Empty email'
});
});
});
describe("when email has incorrect format", function () {
beforeEach(function () {
input.email = 'incorrect-format'
});
it("validation fails", function () {
expect(validator.validate(input)).toEqual({
isSuccess: false,
errorMessage: 'Incorrect email'
});
});
});
describe("when email has correct format", function () {
beforeEach(function () {
input.email = 'correct@email.com'
});
it("validation succeeds", function () {
expect(validator.validate(input)).toEqual({
isSuccess: true
});
});
});
});
});
A co w tym jest takiego ciekawego?
A no, pomysł na zagnieżdżanie testów.
Hierarchia w testach
Jeżeli mamy kod który posiada strukturę drzewiastą (np. walidacja) to często będziemy powtarzać etap przygotowania stanu przed samym testem.
Spójrzmy na standardowy przykład testów w C#:
public class ValidatorTests
{
private Validator _validator;
[SetUp]
public void SetUp()
{
_validator = new Validator();
}
[Test]
public void WhenEmailIsNull_ValidationFails()
{
string email = null;
var result = _validator.Validate(email);
Assert.IsFalse(result.IsSuccess);
}
[Test]
public void WhenEmailIsEmpty_ValidationFails()
{
string email = "";
var result = _validator.Validate(email);
Assert.IsFalse(result.IsSuccess);
}
[Test]
public void WhenEmailHasIncorrectFormat_ValidationFails()
{
string email = "Incorrect";
var result = _validator.Validate(email);
Assert.IsFalse(result.IsSuccess);
}
[Test]
public void WhenEmailHasCorrectFormat_ValidationSucceeds()
{
string email = "correct@email.com";
var result = _validator.Validate(email);
Assert.IsTrue(result.IsSuccess);
}
}
a teraz na wersję z zagnieżdżeniem (warto zwrócić uwagę na hierarchię dziedziczenia):
[TestFixture]
public class ValidatorTests
{
private string _email = null;
private Validator _validator;
[SetUp]
public void SetUp()
{
_validator = new Validator();
}
[TestFixture]
public class WhenEmailIsNull : ValidatorTests
{
[Test]
public void ValidationFails()
{
var result = _validator.Validate(_email);
Assert.IsFalse(result.IsSuccess);
}
}
[TestFixture]
public class WhenEmailIsEmpty : ValidatorTests
{
[SetUp]
public void SetUp()
{
_email = "";
}
[Test]
public void ValidationFails()
{
var result = _validator.Validate(_email);
Assert.IsFalse(result.IsSuccess);
}
}
[TestFixture]
public class WhenEmailIsNotEmpty : ValidatorTests
{
[TestFixture]
public class WhenEmailHasIncorrectFormat : WhenEmailIsNotEmpty
{
[SetUp]
public void SetUp()
{
_email = "incorrect";
}
[Test]
public void ValidationFails()
{
var result = _validator.Validate(_email);
Assert.IsFalse(result.IsSuccess);
}
}
[TestFixture]
public class WhenEmailHasCorrectFormat : WhenEmailIsNotEmpty
{
[SetUp]
public void SetUp()
{
_email = "correct@email.com";
}
[Test]
public void ValidationSucceeds()
{
var result = _validator.Validate(_email);
Assert.IsTrue(result.IsSuccess);
}
}
}
}
Porównajmy również raport z R#:
Bez hierarchiiZ hierarchią
Oczywiście, im więcej mamy rozgałęzień w logice oraz im bardziej skomplikowany kod w środku – tym więcej będziemy w stanie zmieścić w zagnieżdżonych setupach.
Kiedy stosować?
Metody setup zaczynają przybierać na liniach
Struktura kodu przypomina drzewo
Kiedy w metodach testów pojawiają się słowa typu: “when”, “then” itp
Kiedy chcemy mieć bardziej czytelne kolejne kroki algorytmów (w testach)
Inne przypadki
Testy integracyjne często układają się w drzewo-podobne struktury.
Np. w testach zapytań SQL – Join z kilku tabelek zwykle wygląda tak:
SELECT TableA.*, TableB.*, TableC.*
FROM TableA
JOIN TableB ON TableB.aID = TableA.aID
JOIN TableC ON TableC.cID = TableB.cID
raczej nie należy do najprzyjemniejszych, a próba jej przeczytania przez innego programistę prędzej skończy się na tępych odgłosach konsultacji głowa-biurko niż słowach afirmacji.
Be First to Comment