Php testing - пишем тесты на PHP Unit

Опубликовано 2021.08.22

Написание тестов - очень важная часть любого проекта. Если проект большой и сложный-тесты не раз вас спасут.

В чём смысл.

У вас есть какой-то функционал, который работает по определённому алгоритму, который не должен меняться. Тест проверяет, что функционал работает согласно заложенному алгоритму. Если кто-то изменит алгоритм-тест уведомит об этом.

Пример.

Есть бизнес требование "по трём входящим числам найти наибольшее, наименьшее и их сумму".

Решение:

  1. class Values {
  2. function getMax($a, $b, $c) {
  3. return max([$a, $b, $c]);
  4. }
  5.  
  6. function getMin($a, $b, $c) {
  7. return min([$a, $b, $c]);
  8. }
  9.  
  10. function getSum($a, $b, $c) {
  11. return array_sum([$a, $b, $c]);
  12. }
  13. }

Вроде бы всё очевидно и код полностью рабочий. Проверьте сами:

  1. $values = new Values();
  2. echo $values->getMin(5, 45, 2) . '<br />';
  3. echo $values->getMax(55, 122, -7) . '<br />';
  4. echo $values->getSum(5, 45, 0) . '<br />';

Для того чтобы запустить unit test - нужно добавить его библиотеку в composer.json:

  1. {
  2. "name": "root/project",
  3. "description": "description",
  4. "minimum-stability": "stable",
  5. "license": "proprietary",
  6. "authors": [
  7. {
  8. "name": "admin",
  9. "email": "email@example.com"
  10. }
  11. ],
  12. "require": {
  13. "phpunit/phpunit": "5.4.*"
  14. }
  15. }

Далее делаем composer install

Тест для таких функций будет выглядеть вот так, поместим его в файл ValuesTest.php:

  1. include_once "../vendor/autoload.php";
  2. include_once "../file.php"; // в этом файле хранится класс Values
  3.  
  4. use PHPUnit\Framework\TestCase;
  5.  
  6. class ValuesTest extends TestCase
  7. {
  8. /**
  9.   * @dataProvider additionMaxProvider
  10.   */
  11. public function testGetMax($a, $b, $c, $max)
  12. {
  13. $values = new Values();
  14. $result_max = $values->getMax($a, $b, $c);
  15. $this->assertEquals($result_max, $max);
  16. }
  17.  
  18. /**
  19.   * @dataProvider additionMinProvider
  20.   */
  21. public function testGetMin($a, $b, $c, $min)
  22. {
  23. $values = new Values();
  24. $result_min = $values->getMin($a, $b, $c);
  25. $this->assertEquals($result_min, $min);
  26. }
  27.  
  28. /**
  29.   * @dataProvider additionSumProvider
  30.   */
  31. public function testGetSum($a, $b, $c, $sum)
  32. {
  33. $values = new Values();
  34. $result_sum = $values->getSum($a, $b, $c);
  35. $this->assertEquals($result_sum, $sum);
  36. }
  37.  
  38. // Ниже функции, которые вернут аргументы для методов тестирования.
  39. // По их названиям вы поймёте что к чему.
  40. // Первые три числа в каждом массиве - это аргументы, а последний - это правильный результат
  41.  
  42. public function additionMaxProvider()
  43. {
  44. return [
  45. [2, 1, 5, 5],
  46. [0, 1, 1, 1],
  47. [0, 0, 0, 0],
  48. [1, -61, 3, 3]
  49. ];
  50. }
  51.  
  52. public function additionMinProvider()
  53. {
  54. return [
  55. [2, 1, 5, 1],
  56. [0, 1, 1, 0],
  57. [0, 0, 0, 0],
  58. [1, -61, 3, -61]
  59. ];
  60. }
  61.  
  62. public function additionSumProvider()
  63. {
  64. return [
  65. [2, 1, 5, 8],
  66. [0, 1, 1, 2],
  67. [0, 0, 0, 0],
  68. [1, -61, 3, -57]
  69. ];
  70. }
  71. }

Попробуем запустить тесты. Переходим в папку с файлом ValuesTest.php и пишем команду:

  1. phpunit ValuesTest.php

Если вы всё сделали верно - результат будет таким:

  1. PHPUnit 5.4.8 by Sebastian Bergmann and contributors.
  2.  
  3. ............ 12 / 12 (100%)
  4.  
  5. Time: 52 ms, Memory: 4.00MB
  6.  
  7. OK (12 tests, 12 assertions)

12 тестов - это по 4 прогона с разными данными на 3 теста.

А теперь представьте такие ситуации:

  1. $values = new Values();
  2. echo $values->getMin(5, 45, "car") . '<br />';
  3. echo $values->getMin(5, [55, 4], 2) . '<br />';

Ошибки не будет, так как php умеет приводить типы к нужному. Результат будет "car" и 2.

Но мы всё же обработаем такие ситуации, так как согласно бизнес логике мы обрабатываем только числа.

Давайте условимся, что все три аргумента должны быть числовыми, иначе кидаем эксепшн.

Добавим метод checkArguments для валидации. Вынесем её в четвёртый метод и будем вызывать в наших трёх.

  1. class Values {
  2. function checkArguments($a, $b, $c) {
  3. foreach([$a, $b, $c] as $val) {
  4. if (!is_int($val)) {
  5. throw new Exception('Arguments is not integers');
  6. }
  7. }
  8. }
  9.  
  10. function getMax($a, $b, $c) {
  11. $this->checkArguments($a, $b, $c);
  12. return max([$a, $b, $c]);
  13. }
  14.  
  15. function getMin($a, $b, $c) {
  16. $this->checkArguments($a, $b, $c);
  17. return min([$a, $b, $c]);
  18. }
  19.  
  20. function getSum($a, $b, $c) {
  21. $this->checkArguments($a, $b, $c);
  22. return array_sum([$a, $b, $c]);
  23. }
  24. }

Проверяем.

Я получил ошибку:

  1. PHP Fatal error: Uncaught Exception: Arguments is not integers in /var/www/project/file.php:7

Как видим - всё работает. Добавим проверку в тестах.

Создадим методы для формирования некорректных данных:

  1. public function additionMaxProviderNegative()
  2. {
  3. return [
  4. [2, 1, "car"],
  5. [0, 10.11, 1],
  6. [0, [], 0],
  7. [1, '', '']
  8. ];
  9. }
  10.  
  11. public function additionMinProviderNegative()
  12. {
  13. return [
  14. [2, 1, "car"],
  15. [0, 10.11, 1],
  16. [0, [], 0],
  17. [1, '', '']
  18. ];
  19. }
  20.  
  21. public function additionSumProviderNegative()
  22. {
  23. return [
  24. [2, 1, "car"],
  25. [0, 10.11, 1],
  26. [0, [], 0],
  27. [1, '', '']
  28. ];
  29. }

Теперь создадим 3 новых метода для негативных сценариев. Просто добавим им аннотацию @expectedException Exception

Это означает, что для успешного прохождения теста в нём должно быть выброшено исключение типа Exception.

  1. /**
  2. * @dataProvider additionMaxProviderNegative
  3. * @expectedException Exception
  4. */
  5. public function testGetMaxNegative($a, $b, $c)
  6. {
  7. $values = new Values();
  8. $values->getMax($a, $b, $c);
  9. }
  10.  
  11. /**
  12. * @dataProvider additionMinProviderNegative
  13. * @expectedException Exception
  14. */
  15. public function testGetMinNegative($a, $b, $c)
  16. {
  17. $values = new Values();
  18. $values->getMin($a, $b, $c);
  19. }
  20.  
  21. /**
  22. * @dataProvider additionSumProviderNegative
  23. * @expectedException Exception
  24. */
  25. public function testGetSumNegative($a, $b, $c)
  26. {
  27. $values = new Values();
  28. $values->getSum($a, $b, $c);
  29. }

Запускаем тесты и видим, что как позитивные, так и негативные сценарии работают:

  1. PHPUnit 5.4.8 by Sebastian Bergmann and contributors.
  2.  
  3. ........................ 24 / 24 (100%)
  4.  
  5. Time: 44 ms, Memory: 4.00MB
  6.  
  7. OK (24 tests, 24 assertions)

Теперь пробуйте закомментировать exception в методе checkArguments и убедитесь, что тесты упадут.

Полный класс теста:

  1. include_once "../vendor/autoload.php";
  2. include_once "../file.php";
  3.  
  4. use PHPUnit\Framework\TestCase;
  5.  
  6. class ValuesTest extends TestCase
  7. {
  8. /**
  9.   * @dataProvider additionMaxProvider
  10.   */
  11. public function testGetMax($a, $b, $c, $max)
  12. {
  13. $values = new Values();
  14. $result_max = $values->getMax($a, $b, $c);
  15. $this->assertEquals($result_max, $max);
  16. }
  17.  
  18. /**
  19.   * @dataProvider additionMinProvider
  20.   */
  21. public function testGetMin($a, $b, $c, $min)
  22. {
  23. $values = new Values();
  24. $result_min = $values->getMin($a, $b, $c);
  25. $this->assertEquals($result_min, $min);
  26. }
  27.  
  28. /**
  29.   * @dataProvider additionSumProvider
  30.   */
  31. public function testGetSum($a, $b, $c, $sum)
  32. {
  33. $values = new Values();
  34. $result_sum = $values->getSum($a, $b, $c);
  35. $this->assertEquals($result_sum, $sum);
  36. }
  37. /**
  38.   * @dataProvider additionMaxProviderNegative
  39.   * @expectedException Exception
  40.   */
  41. public function testGetMaxNegative($a, $b, $c)
  42. {
  43. $values = new Values();
  44. $values->getMax($a, $b, $c);
  45. }
  46.  
  47. /**
  48.   * @dataProvider additionMinProviderNegative
  49.   * @expectedException Exception
  50.   */
  51. public function testGetMinNegative($a, $b, $c)
  52. {
  53. $values = new Values();
  54. $values->getMin($a, $b, $c);
  55. }
  56.  
  57. /**
  58.   * @dataProvider additionSumProviderNegative
  59.   * @expectedException Exception
  60.   */
  61. public function testGetSumNegative($a, $b, $c)
  62. {
  63. $values = new Values();
  64. $values->getSum($a, $b, $c);
  65. }
  66.  
  67. public function additionMaxProvider()
  68. {
  69. return [
  70. [2, 1, 5, 5],
  71. [0, 1, 1, 1],
  72. [0, 0, 0, 0],
  73. [1, -61, 3, 3]
  74. ];
  75. }
  76.  
  77. public function additionMinProvider()
  78. {
  79. return [
  80. [2, 1, 5, 1],
  81. [0, 1, 1, 0],
  82. [0, 0, 0, 0],
  83. [1, -61, 3, -61]
  84. ];
  85. }
  86.  
  87. public function additionSumProvider()
  88. {
  89. return [
  90. [2, 1, 5, 8],
  91. [0, 1, 1, 2],
  92. [0, 0, 0, 0],
  93. [1, -61, 3, -57]
  94. ];
  95. }
  96.  
  97. public function additionMaxProviderNegative()
  98. {
  99. return [
  100. [2, 1, "car"],
  101. [0, 10.11, 1],
  102. [0, [], 0],
  103. [1, '', '']
  104. ];
  105. }
  106.  
  107. public function additionMinProviderNegative()
  108. {
  109. return [
  110. [2, 1, "car"],
  111. [0, 10.11, 1],
  112. [0, [], 0],
  113. [1, '', '']
  114. ];
  115. }
  116.  
  117. public function additionSumProviderNegative()
  118. {
  119. return [
  120. [2, 1, "car"],
  121. [0, 10.11, 1],
  122. [0, [], 0],
  123. [1, '', '']
  124. ];
  125. }
  126. }