Testing - philosophy of a modern developer

TDD

BDD
ATDD
Unit Tests
Functional Tests
Acceptance Tests

White Box vs Black Box
Unit Tests = AAA
-
Arrange
-
Act
-
Assert
Example
public class СalculatorTests {
@Test
public void multiplicationOfZeroIntegersShouldReturnZero() {
// Calculator, being tested
Calculator сalculator = new Calculator();
// asserts
assertEquals(
0,
сalculator.multiply(10, 0),
"10 x 0 must be 0"
);
assertEquals(
0,
сalculator.multiply(0, 10),
"0 x 10 must be 0"
);
}
}Functional Tests = GWT
-
Given - ititial app state
-
When - actions of a user
-
Then - result
Example
<?php
$I->amOnPage('/');
$I->click('Login');
$I->fillField('Username', 'Miles');
$I->fillField('Password', 'Davis');
$I->click('Enter');
$I->see('Hello, Miles', 'h1');
Acceptance Tests
Feature: Basic Arithmetic
Background: A Calculator
Given a calculator, I just turned on
Scenario: Addition
When I add 4 and 5
Then the result is 9
Scenario: Another Addition
When I add 4 and 7
Then the result is 11Integration Tests
-
Acceptance
-
Functional
-
Unit
ATDD
BDD
TDD
=
Methodology
TDD

Test fails
Test succeed
Refactoring
BDD
Specification
Development
Communication
ATDD
Test Development
Development
Specification
BDD vs ATDD
T is the key
Acceptance
Functional
Integration
Unit

Problems?
What problems?
Not To Write Tests


We can't sell
them!!!!
To Write Tests

public class UserTests extends ActiveRecord {
String name;
String lastName;
}
public class UserTests {
@Test
public void saveUser() {
User user = new User();
user.name = "John";
user.lastName = "Dow";
assertTrue(
user.save(),
"user must be saved"
);
}
}Pseudocode
Example From Yii2 Source Code
public function testConstructor()
{
$config = [
'on' => ['a' => 'b'],
'joinWith' => ['dummy relation'],
];
$query = new ActiveQuery(Customer::className(), $config);
$this->assertEquals($query->modelClass, Customer::className());
$this->assertEquals($query->on, $config['on']);
$this->assertEquals($query->joinWith, $config['joinWith']);
}public class User extends ActiveRecord {
String name;
String lastName;
Integer status;
public boolean isActive() {
return status == 1;
}
}
public class UserTests {
@Test
public void saveUser() {
User user = User.findOne(1);
assertTrue(
user.isActive(),
"user must be active"
);
}
}To Write Redundant Tests
Write Just "For The Record"

Chief
Job done I!
public function testDo() {
$model = new User();
$this->assertTrue($model->do());
$this->assertFalse($model->do());
}Pseudocode
Write Tests "For Yourself"

Lack Of Informativity
public function testSaveLoad()
{
static::$time = static::$filemtime = \time();
$this->prepareData();
$items = $this->auth->items;
$children = $this->auth->children;
$assignments = $this->auth->assignments;
$rules = $this->auth->rules;
$this->auth->save();
$this->auth = $this->createManager();
$this->auth->load();
$this->assertEquals($items, $this->auth->items);
$this->assertEquals($children, $this->auth->children);
$this->assertEquals($assignments, $this->auth->assignments);
$this->assertEquals($rules, $this->auth->rules);
}Yii2 Rocks
public function testInitControllerNamePluralization() {
$suites = $this->getTestsForControllerNamePluralization();
foreach ($suites as $i => $suite) {
list($name, $tests) = $suite;
foreach ($tests as $j => $test) {
list($config, $expected) = $test;
$rule = new UrlRule($config);
$this->assertEquals($expected, $rule->controller, "Test#$i-$j: $name");
}
}
}
public function testRenderWithCustomInputId() {
$expectedValue = <<<EOD
<div class="form-group field-custom-input-id">
<label class="control-label" for="custom-input-id">Attribute Name</label>
<input type="text" id="custom-input-id" class="form-control" name="ActiveFieldTestModel[{$this->attributeName}]">
<div class="hint-block">Hint for attributeName attribute</div>
<div class="help-block"></div>
</div>
EOD;
$this->activeField->inputOptions['id'] = 'custom-input-id';
$actualValue = $this->activeField->render();
$this->assertEqualsWithoutLE($expectedValue, $actualValue);
}

Error Messages
Would Make Us Happy
Magic Numbers
public function testFindScalar()
{
/* @var $customerClass \yii\db\ActiveRecordInterface */
$customerClass = $this->getCustomerClass();
/* @var $this TestCase|ActiveRecordTestTrait */
// query scalar
$customerName = $customerClass::find()->where(['id' => 2])->scalar('name');
$this->assertEquals('user2', $customerName);
$customerName = $customerClass::find()->where(['status' => 2])->scalar('name');
$this->assertEquals('user3', $customerName);
$customerName = $customerClass::find()->where(['status' => 2])->scalar('noname');
$this->assertNull($customerName);
$customerId = $customerClass::find()->where(['status' => 2])->scalar('id');
$this->assertEquals(3, $customerId);
}Code Specs
Saving The Day

class IncomeCalculatorTest extends Specification {
private const EXPECTED_TAX_FOR_FIRST_LEVEL_TAX_RULE = 4500;
private const EXPECTED_TAX_FOR_SECOND_LEVEL_TAX_RULE = 7200;
private const EXPECTED_TAX_FOR_THIRD_LEVEL_TAX_RULE = 30000;
private const INCOME_AFTER_APPLYING_FIRST_LEVEL_TAX_RULE = 300000;
/**
* @test
*/
public function calculateTaxSpec() {
$clientsPayments = []; // dummy variable, just for example
$hoursSpentWorking = 160; // dummy variable, just for example
$service = new IncomeCalculator($clientsPayments, $hoursSpentWorking);
$I = $this->tester;
$I->describe('income tax calculations');
$I->expectThat('for income less that 50 000 calculator use 10% tax rule');
$I->lookAt('first level income tax');
$I->seeNumber($service->calculateTax())
->isNotEmpty()
->isEqualTo(self::EXPECTED_TAX_FOR_FIRST_LEVEL_TAX_RULE);
}
} /**
* @test
*/
public function calculateWithTaxSpec() {
$clientsPayments = []; // dummy variable, just for example
$hoursSpentWorking = 160; // dummy variable, just for example
$service = new IncomeCalculator($clientsPayments, $hoursSpentWorking);
$I = $this->tester;
$I->describe('income calculation');
$I->lookAt('income tax');
$I->expectThat(
'calculator calculates income with tax
using 10% tax rule
for income less that 50 000'
);
$I->seeNumber($service->calculateWithTax())
->isNotEmpty()
->isEqualTo(self::INCOME_AFTER_APPLYING_FIRST_LEVEL_TAX_RULE);
}Error Report Informativity

Scenario And Step Description
$I->describe('income tax calculations');
$I->expectThat('for income less that 50 000 calculator use 10% tax rule');
$I->expectThat('for income between 50 000 and 100 000 calculator use 12% tax rule');
$I->expectThat('for income more than 100 000 calculator use 20% tax rule');Constants For Magic Numbers
private const EXPECTED_TAX_FOR_FIRST_LEVEL_TAX_RULE = 4500;
private const EXPECTED_TAX_FOR_SECOND_LEVEL_TAX_RULE = 7200;
private const EXPECTED_TAX_FOR_THIRD_LEVEL_TAX_RULE = 30000;
private const INCOME_AFTER_APPLYING_FIRST_LEVEL_TAX_RULE = 300000;
.................
->isEqualTo(self::EXPECTED_TAX_FOR_FIRST_LEVEL_TAX_RULE);
->isEqualTo(self::EXPECTED_TAX_FOR_SECOND_LEVEL_TAX_RULE);
->isEqualTo(self::EXPECTED_TAX_FOR_THIRD_LEVEL_TAX_RULE);
->isEqualTo(self::INCOME_AFTER_APPLYING_FIRST_LEVEL_TAX_RULE);DSL
$I->lookAt('third level income tax');
$I->seeNumber('income tax', $service->calculateTax())
->isNotEmpty()
->isEqualTo(self::EXPECTED_TAX_FOR_THIRD_LEVEL_TAX_RULE);
$I->seeBool(true)
->isFalse();
$I->lookAt('user fetched from DB');
$I->seeArray(['name' => 'John'])
->isNotEmpty()
->hasKey('name')
->hasKey('lastName');Questions?
Testing - philosophy of a modern developer
By Dima Kolodko
Testing - philosophy of a modern developer
- 489