Вроде, но не совсем хорошо. Причина в том, что переменные, объявленные в блоке in{}, не видны в блоке out{}. (Были некоторые обсуждения об изменении этого, чтобы он мог проверять состояние до и после, создавая копию в блоке in, но ничего не было реализовано.)
Итак, это не сработает:
void foo()
in { auto before = Clock.currTime(); }
out { assert(Clock.currTime - before < dur!"msecs"(20)); }
body { ... }
Переменная из in не будет перенесена в out, что приведет к ошибке неопределенного идентификатора. Но я говорю «вроде как», потому что есть потенциальный обходной путь:
import std.datetime;
struct Foo {
SysTime test_before;
void test()
in {
test_before = Clock.currTime();
}
out {
assert(Clock.currTime - test_before < dur!"msecs"(20));
}
body {
}
}
Объявление переменной как обычного члена структуры. Но это будет означать множество бесполезных переменных для каждой функции, не будет работать с рекурсией и просто загрязнит пространство имен членов.
Часть меня думает, что вы могли бы сделать свой собственный стек в стороне и заставить {} подталкивать время, а затем {} извлекать его и проверять.... но быстрый тест показывает, что он может сломаться, как только наследование получит вовлеченный. Если вы будете повторять блок in{} каждый раз, это может сработать. Но это кажется мне ужасно хрупким. Правило контрактного наследования состоит в том, что ВСЕ блоки out{} дерева наследования должны пройти, но только ОДИН из блоков in{} должен пройти. Таким образом, если бы у вас был другой вход {} вниз по цепочке, он мог бы забыть установить время, а затем, когда выход попытался бы вытолкнуть его, ваш стек опустился бы.
// just for experimenting.....
SysTime[] timeStack; // WARNING: use a real stack here in production, a plain array will waste a *lot* of time reallocating as you push and pop on to it
class Foo {
void test()
in {
timeStack ~= Clock.currTime();
}
out {
auto start = timeStack[$-1];
timeStack = timeStack[0 .. $-1];
assert(Clock.currTime - start < dur!"msecs"(20));
import std.stdio;
// making sure the stack length is still sane
writeln("stack length ", timeStack.length);
}
body { }
}
class Bar : Foo {
override void test()
in {
// had to repeat the in block on the child class for this to work at all
timeStack ~= Clock.currTime();
}
body {
import core.thread;
Thread.sleep(10.msecs); // bump that up to force a failure, ensuring the test is actually run
}
}
Кажется, это работает, но я думаю, что это больше проблем, чем того стоит. Я ожидаю, что это каким-то образом сломается, когда программа станет больше, и если ваш тест сломает вашу программу, это как бы противоречит цели.
Я бы, вероятно, сделал это как unittest{}, если только проверка с явными тестами удовлетворяет вашим требованиям (однако обратите внимание, что контракты, как и большинство утверждений в D, удаляются, если вы компилируете с ключом -release, поэтому они не будут на самом деле проверяйте и в версиях выпуска.Если вам нужен надежный сбой, создайте исключение, а не утверждение, поскольку это всегда будет работать в режимах отладки и выпуска.).
Или вы можете сделать это с помощью утверждения в функции, вспомогательной структуры или чего-то еще, как в C++. Я бы использовал защиту прицела:
void test() {
auto before = Clock.currTime();
scope(exit) assert(Clock.currTime - before < dur!"msecs"(20)); // or import std.exception; and use enforce instead of assert if you want it in release builds too
/* write the rest of your function */
}
Конечно, здесь вам придется копировать его и в подклассах, но похоже, что вам все равно придется делать это с блоками in{}, так что да, и, по крайней мере, переменная before является локальной.
В итоге я бы сказал, что вам, вероятно, лучше всего делать это более или менее так же, как вы делали это на С++.
person
Adam D. Ruppe
schedule
22.10.2013