1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>TUT Design - TUT: C++ Unit Test Framework</title>
<meta name="keywords" content="C++ test-driven unit tests TUT framework STL">
<meta name="description" content="TUT Design - what's hidden under the hood of tut.h, and why things are such as they
are">
<style type="text/css">
<!--
body, p, td
{
scrollbar-base-color: #eeeeee;
scrollbar-track-color: #ffffff;
scrollbar-arrow-color: #cccccc;
scrollbar-shadow-color: #cccccc;
scrollbar-highlight-color: #cccccc;
scrollbar-darkshadow-color: #ffffff;
scrollbar-3dlight-color: #ffffff;
/*
scrollbar-base-color: #cccccc;
scrollbar-track-color: #ffffff;
scrollbar-arrow-color: #eeeeee;
*/
font-family : Verdana, Arial, Helvetica, sans-serif;
font-size : 14px;
color: #000000;
}
pre
{
font-family : Courier, sans-serif;
font-size : 12px;
}
strong, b
{
font-weight : bold;
}
ul
{
list-style-type : square;
}
.header
{
font-size : 24px;
font-weight : bold;
}
.subheader
{
font-size : 16px;
font-weight : bold;
}
.logo
{
font-size : 24px;
font-weight : bold;
}
.question
{
font-size : 16px;
font-weight : bold;
color : #000000;
}
a
{
font-weight : bold;
color : #ff9900;
text-decoration : none;
}
a:hover
{
font-weight : bold;
color : #ff9900;
text-decoration : underline;
}
a:visited
{
font-weight : bold;
color : #ff9900;
}
a.menu
{
font-weight : bold;
color : #ff9900;
text-decoration : none;
}
a.menu:hover
{
font-weight : bold;
color : #ff9900;
text-decoration : underline;
}
a.menu:visited
{
font-weight : bold;
color : #ff9900;
}
//-->
</style>
</head>
<body text="#000000" link="#ff9900" alink="#ffcc00" vlink="#ff9900" bgcolor="#ffffff" leftmargin=0 topmargin=0 marginheight=0 marginwidth=0>
<img src="/_img/pixel.gif" alt="" width=1 height=20><br>
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td width=20 align=left valign=top>
<img src="/_img/pixel.gif" alt="" width=20 height=1><br>
</td>
<td width="100%" align=left valign=top>
<!-- home table >> -->
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td width="100%" align=center valign=center bgcolor="#cccccc">
<!-- >> -->
<table width="100%" cellspacing=1 cellpadding=10>
<tr>
<td width="100%" align=left valign=top bgcolor="#eeeeee">
<p class="logo"><a href="/">TUT: C++ Unit Test Framework</a></p>
</td>
</tr>
</table>
<!-- << -->
</td>
</tr>
</table>
<!-- home table << -->
</td>
<td width=20 align=left valign=top>
<img src="/_img/pixel.gif" alt="" width=20 height=1><br>
</td>
</tr>
</table>
<table border=0 width="100%" cellspacing=20 cellpadding=0>
<tr>
<td width="30%" align=left valign=top>
<!-- menu table >> -->
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td width="100%" align=center valign=center bgcolor="#cccccc">
<!-- >> -->
<table width="100%" cellspacing=1 cellpadding=10>
<tr>
<td width="100%" align=left valign=top bgcolor="#eeeeee">
<p class="subheader">Documentation</p>
<ul>
<li><p><a href="/howto/" class="menu">TUT How-To</a><br>minimum steps to make TUT work for you</p></li>
<li><p><strong>TUT Design</strong><br>what's hidden under the hood of tut.h, and why things are such as they
are</p></li>
<li><p><a href="/example/" class="menu">TUT Usage Example</a><br>it's better to see once...</p></li>
<li><p><a href="/whole/" class="menu">TUT As Is</a><br>complete source of TUT</p></li>
</ul>
<p class="subheader">Distribution</p>
<ul>
<li><p><a href="/copyright/" class="menu">The TUT License</a><br>almost no restrictions to use</p></li>
<li><p><a href="/download/" class="menu">TUT Downloads</a><br>latest version of TUT as well as other related stuff</p></li>
</ul>
<p class="subheader">Support</p>
<ul>
<li><p><a href="/faq/" class="menu">TUT Frequently Asked Questions</a><br>often asked questions and answers for them</p></li>
<li><p><a href="/links/" class="menu">Links</a><br>related projects and concepts</p></li>
<li><p><a href="/author/" class="menu">TUT Author</a><br>who is the author</p></li>
</ul>
</td>
</tr>
</table>
<!-- << -->
</td>
</tr>
</table>
<!-- menu table << -->
</td>
<td width="70%" valign=top>
<!-- content table >> -->
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td width="100%" align=center valign=center bgcolor="#cccccc">
<!-- >> -->
<table width="100%" cellspacing=1 cellpadding=10>
<tr>
<td width="100%" align=left valign=top bgcolor="#eeeeee">
<p class="header">TUT Design</p>
<p>In this document I attempt to explain the decisions made while developing
TUT.</p>
<p class="subheader">Requirements</p>
<p> One day I ran into need of unit test framework for C++. So, I've made a small research
and discovered C++Unit, boost::test and a bunch of similar libraries.. Though
they were usable, I was not satisfied with the approach they offered; so I
designed my own variant of unit test framework based on the following
restrictions: </p>
<ul>
<li>No C-style macros</li>
<li>No manual registration for test groups and methods</li>
<li>No libraries of any kind</li>
<li>Neutrality to user interface</li>
<li>No Javisms</li>
</ul>
<p class="subheader">C-style macros and what's wrong with them</p>
<p> Usually C++ Unit test frameworks define a lot of macroses to achieve the goals
other languages have as built-in features: for example, Java is able to show you
the whole exception stack; and C++ cannot. So, to achieve the same (or similar)
results, C++ frameworks often declare a macro to catch any exception and trace
__FILE__ and __LINE__ variables. </p>
<p> The problem is that it turns the C++ code into something that is hard to read, where
"hard to read" actually means "hard to maintain". </p>
<p> Macros don't recognize namespace borders, so a simple macro can expand in the user
code into something unexpected. To avoid this, we have to give macros unique
prefixes, and this, in turn, reduces code readability even more. </p>
<p> From bad to worse, C-style macros can't handle modern C++ templates, so comma
separated template arguments will break the macro, since preprocessor will
handle the template as two arguments (separated by the comma used in the
template) to this macro. </p>
<p> And the final contra for macros is that even if used they cannot achieve the same
usability level as the native language tools; for example, macros cannot
generate a full stack trace (at least, in a platform-independent manner). So it
looks like we loose readability and get almost nothing for this. </p>
<p> See also Bjarne Stroustrup notices about macros harmness:
<a href="http://www.research.att.com/~bs/bs_faq2.html#macro"> So,
what's wrong with using macros?</a> </p>
<p class="subheader">Manual registration and why it annoys me</p>
<p> In JUnit (Java-based Unit Tests framework) reflection is used to recognize
user-written test methods. C++ has no reflection or similar mechanism, so user
must somehow tell the framework that "this, this and that" methods should be
considered as test methods, and others are just helpers for them. </p>
<p> The same can be said about test groups, which have to be registered in test runner
object. </p>
<p> Again, most C++ frameworks introduce macros or at least methods to register a
freestanding function or method as a test instance. I find writing redundant
code rather annoying: I have to write test itself and then I have to write more code
to mark that my test is a test. Even more, if I forget to register the method,
nothing will warn me or somehow indicate that I have not done what I should. </p>
<p class="subheader">Library and integration problems</p>
<p> Most of C++ frameworks require building a library that user must link to test
application to be able to run tests. The problem is that various platforms imply
different ways for building libraries. One popular C++ framework has more than
60% bugs in their bug database that sound like "cannot build library on platform
XXX" or "built library doesn't work on platform YYY". </p>
<p> Besides, some platforms has complexities in library memory management (e.g.
Win32). </p>
<p class="subheader">User interface</p>
<p> Some frameworks provide predefined formatters for output results, for example
CSV or XML. This restricts users in test results presentation options. What if a
user wants some completely different format? Of course, he can implement his own
formatter, but why frameworks provide useless formatters then? </p>
<p> The ideal test framework must do only one thing: run tests. Anything beyond that is
the responsibility of the user code. Framework provides the test results, and
the user code then represents them in any desired form. </p>
<p class="subheader">Javisms</p>
<p>Most implementors of C++ test frameworks know about JUnit and inspired by
this exciting tool. But, carelessly copying a Java implementation to C++, we can
get strange and ugly design.</p>
<p>Rather obvious example: JUnit has methods for setting up a test (setUp) and for
cleaning after it (tearDown). I know at least two C++ frameworks that have these
methods with the same semantics and names. But in C++ the job these methods do is
the responsibility of constructor and destructor! In Java we don't have
guaranteed destruction, so JUnit authors had to invent their own replacement
for it - tearDown(); and it was natural then to introduce constructing
counterpart - setUp(). Doing the same in C++ is absolutely redundant
</p>
<p>C++ has its own way of working, and whenever possible, I am going to stay at the C++
roads, and will not repeat Java implementation just because it is really good for
Java. </p>
<p class="subheader">Decisions</p>
<p class="subheader">No C-style macros</p>
<p>The solution is that simple: just
<b>do not</b> use any macros. I personally never needed a macro during
development. </p>
<p class="subheader">No manual registration</p>
<p>Since C++ has no reflection, the only way to mark a method as a test is to give it a kind
of predefined name.</p>
<p>There would be a simple solution: create a set of virtual methods in test object base
class, and allow user to overwrite them. The code might look like:</p>
<pre> struct a_test_group : public test_group { virtual void test1() { ... } virtual
void test2() { ... } }; </pre>
<p>Unfortunately, this approach has major drawbacks:</p>
<ul>
<li>It scales badly. Consider, we have created 100 virtual test methods in a test
group, but user needs 200. How can he achieve that? There is no proper way.
Frankly speaking, such a demand will arise rarely (mostly in
script-generated tests), but even the possibility of it makes this kind of
design seemingly poor. </li>
<li>There is no way to iterate virtual methods automatically. We
would end up writing code that calls test1(), then test2(), and so on, each
with its own exception handling and reporting.</li>
</ul>
<p>Another possible solution is to substitute reflection with a dynamic loading.
User then would write static functions with predefined names, and TUT would use
dlsym()/GetProcAddress() to find out the implemented tests. </p>
<p>But I rejected the solution due to its platform and library operations
dependencies. As I described above, the library operations are quite different
on various platform. </p>
<p>There was also an idea to have a small parser, that can scan the user code and
generate registration procedure. This solution only looks simple; parsing
free-standing user code can be a tricky procedure, and might easily overgrow twelve TUTs
in complexity.</p>
<p>Fortunately, modern C++ compilers already have a tool that can parse the user code
and iterate methods. It is compiler template processing engine. To be more
precise, it is template specialization technique.</p>
<p>The following code iterates all methods named test<N> ranging from n to 0,
and takes the address of each:</p>
<pre>
template <class Test,class Group,int n>
struct tests_registerer
{
static void reg(Group& group)
{
group.reg(n,&Test::template test<n>);
tests_registerer<Test,Group,n-1>::reg(group);
}
};
template<class Test,class Group>
struct tests_registerer<Test,Group,0>
{
static void reg(Group&){};
};
...
test_registerer<test,group,100>.reg(grp);
</pre>
<p>This code generates recursive template instantiations until it reaches
tests_registerer<Test,Group,0> which has empty specialization.
There the recursion stops.</p>
<p>The code is suitable for our needs because in the specialization preference is
given to the user-written code, not to the generic one. Suppose we have a default
method test<N> in our test group, and the user-written specialization
for test<1>. So while iterating, compiler will get the address of the
default method for all N, except 1, since user has supplied a special version of
that method. </p>
<pre>
template<int N>
void test() { };
...
template<>
void test_group::test<1>() { // user code here }
</pre>
<p>This approach can be regarded as kind of compile-time virtual functions, since
the user-written methods replace the default implementation. At the same time,
it scales well - one just has to specify another test number upper bound at compile
time. The method also allows iteration of methods, keeping code compact.</p>
<p class="subheader">Library</p>
<p>Since we dig into the template processing, it is natural to not build any
libraries, therefor this problem mostly disappeares. Unfortunately, not
completely: our code still needs some central point where it could register
itself. But that point (singleton) is so small that it would be an overkill to
create library just to put there one single object. Instead, we assume that the
user code will contain our singleton somewhere in the main module of test
application.</p>
<p class="subheader">User interface. Callbacks.</p>
<p>Our code will perform only minimamum set of tasks: TUT shall run tests. But we still
need a way to adapt the end-user presentation requirements. For some of users it
would be enough to see only failed tests in listing; others would like to see the
complete plain-text report; some would prefer to get XML reports, and some would
not want to get any reports at all since they draw their test execution log in GUI
plugin for an IDE. </p>
<p>"Many users" means "many demands", and satisfying all of them is quite a hard task.
Attempt to use a kind of silver bullet (like XML) is not the right solution, since
user would lack XML parser in his environment, or just would not want to put it into
his project due to integration complexities.</p>
<p>The decision was made to allow users to form their reports by themselfs. TUT will
report an event, and the user code will form some kind of an artefact based on this
event.</p>
<p>The implementation of this decision is interface tut::callback. The user code
creates a callback object, and passes it to the runner. When an appropriate event
occures, the test runner invokes callback methods. User code can do anything,
from dumping test results to std::cout to drawing 3D images, if desired. </p>
<p class="subheader">STL</p>
<p>Initially, there were plans to make TUT traits-based in order not to restrict
it with STL only, but have a possibility to use other kinds of strings
(TString, CString), containers and intercepted exceptions. </p>
<p>In the current version, these plans are not implemented due to relative
complexity of the task. For example, the actual set of operations can be quite
different for various map implementations and this makes writing generic code
much harder. </p>
<p>Thus so far TUT is completely STL-based, since STL is the only library existing
virtually on every platform. </p>
<img src="/_img/pixel.gif" alt="" width=1 height=300><br>
<center>
<!--RAX counter-->
<script language="JavaScript">
<!--
document.write('<a href="http://www.rax.ru/click" '+
'target=_blank><img src="http://counter.yadro.ru/hit?t13.4;r'+
escape(document.referrer)+((typeof(screen)=='undefined')?'':
';s'+screen.width+'*'+screen.height+'*'+(screen.colorDepth?
screen.colorDepth:screen.pixelDepth))+';'+Math.random()+
'" alt="rax.ru: 24 , 24 \" '+
'border=0 width=88 height=31></a><br>')
//-->
</script>
<!--/RAX-->
</center>
</td>
</tr>
</table>
<!-- << -->
</td>
</tr>
</table>
<!-- content table << -->
</td>
</tr>
</table>
</body>
</html>
|