Хрупкие тесты

whirlwind

TDD infected, paranoid
Не тестировать в базовом классе, а подготовить средства для тестирования общего функционала, что бы их тестирование занимало не больше строчки. Например прописать общие ассерты, создать нужные зависимые объекты.

-~{}~ 15.05.09 01:25:

ЗЫ. В этом случае если тесты сломаются, ты исправишь только разницу в тестах, а не все тесты.
 

Lightning

Трудоголик
Не тестировать в базовом классе, а подготовить средства для тестирования общего функционала, что бы их тестирование занимало не больше строчки. Например прописать общие ассерты, создать нужные зависимые объекты.
Я ж и говорю, что это когда тестовый код явно повторяется...
А если дублирование неявное.

Вот простейший сферический пример кода в вакууме:
PHP:
abstract class B {
    //...
    public function template_method(/* parameters */) {
        //...
        $substring1 = $this->get_substring1();
        //...
        $substring2 = $this->get_substring2();
        //...
        //$substring3 =  ... ;
        //...

        return $substring1.$substring2.$substring3;
    }

     abstract protected function get_substring1();

     abstract protected function get_substring2();

     //...
}

class A1 extends B {
     protected function get_substring1() {
          //...
     }

     protected function get_substring2() {
          //...
     }
}

class A2 extends B {
     protected function get_substring1() {
          //...
     }

     protected function get_substring2() {
          //...
     }
}

//...
В тестах классов A1 и A2, мы тестируем метод template_method, который в этих классах выдает различный результат. Если в классе B изменить template_method (например вместо $substring1.$substring2.$substring3 написать $substring2.$substring3.$substring1) то тесты классов A1 и A2 сломаются. Специальный класс, от которого наследуются тесты тут не поможет.
 

whirlwind

TDD infected, paranoid
Ну и? Тест тебе говорит что с кодом что то не то. Обоснуй зачем тут наследование? Я навскидку вижу два решения без наследования которые лишены описанных тобой недостатков при тестировании.
 

Lightning

Трудоголик
Обоснуй зачем тут наследование?
Это ж абстрактный пример. Естественно, что для того чтобы просто сложить строки не нужно городить Template method.
Это не важно.

Я хотел сказать, что может быть ситуация, когда при тестировании потомков, происходит повторное тестирование части родительского поведения, но при этом в коде тестов нет явного дублирования.
 

john.brown

просто кулибин
Не, Lightning, похоже, все же с кодом что то не так. В абстрактном примере суть не в том, что складывется строки, а в том, что родитель вызывает методы потомка, имхо. От этого и вся далнейщая трабла. Т.е. в твоем абстрактном примере получается, что ты не тестируеш классы А1, А2, а по сути всегда тестируеш класс В. Но наивно полагаеш, что тестируеш наследников :) Имхо, вот так ты решил бы данную проблему:
PHP:
class B {
	private $a;
	
	function __construct(InterfaceA $a) {
		$this->a = $a;
	}
	function template_method(/* parameters*/) {
		$substr1 = $this->a->getSubstr1();
		$substr2 = $this->a->getSubstr2();
		//... 
        //$substr3 =  ... ; 
        //...
        return $substr1.$substr2.$substr3;
	}
}

interface InterfaceA {
	function getSubstr1(); 

    function getSubstr2();
....
}
class A1 implements InterfaceA {
	...
}
 

Lightning

Трудоголик
john.brown
А если методы потомка еще при этом вызывают методы родителя, которые в свою очередь вызывают методы потомка? Тогда городить двунаправленую связь?
.е. в твоем абстрактном примере получается, что ты не тестируеш классы А1, А2, а по сути всегда тестируеш класс В. Но наивно полагаеш, что тестируеш наследников
Все правильно. Представь, что в методе Template_method находиться сложный алгоритм. Логично тестировать именно то, как отработает этот алгоритм, если я в потомках перегружу примитивные операции getSubstr1() и getSubstr2(), а не сами эти примитивные операции.
Кроме того вызывающий код не должен знать, какие классы использует класс A1, т.к. это черный ящик, т.е:
$a = new A1();
а не
$a = new B (new A1());

Конечно, наверное, любое наследование можно как-то обойти, но это не всегда правильно.
 

john.brown

просто кулибин
И чего там городить в двунаправленной свяязи? Пару строчек кода... Но если у тебя так бурно общаються поромок с предком, то возникает аодозрение, что напрашиваеться еще какая не то сущность...

любое наследование можно как-то обойти, но это не всегда правильно.
В твоем случае, имхо, правильно, ибо ерунда с тестированием получается.
Ты бы все же кусок реального кода выложил, а то такй черезмерно теоретический разговор получается...
 

Lightning

Трудоголик
Ты бы все же кусок реального кода выложил, а то такй черезмерно теоретический разговор получается...
Ну это ж вопрос по теории программирования :)
Да и нельзя на форум много кода постить.
В твоем случае, имхо, правильно, ибо ерунда с тестированием получается.
Подстройка кода под тестирование - это хорошо?
 

john.brown

просто кулибин
Если у тебя разработка основана на тестах (типа TDD), то однозначно хорошо. К тому же, траблы с тестами все же реально указывает на неудачные архитектурные решения.
 
Сверху