This repository has been archived by the owner on Nov 16, 2022. It is now read-only.
forked from nette/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtesting.texy
1019 lines (716 loc) · 28.6 KB
/
testing.texy
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
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Nette Tester - enjoyable unit testing
*************************************
/--div .[perex]
Even good programmers make mistakes. The difference between a good programmer and a bad one is that a good one
detects it sooner by using automated tests.
- "One who doesn't test is doomed to repeat his or her own mistakes." (wise proverb)
- "When we get rid of one error, another one appears." (Murphy's Law)
- "Do test, do test, do test" (Martin Iljič Fowler)
\--
Have you ever written the following code in PHP?
/--php
$obj = new MyClass;
$result = $obj->process($input);
var_dump($result);
\--
So, have you ever dumped a function call result just to check by eye that it returns what it should return?
You surely do it many times per day. If everything works, do you delete this code and expect that the class
will not be broken in the future? Murphy's Law guarantees the opposite :-)
In fact, we wrote the test. And if we didn't delete it we could run it any time in the future to verify that
everything still works as it should. You may create a large amount of these tests over time, so it would be nice
if we were able to run them automatically. It would be useful to slightly modify test not to require our inspection,
simply to be able to check itself.
And Nette Tester helps exactly with that.
Installation and requirements
=============================
The Tester requires PHP version 5.3.0 or newer.
Preferred way of installation is by "Composer":http://getcomposer.org and every following example assumes that.
But the Tester can be used without it as will also be shown below.
Installation by Composer
------------------------
Let's assume you have Composer up and running, and an application named `demo` with the following structure:
/--
demo/
├── src/ # application code we want to test
├── tests/ # tests we are writing
├── vendor/
└── composer.json
\--
Navigate in terminal to the application directory and add Tester as a dependency using Composer:
/--
cd demo
php composer.phar require --dev nette/tester '~1.4.0'
\--
Manual installation
--------------------
Download Tester "from GitHub":https://github.com/nette/tester/releases and extract it or clone its repository
by `git clone https://github.com/nette/tester.git`. Directory structure of our `demo` application is now as follows:
/--
demo/
├── src/ # application code we want to test
├── tester/ # source of downloaded Tester
│ ├── src/
│ ├── tests/
│ ├── ...
│ └── readme.md
│
└── tests/ # tests we are writing
\--
Running the Tester
==================
Nette Tester is run from the command line. We can try it, without any arguments it will only show a help summary.
/--
cd demo
php vendor/nette/tester/src/tester.php # installation by Composer
php tester/src/tester.php # manual installation
\--
If Tester was installed by Composer we can also use the more convenient shortcut scripts:
/--
cd demo
vendor/bin/tester # UNIX
vendor\bin\tester.bat # Windows
\--
.[note]
For simplification we only use the **tester** command further in this document. But one of the ways mentioned
above with a full path is required to run it.
Running the tests
=================
Our application has no tests yet. We create a simple class to be tested and we save it to file `src/Greeting.php`
/--php
<?php
class Greeting
{
public function say($name)
{
if (!$name) {
throw new InvalidArgumentException('Invalid name');
}
return "Hello $name";
}
}
\--
Let's write a test now. We save it to file `tests/greeting.phpt`. Don't be discouraged by its length, it is shown later how to simplify it.
/--php
<?php
use Tester\Assert;
# Load Tester library
require __DIR__ . '/../vendor/autoload.php'; # installation by Composer
require __DIR__ . '/../tester/src/bootstrap.php'; # manual installation
# Load the tested class. Composer or your autoloader surely takes care of that in practice.
require __DIR__ . '/../src/Greeting.php';
# Environment configuration improves error dumps readability.
# You don't have to use it if you prefer the PHP default dump.
Tester\Environment::setup();
$o = new Greeting;
Assert::same( 'Hello John', $o->say('John') ); # we expect the same
Assert::exception(function() use ($o) { # we expect an exception
$o->say('');
}, 'InvalidArgumentException', 'Invalid name');
\--
The test is written we can run it from command line for the first time:
/--
cd tests
php greeting.phpt
\--
Yes, we have run the test as ordinary PHP script for the first time. Even it seems common, big potential is hidden in it. We can step through the test in IDE or load it via web browser.
The first running finds syntactic errors and if we didn't make a typo the test ends without error report. Let's change an assertion in the test to `Assert::same( 'Hi John', $o->say('John') );` and let's watch what happens when run.
As the application grows number of tests grows with it. It would not be practical to run tests one by one. We use the Tester for running:
/--
cd demo
tester tests/greeting.phpt # we run a single test
tester tests # we run all tests in directory
\--
To see how more tests running looks like, let's try to run all tests which test the Tester itself. They are part of its installation:
/--
tester vendor/nette/tester/tests # installation by Composer
tester tester/tests # manual installation
\--
How the Tester runs
-------------------
The Tester walks through passed directories at first, it creates a list of found tests and it runs them one by one. It runs every test as a new process, every test runs completely separated from others.
.[warning]
The Tester runs PHP processes with **-n** option, so without **php.ini**. More details in the [#Own php.ini] chapter.
Test results evaluation
-----------------------
The Tester prints test results continuously during testing:
- `.` (dot) - test passed
- `s` - test has been skipped
- `F` - test failed
Output may look like:
/--
_____ ___ ___ _____ ___ ___
|_ _/ __)( __/_ _/ __)| _ )
|_| \___ /___) |_| \___ |_|_\ v1.4.0
PHP 5.4.4-14+deb7u7 | 'php-cgi' -n | 1 threads
........s................F.........
-- FAILED: tests/greeting.phpt
Failed: 'Hello John' should be
... 'Hi John'
in src/Framework/Assert.php(370)
in src/Framework/Assert.php(52) Tester\Assert::fail()
in tests/greeting.phpt(6) Tester\Assert::same()
FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)
\--
35 tests were run, one failed, one was skipped. If no one fails Tester's exit code is zero. Non-zero otherwise.
And that's all for introduction. We will discuss details in following chapters:
Directories structure
=====================
It may seem early to talk about it but well-structured directory with tests saves work a lot. We separate tests into subdirectories by namespace of tested classes:
/--
demo/
└── tests/
├── NamespaceOne/
│ ├── MyClass.getUsers.phpt
│ ├── MyClass.setUsers.phpt
│ └── ...
│
├── NamespaceTwo/
│ ├── MyClass.creating.phpt
│ ├── MyClass.dropping.phpt
│ └── ...
│
├── test.one.phpt
├── test.two.phpt
├── ...
└── bootstrap.php
\--
and we create a file `bootstrap.php`. It contains a common code for all tests. For example classes autoloading, environment configuration, temporary directory creation, helpers and similar. Every test loads the bootstrap and pays attention to testing only. The bootstrap can look like:
/--php
require __DIR__ . '/../vendor/autoload.php';
Tester\Environment::setup();
date_default_timezone_set('Europe/Prague');
define('TMP_DIR', '/tmp/demo-app-tests');
\--
There is no difference to run the tests with changed directory structure. The Tester finds all `*.phpt` tests recursively and runs them:
/--
cd demo
tester tests
\--
But we can run tests for single namespace easily:
/--
tester tests/NamespaceOne
\--
Assertions
==========
In the example test at the beginning we used two assertions: `Assert::same()` and `Assert::exception()`. Now we introduce all other types and we explain how to be used. They are methods of the `Tester\Assert` class but for simplification we use them further without namespace.
Assert::same($expected, $actual) .{toc: Assert::same()}
--------------------------------
`$expected` must be the same as `$actual`. It is the same as PHP operator `===`.
Assert::notSame($expected, $actual) .{toc: Assert::notSame()}
-----------------------------------
Opposite to `Assert::same()`.
Assert::equal($expected, $actual) .{toc: Assert::equal()}
---------------------------------
`$expected` must be equal to `$actual`. Object identities and array keys order are ignored.
Assert::notEqual($expected, $actual) .{toc: Assert::notEqual()}
------------------------------------
Opposite to `Assert::equal()`.
Assert::contains($needle, $actual) .{toc: Assert::contains()}
----------------------------------
`$actual` must contain `$needle`. If `$actual` is string, it must contain `$needle` substring. If it is an array, it must contain `$needle` item.
Assert::notContains($needle, $actual) .{toc: Assert::notContains()}
-------------------------------------
Opposite to `Assert::contains()`.
Assert::true($value) .{toc: Assert::true()}
--------------------
`$value` must be `TRUE`, so `$value === TRUE`.
Assert::truthy($value) .{toc: Assert::truthy()}
----------------------
`$value` must be truthy, so `$value == TRUE`.
Assert::false($value) .{toc: Assert::false()}
---------------------
`$value` must be `FALSE`, so `$value === FALSE`.
Assert::falsey($value) .{toc: Assert::falsey()}
----------------------
`$value` must be falsey, so `$value == FALSE`.
Assert::count($count, $value) .{toc: Assert::count()}
-----------------------------
`$count` must be number of elements in `$value`. Countable is an array or an object implementing `Countable`.
Assert::null($value) .{toc: Assert::null()}
--------------------
`$value` must be `NULL`, so `$value === NULL`.
Assert::nan($value) .{toc: Assert::nan()}
-------------------
`$value` must be Not a Number.
Assert::type($type, $value) .{toc: Assert::type()}
---------------------------
`$value` must be of given type. As `$type` we can use string:
- `array`
- `list` - same as the array but keys must start by zero and must be incremented by one
- `bool`
- `callable`
- `float`
- `int` or `integer`
- `null`
- `object`
- `resource`
- `scalar`
- `string`
- class name or object directly then must pass `$value instanceof $type`
Assert::exception($callable, $class, $message = NULL) .{toc: Assert::exception()}
-----------------------------------------------------
On `$callable` invocation an exception of `$class` instance must be thrown. If we pass `$message`, message of the exception must match (see "Assert::match()":#toc-assert-match). And if we pass `$code`, code of the exception must be the same. For example, this test fails because message of the exception does not match:
/--php
Assert::exception(function() {
throw new App\InvalidValueException('Zero value');
}, 'App\InvalidValueException', 'Value is to low');
\--
The `Assert::exception()` is unique by returning the thrown exception. So we can test a previous exception:
/--php
$e = Assert::exception(function() {
throw new MyException('Something is wrong', 0, new RuntimeException);
}, 'MyException', 'Something is wrong');
Assert::type('RuntimeException', $e->getPrevious());
\--
Assert::error($callable, $type, $message = NULL) .{toc: Assert::error()}
------------------------------------------------
If we pass a class name as `$type`, this assertion behaves as absolutely same as `Assert::exception()`.
If `$type` is one of `E_...` constants, for example `E_WARNING`, the `$callable` must generate this error when invoked. And if we pass `$message` message of the error must match (see "Assert::match()":#toc-assert-match). For example:
/--php
Assert::error(function() {
$i++;
}, E_NOTICE, 'Undefined variable: i');
\--
And the last possibility, if `$type` is an array the `$callable` must generate all expected errors. An example shows it best:
/--php
Assert::error(function() {
$a++;
$b++;
}, array(
array(E_NOTICE, 'Undefined variable: a'),
array(E_NOTICE, 'Undefined variable: b'),
));
\--
Assert::match($pattern, $actual) .{toc: Assert::match()}
--------------------------------
`$actual` must match to `$pattern`. We can use two pattern types:
We pass regular expression as `$pattern`. To delimit it we must use `~` or `#`. Other delimiters are not supported. For example test where `$var` must contain only hexadecimal digits:
/--php
Assert::match( '#^[0-9a-f]$#i', $var );
\--
The other variant is similar to string comparing but we can use some modifiers in `$pattern`:
- `%a%` one or more of anything except the end of line characters
- `%a?%` zero or more of anything except the end of line characters
- `%A%` one or more of anything including the end of line characters
- `%A?%` zero or more of anything including the end of line characters
- `%s%` one or more white space characters except the end of line characters
- `%s?%` zero or more white space characters except the end of line characters
- `%S%` one or more of characters except the white space
- `%S?%` zero or more of characters except the white space
- `%c%` a single character of any sort (except the end of line)
- `%d%` one or more digits
- `%d?%` zero or more digits
- `%i%` signed integer value
- `%f%` floating point number
- `%h%` one or more HEX digits
Examples:
/--php
# Again, hexadecimal number test
Assert::match( '%h%', $var );
# Generalized path to file and line number
Assert::match( 'Error in file %a% on line %i%', $errorMessage );
\--
Assert::matchFile($file, $actual) .{toc: Assert::matchFile()}
---------------------------------
The assertion is identical to "Assert::match()":#toc-assert-match but the pattern is loaded from `$file`. It is useful for very long strings testing. Test file stands readable.
Assert::fail($message, $actual = NULL, $expected = NULL) .{toc: Assert::fail()}
--------------------------------------------------------
This assertion always fails. It is just handy. We can optionally pass expected and actual values.
Failed assertions investigation
-------------------------------
The Tester shows where the error is when an assertion fails. When we compare complex structures, the Tester creates dumps of compared values and saves them into directory **output**. For example when imaginary test `Arrays.recursive.phpt` fails the dumps will be saved as follows:
/--
demo/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # actual value
│ └── Arrays.recursive.expected # expected value
│
└── Arrays.recursive.phpt # failing test
\--
We can change the name of the directory in `Tester\Dumper::$dumpDir`.
Skipping tests
==============
Some tests can run only under certain circumstances. If these circumstances aren't met, we can skip the tests. For example, missing PHP extension:
/--php
if (!extension_loaded('DOM')) {
Tester\Environment::skip('Test requires DOM extension to be loaded.');
}
\--
Test will be stopped and marked as `s` - skipped. Later we find out how to skip the test using the `@skip` annotation.
TestCase
========
We have shown the simple test where assertions follow one by one at the beginning of this guide. Sometimes it is useful to enclose the assertions to test class and structure them in this way. The class must be descendant of `Tester\TestCase` and we talk about it simply as about **testcase**.
/--php
use Tester\Assert;
class GreetingTest extends Tester\TestCase
{
public function testOne() {
Assert::same(......);
}
public function testTwo() {
Assert::match(......);
}
}
# Testing methods run
$testCase = new GreetingTest;
$testCase->run();
\--
We can enrich a testcase by `setUp()` and `tearDown()` methods. They are called before/after every testing method:
/--php
use Tester\Assert;
class NextTest extends Tester\TestCase
{
public function setUp() {
# Preparation
}
public function tearDown() {
# Clean-up
}
public function testOne() {
Assert::same(......);
}
public function testTwo() {
Assert::match(......);
}
}
# Testing methods run
$testCase = new NextTest;
$testCase->run();
/*
Method calls order
------------------
setUp()
testOne()
tearDown()
setUp()
testTwo()
tearDown()
*/
\--
There are a few annotations available to help us with testing methods. We write them toward the testing method.
@throws
-------
It is equal usage of `Assert::exception()` inside a testing method. But notation is more readable:
/--php
/**
* @throws RuntimeException
*/
public function testOne()
{
...
}
/**
* @throws LogicException Wrong argument order
*/
public function testTwo()
{
...
}
\--
@dataProvider
-------------
This annotation suits when we want to run the testing method multiple times but with different arguments. As argument we write method name which return parameters for testing method. Simple example:
/--php
public function getLoopArgs()
{
return array(
array(1, 2, 3),
array(4, 5, 6),
array(7, 8, 9),
);
}
/**
* @dataProvider getLoopArgs
*/
public function testLoop($a, $b, $c)
{
...
}
\--
The other annotation **@dataProvider** variation accepts a path to INI file (relatively to test file) as argument. The method is called so many times as the number of sections contained in INI file. File `loop-args.ini`:
/--
[one]
a=1
b=2
c=3
[two]
a=4
b=5
c=6
[three]
a=7
b=8
c=9
\--
and the method which uses the INI file:
/--php
/**
* @dataProvider loop-args.ini
*/
public function testLoop($a, $b, $c)
{
...
}
\--
Test file annotations
=====================
.[warning]
Following annotations are evaluated only if we run the test by the Tester, not manually as ordinary PHP script.
We write annotations at the beginning of the test file and the typing is case-insensitive. Imaginary example:
/--php
/**
* TEST: Basic database query test.
*
* @dataProvider files/databases.ini
* @exitCode 56
* @phpVersion < 5.5
*/
require __DIR__ . '/../bootstrap.php';
\--
TEST:
-----
It is not an annotation actually. It only sets the test name which is printed on fail or into logs.
@skip
-----
Test is skipped. It is handy for temporary tests deactivation.
@phpVersion
-----------
Test is skipped if is not run by corresponding PHP version. We write annotation as `@phpVersion [operator] version`. We can leave out the operator, default is `>=`. Examples:
/--php
/**
* @phpVersion 5.3.3
* @phpVersion < 5.5
* @phpVersion != 5.4.5
*/
\--
@dataProvider
-------------
We write annotation as `@dataProvider file.ini`. INI file path is relative to the test file. Test runs as many times as number of sections contained in the INI file. Let's assume the INI file `databases.ini`:
/--
[mysql]
dsn = "mysql:host=127.0.0.1"
user = root
password = ******
[postgresql]
dsn = "pgsql:host=127.0.0.1;dbname=test"
user = postgres
password = ******
[sqlite]
dsn = "sqlite::memory:"
\--
and the file `database.phpt` in the same directory:
/--php
/**
* @dataProvider databases.ini
*/
$args = Tester\Environment::loadData();
\--
The test runs three times and `$args` will contain values from sections **mysql**, **postgresql** or **sqlite**.
There is one more variation when we write annotations with a question mark as `@dataProvider? file.ini`. In this case test is skipped if the INI file doesn't exist.
Annotation possibilities have not been mentioned all yet. We can write conditions after the INI file. Test runs for given section only if all conditions match. Let's extend the INI file:
/--
[mysql]
dsn = "mysql:host=127.0.0.1"
user = root
password = ******
[postgresql 8.4]
dsn = "pgsql:host=127.0.0.1;dbname=test"
user = postgres
password = ******
[postgresql 9.1]
dsn = "pgsql:host=127.0.0.1;dbname=test;port=5433"
user = postgres
password = ******
[sqlite]
dsn = "sqlite::memory:"
\--
and we will use annotation with condition:
/--php
/**
* @dataProvider databases.ini postgresql, >=9.0
*/
\--
The test runs only once for section `postgresql 9.1`. Other sections don’t match the conditions.
@multiple
---------
We write it as `@multiple N` where **N** is integer. Test runs exactly N-times.
@testCase
---------
Annotation has no parameters. We use it when we write a test as `Tester\TestCase` classes. In this case the test runs as many times as the number of testing methods. Every testing method runs in separated process. It can dramatically speed up the whole testing procedure. We recommend usage of this annotation.
@exitCode
---------
We write it as `@exitCode N` where **N** is the exit code of the test. For example if `exit(10)` is called in the test, we write annotation as `@exitCode 10`. It is considered to be fail if the test ends with a different code. Exit code 0 (zero) is verified if we leave out the annotation
@httpCode
---------
Annotation is evaluated only if PHP binary is CGI. It is ignored otherwise. We write it as `@httpCode NNN` where **NNN** is expected HTTP code. HTTP code 200 is verified if we leave out the annotation.
@outputMatch a @outputMatchFile
-------------------------------
The behaviour of annotations is consistent with `Assert::match()` and `Assert::matchFile()` assertions. But pattern is found in test’s standard output. A suitable use case is when we assume the test to end by fatal error and we need to verify its output.
@phpIni
-------
It sets INI configuration values for test. For example we write it as `@phpIni precision=20` and it works in the same way as if we passed value from the command line by parameter `-d precision=20`.
Helpers
=======
DomQuery
--------
`Tester\DomQuery` class helps to test HTML or XML content. Description of all the methods is in "API documentation":http://api.nette.org/tester/Tester.DomQuery.html. Here, we show basic usage:
/--php
# HTML which we want to test
$html = $template->render();
# We create DOM structure from HTML
$dom = Tester\DomQuery::fromHtml($html);
# We can test
Assert::true( $dom->has('form#registration') );
Assert::true( $dom->has('input[name="username"]') );
Assert::true( $dom->has('input[name="password"]') );
Assert::true( $dom->has('input[type="submit"]') );
\--
We use CSS selector as `DomQuery::has($selector)` parameter.
FileMock
--------
`Tester\FileMock` emulates files in memory and helps you to test a code which uses functions like `fopen()`, `file_get_contents()` or `parse_ini_file()`. For example:
/--php
# Tested class
class Logger
{
private $logFile;
public function __construct($logFile)
{
$this->logFile = $logFile;
}
public function log($message)
{
file_put_contents($this->logFile, $message . "\n", FILE_APPEND);
}
}
# New empty file
$file = Tester\FileMock::create('');
$logger = new Logger($file);
$logger->log('Login');
$logger->log('Logout');
# Created content testing
Assert::same( "Login\nLogout\n", file_get_contents($file) );
\--
purge()
-------
Class `Tester\Helpers` offers method `purge()` which creates passed directory but only if doesn't exist yet. If so, it purges all its content. It is handy for temporary directory creation. For example in `tests/bootstrap.php`:
/--php
@mkdir(__DIR__ . '/tmp'); # @ - directory may already exist
define('TEMP_DIR', __DIR__ . '/tmp/' . getmypid());
Tester\Helpers::purge(TEMP_DIR);
\--
lock()
------
Later we learn that tests run in parallel. Sometimes we need not to overlap the test running. Typically database tests need to prepare structure and data in database and they need nothing disturbs them during running time of the test. In these cases we use `Tester\Environment::lock($name, $dir)`:
/--php
Tester\Environment::lock('database', __DIR__ . '/tmp');
\--
The first argument is a lock name. The second one is a path to directory for saving the lock.
The test which acquires the lock first runs. Other tests must wait till it is completed.
Command line options
====================
We obtain command line options overview by running the Tester without parameters or with option `-h`.
-p <path>
---------
The Tester uses this PHP binary for tests running:
/--
tester -p /home/user/php-5.5.0/php-cgi tests
\--
-c <path>
---------
The option is the same as for PHP binary. Passed **php.ini** is used for tests. No php.ini is used in default. Example when we distribute php.ini along with tests:
/--
tester -c tests/php.ini tests
\--
-l | --log <path>
-----------------
Progress of the test is written to file. All failed, skipped and also successful tests:
/--
tester -log /var/log/tests.log tests
\--
-d <key=value>
--------------
The option is the same as for PHP binary. It defines INI value for tests. The option can be used multiple times.
-s
---
Information about skipped tests will be shown.
--stop-on-fail
--------------
Tester stops testing upon the first failing test.
-j <num>
--------
Tests run in **<num>** parallel threads. Default value is 8. If we wish to run tests in series, we use value 1.
-o <console|tap|junit|none>
---------------------------
Output format. Default is the console format.
- `console`: the same as default, but logo is not printed in this case
- `tap`: "TAP format":http://en.wikipedia.org/wiki/Test_Anything_Protocol appropriate for machine processing (replaces the `--tap` option)
- `junit`: JUnit XML format, appropriate for machine processing too
- `none`: nothing is printed
-w | --watch <path>
-------------------
The Tester doesn't end after tests are completed but it keeps running and watching given directory. It runs tests again whenever directory changes. Parameter can be used multiple times. It is handy during tests writing and debugging.
-i | --info
-----------
It shows information about a test running environment. For example:
/--
tester -p /home/php/php-5.5.6 -c tests/php.ini --info
PHP binary:
/home/php/php-5.5.6
PHP version:
5.5.6 (cli)
Loaded php.ini files:
/var/www/dev/demo/tests/php.ini
Loaded extensions:
Core, ctype, date, dom, ereg, fileinfo, filter, hash, ...
\--
--setup <path>
--------------
The Tester loads given PHP script on start. Variable `Tester\Runner\Runner $runner` is available in it. Let's assume file `tests/runner-setup.php`:
/--php
$runner->outputHandler[] = new MyOutputHandler;
\--
and we run the Tester:
/--
tester --setup tests/runner-setup.php tests
\--
--colors 1|0
------------
The Tester detects a colour-able terminal in default and colorizes its output. This option is over the autodetection. We can set colouring globally by a system environment variable `NETTE_TESTER_COLORS`.
--coverage <path>
-----------------
Tester will generate a report with overview how much is the source code coveraged by tests. This option requires PHP extension "Xdebug":http://xdebug.org/ enabled. The destination file extension determines the contents format. HTML or Clover XML.
/--
tester tests --coverage coverage.html # HTML report
tester tests --coverage coverage.xml # Clover XML report
\--
--coverage-src <path>
---------------------
We use it with option `--coverage` simultaneously. The `<path>` is a path to source code for which we generate the report.
HHVM support
============
Tester supports [HHVM |http://hhvm.com] 3.3.0 or newer. Usage is simple, just pass a HHVM binary path using the `-p` option.
/--
tester -p hhvm
\--
Tips
====
- It is not good practice to use `exit()` and `die()` to end test with a fail message. For example `exit('Error in connection')` ends a test with exit code 0 (zero) and it means success. Always use `echo 'Error in connection'` followed by `exit(1)`.
- It is convenient to write accurate assertions `Assert::same($a, $b)`, not `Assert::true($a === $b)`. Only in the first case we get meaningful error message. In the other case we get `FALSE should be TRUE` only and it says nothing about $a and $b variables.
- Use the `Assert::nan()` for NAN (Not a Value) testing. NAN value is very specific and assertions `Assert::same()` or `Assert::equal()` can work unpredictably.
Own php.ini
===========
The Tester runs PHP processes with **-n** option, so no **php.ini** is loaded (not even that from `/etc/php/conf.d/*.ini` in UNIX). It ensures the most clean environment for the tests run, but it also deactivates all the external PHP extensions commonly loaded by system PHP.