Тестирование -

философия современного разработчика

TDD

BDD

ATDD

Unit Tests

Functional  Tests

Acceptance Tests

White Box vs Black Box 

Unit Tests / Модульные Тесты

  • Arrange - подготовка

  • Act - выполнение​

  • Assert - проверка​

Пример

public class СalculatorTests {
    @Test
    public void multiplicationOfZeroIntegersShouldReturnZero() {
        // Калькулятор, который тестируется
        Calculator сalculator = new Calculator();
        // проверки
        assertEquals(
                0,
                сalculator.multiply(10, 0),
                "10 x 0 must be 0"
        );
        assertEquals(
                0,
                сalculator.multiply(0, 10),
                 "0 x 10 must be 0"
        );
    }
}

GWT

  • Given - начальное состояние системы

  • When - действия пользователя

  • Then - результат

Functional Tests / Функциональные Тесты

<?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 11

Integration Tests / Интеграционные Тесты

  • Acceptance

  • Functional

  • Unit

ATDD
BDD
TDD

=

Methodology

TDD

Тест падает

Тест проходит

Рефакторим

BDD

Спецификация

Разработка

Коммуникация

ATDD

Разработка Тестов

Разработка

Спецификация

BDD vs ATDD

T это ключ

Acceptance

Functional

Integration

Unit

Не Писать Тесты

Не можем мы

их продать!!!!!

Писать Тесты

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"
        );
    }
}

Псевдокод

Пример из Yii2


    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"
        );
    }
}

Писать Избыточные Тесты

Писать "Для Галочки"

Насяльника

Я сделаль!


    public function testDo() {
        $model = new User();

        $this->assertTrue($model->do());

        $this->assertFalse($model->do());
    }

Псевдокод

Писать Тесты "Для Себя"

Отсутствие Информативности


    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);
    }

При Ошибке Нас Обрадуют Сообщения

Магические Числа

    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

Спешит На Помощь

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);
    }

Информативность Отчета Об Ошибках

Описание Сценария

И Шагов

$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');

Константы

Для Магических Цифр

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');

Вопросы?

Тестирование - философия современного разработчика

By Dima Kolodko

Тестирование - философия современного разработчика

  • 549