-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
888 lines (859 loc) · 76.7 KB
/
index.html
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
<!DOCTYPE html>
<html class="no-js" lang="en-us">
<head>
<meta name="generator" content="Hugo 0.80.0" />
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#1b1b1b">
<title>Snowyurik's Blog</title>
<script>(function(d,e){d[e]=d[e].replace("no-js","js");})(document.documentElement,"className");</script>
<meta name="yandex-verification" content="11817d5c29245445" />
<meta name="description" content="">
<link rel="alternate"
type="application/rss+xml"
href="https://snowyurik.vihv.org//index.xml"
title="Snowyurik's Blog">
<link rel="stylesheet" href="/css/bundle.css">
<link rel="icon" href="/icons/16.png" sizes="16x16" type="image/png">
<link rel="icon" href="/icons/32.png" sizes="32x32" type="image/png">
<link rel="alternate" type="application/rss+xml" href="/index.xml" title="Snowyurik's Blog">
<link rel="stylesheet" href="/highlight-api-smooth.css">
<script data-name="BMC-Widget" data-cfasync="false" src="https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js" data-id="snowyurik" data-description="Support me on Buy me a coffee!" data-message="Questions / Comments / Feedback / Donate" data-color="#d89700" data-position="Right" data-x_margin="18" data-y_margin="18"></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-15855473-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-15855473-1');
</script>
</head>
<body class="body kind-home">
<header class="header">
<a class="logo" href="/">Snowyurik's Blog</a>
<nav class="main-nav" role="navigation">
<button id="toggle" class="main-nav__btn" aria-label="Menu toggle" aria-expanded="false" tabindex="0">
<div class="main-nav__btn-box" tabindex="-1">
<svg class="main-nav__icon icon-menu" width="18" height="18" viewBox="0 0 18 18">
<path class="icon-menu__burger" d="M18 0v3.6H0V0h18zM0 10.8h18V7.2H0v3.6zM0 18h18v-3.6H0V18z"/>
<path class="icon-menu__x" d="M11.55 9L18 15.45 15.45 18 9 11.55 2.55 18 0 15.45 6.45 9 0 2.55 2.55 0 9 6.45 15.45 0 18 2.55 11.55 9z"/>
</svg>
</div>
</button>
<ul id="menu" class="main-nav__list">
<li class="main-nav__item">
<a class="main-nav__link" href="/hireme/">
<span class="main-nav__text">Contacts (hire me!)</span>
</a>
</li>
</ul>
</nav>
</header>
<div class="primary">
<main class="main">
<div class="cards">
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/frameworks-dont-last-long/">Why frameworks don't last long?</a></h1>
<div class="entry__content"><blockquote>
<p>They are costly to maintain</p>
</blockquote>
<p>That’s why we have brand new framework every year.</p>
<p>It’s kind of weird:</p>
<ul>
<li>Framework without components is just a pattern.</li>
<li>The more components you have, the more successfull will be your framework.</li>
<li>To create competitive minimum of components you need to engage 3rd party devs.</li>
</ul>
<p>However, for 3rd party component creators situation is different:<br>
the more frameworks use your component - the more succesful your component is.</p>
<p>At the end of the day, we have framework-idependent component + set of wrappers.</p>
<p>So, frameworks tend to dissolve and turn back into patterns.
Unless, they have some unique “game changing” features which are hard to replicate, complicated and bloated.</p>
<p>Here is a fork:</p>
<ul>
<li>If fremework creators refactor framework architecture - that lead to simplification of core features, they become replicatabe, they are replicated, and framework turn into design pattern.</li>
<li>If they do not refactor their code - that lead to complicated legacy code which is impossible to maintain.</li>
</ul>
<p>One way or another - all frameworks are doomed 🙄</p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2023-04-16T18:50:43+07:00">April 16, 2023</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/framework/" rel="tag">framework</a>,
<a class="meta-tags__link" href="/tags/architecture/" rel="tag">architecture</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/do-not-trust-trusted-third-party-code-signing/">NEVER trust Trusted Third Party (application signing)</a></h1>
<div class="entry__content"><p>You might had seen complains from Windows Defender, like “unknown publisher”.</p>
<p>And if is a developer, you do not want this message to be displayed, so you have to buy certificate from so called “trusted third party”, and voila.</p>
<p>But.. from end user point of view, does it even make any sence?</p>
<blockquote>
<p>This “trusted” companies check NOTHING important for end user</p>
</blockquote>
<p>All they do is googling company name. That’s all.</p>
<p>You can do it yourself, you can do it better.</p>
<ul>
<li>Do they check the package for viruses? NO!</li>
<li>Will anyone be responsible if the app will damage your data? NO!
Every license agreement have a “disclaimer” section.</li>
<li>Will “trusted” third party be responsibe? Of cause not.</li>
<li>Does it guarantee that the copy is legal and not pirated or modified? NO!
In most companies literally everyone has access to this certificates. Why? Because, from the company point of view, the sole purpose of this certificate is to suppress windows defender warning. So, nobody cares. If certificate will leak - company won’t lose anything valuable.</li>
</ul>
<p>In fact, self-signed applicationas are more secure. At least, no “trusted” third party has access to the certificate.</p>
<p>So if (if!) the company want to prevent supply-chain attacks, it can do it.<br>
Will company benefit from preventing supply-chain attacks? 🤔</p>
<p>Also, checksums will actually work better. Attacker would have to hack both app and company website.</p>
<p>So, there is a good idea to check package for viruses.</p>
<p>For example, here you can check it before download: <a href="https://www.virustotal.com/gui/home/url">https://www.virustotal.com/gui/home/url</a></p>
<p>Especially, if the app is signed by certificate issued by “trusted” third party.</p>
<p>The only argument, “trusted” third pary companies have for trusting this “trusted” certificates is: if company can pay, that company is probably big enouth to trust.</p>
<p>REALLY?</p>
<blockquote>
<p>Like we can’t remember any big company which add backdoors, spy on users, collect personal data, sell that data to everyone who ask, make unexpected modifications on your disk etc, etc.</p>
</blockquote>
<p>What’s I’m talking about, things like that never happend. That’s just imaginary situation, which has nothing common with reality.</p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2023-03-07T23:32:39+07:00">March 07, 2023</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/deploy/" rel="tag">deploy</a>,
<a class="meta-tags__link" href="/tags/security/" rel="tag">security</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/agile-why-we-use-todo-work-ready-done-not-red-green-refactor-deploy/">Agile: Why do we use Todo Work Ready Done, instead of Red Green Refactor Deploy?</a></h1>
<div class="entry__content"><blockquote>
<p>Looks obvious, right? 🤔</p>
</blockquote>
<h2 id="current-setup">Current setup</h2>
<p>In TDD, we use the following sequence:</p>
<ul>
<li>Red: try and fail</li>
<li>Green: fix and confirm</li>
<li>Refactor: polish solution</li>
</ul>
<p>That’s 100% solid time-proven sequence. But in Agile/Scrum/Kanban we completely different approach:</p>
<ul>
<li>Todo</li>
<li>Work</li>
<li>Ready</li>
<li>Done</li>
</ul>
<p>Which is not as good as it looks at first sight. That’s why Trello and Jira had an option to set custom workflow and custom task states.</p>
<blockquote>
<p>TDD workflow and Agile task workflow does not map to each other in any aspect! 🥲</p>
</blockquote>
<p>So, why are we doing that? To be more “manager friendly”? Is it “positive thinking” which stops as from using the word “fail”?</p>
<p>Well, we can replace “try and fail” with “try and confirm that feature is not implemented / reproduce the bug”, happy?</p>
<h2 id="suggestion">Suggestion</h2>
<p>Maybe we can use sequence like that, for task management:</p>
<ul>
<li>Pick: from heap to sprint backlog</li>
<li>Try/Red: Try it and confirm the issue</li>
<li>Fix/Green: Fix and confirm solution</li>
<li>Polish/Refactor: polish solution</li>
<li>Commit: share changes with the team</li>
<li>Package: aggregate changes into release package</li>
<li>Test: QA team test the package</li>
<li>Deploy: publish package for general public</li>
<li>Collect feedback: from support team, investors, etc.</li>
<li>Fund: get money for next sprint</li>
</ul>
<p>This looks much closer to reality, you know.. 😎</p>
<p>I suggest using <em>Try/Fix/Polish</em> insteard of <em>Red/Green/Refactor</em> to avoid confusion between task management and coding. But that sets maps to each other perfectly! <br>
Also, that’s closer to the idea behind SMART criteria.</p>
<p>For in-house projects, it can be simplified to just:</p>
<ul>
<li>Pick</li>
<li>Try</li>
<li>Fix</li>
<li>Polish</li>
<li>Deploy</li>
</ul>
<blockquote>
<p>Why not?</p>
</blockquote></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2023-01-12T00:40:48+07:00">January 12, 2023</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/agile/" rel="tag">Agile</a>,
<a class="meta-tags__link" href="/tags/test-driving-development/" rel="tag">Test Driving Development</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/cli-table-draw-python/">CLI table draw in Python</a></h1>
<div class="entry__content"><blockquote>
<p>Well.. I migrated all my current project to python 😅</p>
</blockquote>
<p>Recipie is the same: fill all cells with spaces to maximum column length, then concatenate array representing table row using separator. Flavor with special chars like “├”.</p>
<p>Implemetation changed from <a href="https://snowyurik.vihv.org/post/php-cli-how-to-draw-table/" title="Php CLI how to draw table">Php CLI how to draw table</a> , but not really much:</p>
<ul>
<li>Multibyte string length:
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="nx">mb_strlen</span><span class="p">(</span> <span class="nx">cellContent</span> <span class="p">)</span>
</code></pre></div><p>become</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="nb">len</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span> <span class="n">cellContent</span> <span class="p">))</span>
</code></pre></div></li>
<li>Column names:
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="nv">$key</span><span class="o">.</span><span class="nx">str_repeat</span><span class="p">(</span><span class="s2">" "</span><span class="p">,</span><span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">-</span> <span class="nx">mb_strlen</span><span class="p">(</span><span class="nv">$key</span><span class="p">))</span>
</code></pre></div><p>become</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">key</span><span class="o">.</span><span class="n">ljust</span><span class="p">(</span> <span class="n">maxLenghts</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="p">)</span>
</code></pre></div></li>
<li>Horizontal lines:
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="nx">str_repeat</span><span class="p">(</span><span class="s2">"─"</span><span class="p">,</span><span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">])</span>
</code></pre></div><p>become</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="s2">""</span><span class="o">.</span><span class="n">ljust</span><span class="p">(</span> <span class="n">maxLenghts</span><span class="p">[</span><span class="n">key</span><span class="p">],</span> <span class="s2">"─"</span><span class="p">)</span>
</code></pre></div></li>
<li>Data cell:
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="nv">$cell</span> <span class="o">.=</span> <span class="nx">str_repeat</span><span class="p">(</span><span class="s2">" "</span><span class="p">,</span> <span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">-</span> <span class="nx">mb_strlen</span><span class="p">(</span><span class="nv">$cell</span><span class="p">))</span>
</code></pre></div><p>become</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="nb">str</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span><span class="o">.</span><span class="n">ljust</span><span class="p">(</span> <span class="n">maxLenghts</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="p">)</span>
</code></pre></div><p>(some cells contain numbers, thats why <code>str(..)</code>)</p>
</li>
<li>Drawing table header:
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="k">echo</span> <span class="s2">"╭─"</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">"─┬─"</span><span class="p">,</span> <span class="nv">$horizontalLines</span><span class="p">)</span><span class="o">.</span><span class="s2">"─╮</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"│ "</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">" │ "</span><span class="p">,</span> <span class="nv">$columnNames</span> <span class="p">)</span><span class="o">.</span><span class="s2">" │</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"├─"</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">"─┼─"</span><span class="p">,</span> <span class="nv">$horizontalLines</span><span class="p">)</span><span class="o">.</span><span class="s2">"─┤</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
</code></pre></div><p>become</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">print</span><span class="p">(</span><span class="s2">"╭─"</span><span class="o">+</span><span class="s2">"─┬─"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">horizontalLines</span><span class="o">.</span><span class="n">values</span><span class="p">())</span><span class="o">+</span><span class="s2">"─╮"</span><span class="p">);</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"│ "</span><span class="o">+</span><span class="s2">" │ "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">columnNames</span><span class="o">.</span><span class="n">values</span><span class="p">()</span> <span class="p">)</span><span class="o">+</span><span class="s2">" │"</span><span class="p">);</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"├─"</span><span class="o">+</span><span class="s2">"─┼─"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">horizontalLines</span><span class="o">.</span><span class="n">values</span><span class="p">())</span><span class="o">+</span><span class="s2">"─┤"</span><span class="p">);</span>
</code></pre></div><p>Pho associative key/value arrays are python dictionaries, and <code>join</code> will concatenate dictionary keys, not values in python. So we need <code>.values()</code> to turn it into array of values only.</p>
</li>
<li>Drawing data row:
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="k">echo</span> <span class="s2">"│ "</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">" │ "</span><span class="p">,</span> <span class="nv">$row</span><span class="p">)</span><span class="o">.</span><span class="s2">" │</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
</code></pre></div><p>bacome</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">print</span><span class="p">(</span><span class="s2">"│ "</span><span class="o">+</span><span class="s2">" │ "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">row</span><span class="o">.</span><span class="n">values</span><span class="p">())</span><span class="o">+</span><span class="s2">" │"</span><span class="p">)</span>
</code></pre></div></li>
<li>Drawing table footer
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="k">echo</span> <span class="s2">"╰─"</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">"─┴─"</span><span class="p">,</span> <span class="nv">$horizontalLines</span><span class="p">)</span><span class="o">.</span><span class="s2">"─╯</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
</code></pre></div><p>become</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">print</span><span class="p">(</span><span class="s2">"╰─"</span><span class="o">+</span><span class="s2">"─┴─"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">horizontalLines</span><span class="o">.</span><span class="n">values</span><span class="p">())</span><span class="o">+</span><span class="s2">"─╯"</span><span class="p">)</span>
</code></pre></div></li>
</ul>
<p>That’s it 😊</p>
<p>Should I make nice function, like in previous post? 🤔 I doubt that it’s really nesessary, but if you want it - drop me a message with “buy me a coffee” widget in bottom-right corner 🤑</p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2023-01-11T16:26:05+07:00">January 11, 2023</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/python/" rel="tag">Python</a>,
<a class="meta-tags__link" href="/tags/pseudographics/" rel="tag">Pseudographics</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/php-cli-how-to-draw-table/">Php CLI how to draw table</a></h1>
<div class="entry__content"><h2 id="the-goal">The goal</h2>
<p>Php shine whenever you work with a lot of text data. So php-cli applications are quite demandable.
But for cli (<strong>c</strong>ommand <strong>l</strong>ine <strong>i</strong>nterface) we do not have all this fancy html tags and css3 styles.
Cli applications are intendent for advanced users, who often want output as stream, which can be directed to next tool in chain.
So, in most cases writing results with simple <em>echo</em> works just fine.. unless you need to add a <em>table</em> to the output.</p>
<h2 id="solution">Solution</h2>
<p>My first idea was to use ncurses, but.. there is much simpler solution.</p>
<blockquote>
<p>All you need is <code>str_repeat()</code></p>
</blockquote>
<p>It’s also possible to use <code>str_pad()</code> in some cases. But, unfortunately, <code>str_pad()</code> does not support unicode.</p>
<p>The idea is to calculate max width for every column, and than fill all cells to this width with spaces.
After that, just concatenate array with <code>implode</code> to display each table row.</p>
<p>Example of the following <em>drawTable()</em> function output:</p>
<p><img src="/img/2023-01-10/table.png" alt="php-cli table"></p>
<p>Source code:</p>
<div class="highlight"><pre class="chroma"><code class="language-php" data-lang="php"><span class="o"><?</span><span class="nx">php</span>
<span class="sd">/**
</span><span class="sd"> @param Array data - array of table rows
</span><span class="sd"> @return String representing the table
</span><span class="sd"> @example echo drawTable([
</span><span class="sd"> [ "id"=>1, "name"=>"Test A", "description"=>"short text" ],
</span><span class="sd"> [ "id"=>2, "name"=>"Test B", "description"=>"long text, describing something" ],
</span><span class="sd"> [ "id"=>3, "name"=>"Test C", "description"=>"long text with multibyte string ä ë ï ö ü" ],
</span><span class="sd"> [ "id"=>4, "name"=>"Test ö", "description"=>"myltybyte in name column" ],
</span><span class="sd"> [ "id"=>5, "name"=>"Test E", "description"=>"weeeee!!! äëïöü äëïöü äëïöü äëïöü äëïöü äëïöü" ],
</span><span class="sd"> ]);
</span><span class="sd"> */</span>
<span class="k">function</span> <span class="nf">drawTable</span><span class="p">(</span><span class="k">Array</span> <span class="nv">$data</span><span class="p">)</span><span class="o">:</span><span class="nx">String</span> <span class="p">{</span>
<span class="c1">// sanity check
</span><span class="c1"></span> <span class="k">if</span><span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="s2">""</span><span class="p">;</span> <span class="p">}</span>
<span class="c1">// get column names
</span><span class="c1"></span> <span class="nv">$keys</span> <span class="o">=</span> <span class="nx">array_keys</span><span class="p">(</span> <span class="nx">reset</span><span class="p">(</span><span class="nv">$data</span><span class="p">)</span> <span class="p">);</span>
<span class="c1">// calculate maximum width for each column
</span><span class="c1"></span> <span class="nv">$columnWidths</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">foreach</span><span class="p">(</span><span class="nv">$keys</span> <span class="k">as</span> <span class="nv">$key</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">mb_strlen</span><span class="p">(</span><span class="nv">$key</span><span class="p">);</span>
<span class="k">foreach</span><span class="p">(</span><span class="nv">$data</span> <span class="k">as</span> <span class="nv">$item</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$width</span> <span class="o">=</span> <span class="nx">mb_strlen</span><span class="p">(</span><span class="nv">$item</span><span class="p">[</span><span class="nv">$key</span><span class="p">]);</span>
<span class="k">if</span><span class="p">(</span> <span class="nv">$width</span> <span class="o">></span> <span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="p">)</span> <span class="p">{</span>
<span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$width</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$columnNames</span> <span class="o">=</span> <span class="nv">$horizontalLines</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">foreach</span><span class="p">(</span> <span class="nv">$keys</span> <span class="k">as</span> <span class="nv">$key</span> <span class="p">)</span> <span class="p">{</span>
<span class="c1">// fill column titles with spaces
</span><span class="c1"></span> <span class="nv">$columnNames</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$key</span><span class="o">.</span><span class="nx">str_repeat</span><span class="p">(</span><span class="s2">" "</span><span class="p">,</span><span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">-</span> <span class="nx">mb_strlen</span><span class="p">(</span><span class="nv">$key</span><span class="p">));</span>
<span class="c1">// prepare horisontal lines
</span><span class="c1"></span> <span class="nv">$horizontalLines</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">str_repeat</span><span class="p">(</span><span class="s2">"─"</span><span class="p">,</span><span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]);</span>
<span class="p">}</span>
<span class="c1">// fill every cell to max column width
</span><span class="c1"></span> <span class="nv">$data</span> <span class="o">=</span> <span class="nx">array_map</span><span class="p">(</span><span class="k">function</span><span class="p">(</span><span class="nv">$row</span><span class="p">)</span> <span class="k">use</span> <span class="p">(</span><span class="nv">$columnWidths</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">array_walk</span><span class="p">(</span><span class="nv">$row</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="o">&</span><span class="nv">$cell</span><span class="p">,</span> <span class="nv">$key</span><span class="p">)</span> <span class="k">use</span> <span class="p">(</span><span class="nv">$columnWidths</span><span class="p">){</span>
<span class="nv">$cell</span> <span class="o">.=</span> <span class="nx">str_repeat</span><span class="p">(</span><span class="s2">" "</span><span class="p">,</span> <span class="nv">$columnWidths</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">-</span> <span class="nx">mb_strlen</span><span class="p">(</span><span class="nv">$cell</span><span class="p">));</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nv">$row</span><span class="p">;</span>
<span class="p">},</span> <span class="nv">$data</span><span class="p">);</span>
<span class="nx">ob_start</span><span class="p">();</span>
<span class="c1">// draw table header
</span><span class="c1"></span> <span class="k">echo</span> <span class="s2">"╭─"</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">"─┬─"</span><span class="p">,</span> <span class="nv">$horizontalLines</span><span class="p">)</span><span class="o">.</span><span class="s2">"─╮</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"│ "</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">" │ "</span><span class="p">,</span> <span class="nv">$columnNames</span> <span class="p">)</span><span class="o">.</span><span class="s2">" │</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"├─"</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">"─┼─"</span><span class="p">,</span> <span class="nv">$horizontalLines</span><span class="p">)</span><span class="o">.</span><span class="s2">"─┤</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="c1">// draw lines
</span><span class="c1"></span> <span class="k">foreach</span><span class="p">(</span> <span class="nv">$data</span> <span class="k">as</span> <span class="nv">$row</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"│ "</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">" │ "</span><span class="p">,</span> <span class="nv">$row</span><span class="p">)</span><span class="o">.</span><span class="s2">" │</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// draw bottom line
</span><span class="c1"></span> <span class="k">echo</span> <span class="s2">"╰─"</span><span class="o">.</span><span class="nx">implode</span><span class="p">(</span><span class="s2">"─┴─"</span><span class="p">,</span> <span class="nv">$horizontalLines</span><span class="p">)</span><span class="o">.</span><span class="s2">"─╯</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">ob_get_clean</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div><blockquote>
<p>Note: This solution draw to string buffer, not stdout. For actual display use <code>echo drawTable(...)</code></p>
</blockquote>
<p>Of cause, you can just print it to stdout directly, without <code>ob_start()</code>, <code>ob_get_clean()</code> and use different symbols for table borders.</p>
<p>Here, take some:</p>
<pre><code> ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
</code></pre></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2023-01-10T03:13:15+07:00">January 10, 2023</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/php/" rel="tag">Php</a>,
<a class="meta-tags__link" href="/tags/command-line/" rel="tag">Command line</a>,
<a class="meta-tags__link" href="/tags/preudographics/" rel="tag">Preudographics</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/instant-jun/">Instant Jun</a></h1>
<div class="entry__content"><blockquote>
<p>It’s possible to create artificial junior developer, but does it make any sence?</p>
</blockquote>
<h2 id="artificial-solution-for-artificial-problem">Artificial solution for artificial problem</h2>
<p>Recruiring agencies and recruits stuck in the dillema:</p>
<ul>
<li>Agencies tend to record as much recruis as possible, and ask them to compete for the job.</li>
<li>Recruits tend to apply to as much jobs as possible, and ask them to compete for the employee.</li>
</ul>
<p>Yes, that problem can be easily avoided by “not doing that”, but here we are.</p>
<p>Recruiting agencies try to create at least some funnel for the flood of applications, preferably, automated.
And now we have a lot of test systems which make no sence in terms of real IT tasks.</p>
<blockquote>
<p>Because, in real life, coding is the tiniest part of software development. Biggest part is interaction with the customer: defining the task details, working with feedback, analysis of limitations etc.</p>
</blockquote>
<p>But you are supposed to pass that useless tests anyway, just to reach someone compitent.</p>
<p>So, we have a task defined as a test case. Even more: we are absolutely sure that test is correct.</p>
<blockquote>
<p>If the task can be defined as a test case - it can be solved automatically.</p>
</blockquote>
<p>Developing tests and developing code is interchangable. We use <em>both</em> in Test Driven Development, because we might have an error in the test itself. We use code to check tests and tests to check the code. And push both parties forward in small steps.</p>
<p>But for recruiting tests thats not the case. We don’t care about bugs in recruiting tests. We can’t fix them anyway.
However, that means we should be able to solve that tests automatically.</p>
<h2 id="how">How?</h2>
<p>So let’s check several approaches:</p>
<h3 id="bruteforce">Bruteforce</h3>
<p>We can just check all possible combinations of symbols, that will work, but it will take crazy amount of time. My solutions for tests like that are about 500 symbols in size, English alphabet + special symbols will be about 80 letters, so we have 80^500, combinations, thats approximately 3.5*10^931. Build and run tiny test program on my laptop will take about 0.5 second, so that’s not acceptable solution.
If I will create a dictionary of words used in actual programs, that can be reduced to, like 10^200 seconds, but that’s too much anyway.</p>
<h3 id="algorithmic-approach">Algorithmic approach</h3>
<p>We can reduce choises on each step of generations. For example, if we have <code>{</code> we have to have <code>}</code> later. Also, we can remove some operators like <code>goto</code> or <code>else</code>, as for we do not need them anyway. This way we will have a set of templates, which can be called recursively. That looks more promising, at least if we won’t need to take all possible libraries in consideration.</p>
<h3 id="neural-net">Neural net</h3>
<p>Enter the elephant in the room: <a href="https://openai.com/blog/chatgpt/">https://openai.com/blog/chatgpt/</a></p>
<p>We can use GAN + additional automated tests (buid + run + check if results match). In GAN we have descriminator, but it has to output “alikeness” of generated solution, which is float, actual tests will gave us boolean. So we need to learn AI by set of examples. Luckely, we have a lot of opensource code everywhere.</p>
<h2 id="should-we">Should we?</h2>
<p>We definitely can, but should we waste our time on that stupidity? Yes, we can create AI to pass every test that recruiting agency can imagine, and we will bypass all time limitations smoothly and all attempts to check that we are ‘real human beings’ including screen share and cameras etc etc. Because IT is our world, recruiters are aliens here.</p>
<p>But maybe we should teach them how to become native. How to rewiew the code. Learn them to look for methods, not instruments. Make the world better together.
Nope, that wont work )</p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2023-01-02T12:56:17+07:00">January 02, 2023</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/ai/" rel="tag">AI</a>,
<a class="meta-tags__link" href="/tags/code-generation/" rel="tag">Code generation</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/knockoutjs-is-nice/">Knockoutjs Is Nice</a></h1>
<div class="entry__content"><p>I used that a while ago, but looks like it still alive 😊</p>
<p>Actually, I still think it’s the best js UI framework, at least for a browser.</p>
<p>Why:</p>
<ul>
<li>MVVM is great, much better than “state” variable like in ReactJS or Vue.</li>
<li>It has nice publisher-subscriber implementation inside to serve data to controls, like “normal” ui applications for desktop.</li>
<li>Controls are louse coupled, almost independant from each other, yet can communicate</li>
<li>It’s tiny!</li>
</ul>
<p>Latest realse is 2019-11-05, though.. 3 years ago, but just before covid lockdowns.</p>
<p>Link (mostly note for myself): <a href="https://knockoutjs.com/">https://knockoutjs.com/</a></p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2022-12-23T03:00:00+07:00">December 23, 2022</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/javascript/" rel="tag">Javascript</a>,
<a class="meta-tags__link" href="/tags/knockoutjs/" rel="tag">Knockoutjs</a>,
<a class="meta-tags__link" href="/tags/note/" rel="tag">Note</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/unexpected-usefulness-of-hugo-offline-editing/">Unexpected usefulness of offline blogging with Hugo</a></h1>
<div class="entry__content"><p>Life in changing. Internet evolve and become available everywhere.. suddenly, we are more and more offline.</p>
<p>When you have cable connection and expect to be online only in the office or at home - you are always online, always with good (or at least the same) speed. Then, mobile internet shine and at first it had affected only content reading, but not content creation.</p>
<p>But now, lifestyle itself changed and people become more mobile. More and more professions become remote. That lead to more travel. And more travel lead to more time offline. Quite often: unexpectedly offline. But you still have to do your remote work in time.</p>
<blockquote>
<p>Right now I’m writing this post from airport. I don’t have local sim yet, and airport wifi disconnects every hour.</p>
</blockquote>
<p>With Hugo I stll can work with my website. Local server let me check everything, I can fix elder posts, can work around SEO, can write new.</p>
<p>What can I do with oldschool CMS which require me to be online and with stable connection? Only teke some notes to copy-paste them into CMS and edit their look later. And even copy-paste can be harmful for website layout, as for lot’s of CMS themes do not provide html validation and rely on WYSIWYG editors.</p>
<p>My flight was rescheduled twice and so back in Wordpress days I will lose 27 hours of working time. That’s a lot!</p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2022-11-23T14:21:51+07:00">November 23, 2022</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/hugo/" rel="tag">Hugo</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/spring-boot-app-to-kubernetes/">Spring Boot Multimodule App to Kubernetes</a></h1>
<div class="entry__content"><blockquote>
<p><a href="/post/mvn.spring.boot.multimodule/">Previously on the series</a>
(I will use same setup)</p>
</blockquote>
<h2 id="create-docker-image">Create docker image</h2>
<pre><code>mvn -am -pl web spring-boot:build-image
</code></pre><p>Where <code>web</code> is subproject name. According to 12factor we have to use enviromnemt variables for connections, and we will propbably have at least jdbc connection to database,
so it becomes:</p>
<pre><code>DATASOURCE_URL="jdbc:mysql://localhost:3306/dbname" \
DATASOURCE_USERNAME="username" \
DATASOURCE_PASSWORD="password" \
mvn -am -pl web spring-boot:build-image
</code></pre><p>(If it’s not multimodule, just skip <code>-am -pl web</code>)</p>
<p>When build is finished, we should get in the output:</p>
<pre><code>Successfully built image 'docker.io/library/projectName:1.0-SNAPSHOT'
</code></pre><p>Whis image name can be used to run docker container</p>
<h2 id="run-in-docker">Run in docker</h2>
<pre><code>docker run --network="host" \
-e DATASOURCE_URL="jdbc:mysql://localhost:3306/dbname" \
-e DATASOURCE_USERNAME="username" \
-e DATASOURCE_PASSWORD="password" \
-p 8080:8080 \
projectName:1.0-SNAPSHOT
</code></pre><p>Now we have the image, but kubernetes can’t access it yet. Image has to be pushed to some <code>registry</code> or loaded into minikube directly like this:</p>
<h2 id="load-image-to-minikube">Load image to minikube</h2>
<pre><code>minikube image load projectName:1.0-SNAPSHOT
</code></pre><h2 id="generate-yaml-for-kubernetes-deployment">Generate .yaml for kubernetes deployment</h2>
<pre><code>kubectl create deployment \
--image=projectName:1.0-SNAPSHOT \
--dry-run=client \
-o=yaml projectName > projectName.yaml
echo --- >> projectName.yaml
kubectl create service clusterip projectName \
--tcp=8080:8080 \
--dry-run=client \
-o=yaml >> projectName.yaml
</code></pre><h2 id="adjust-spectemplatespeccontainers">Adjust spec/template/spec/containers</h2>
<h3 id="disable-image-pull">Disable image pull</h3>
<p>As for we use local image we do not need kubernetes to pull it from Docker public registry.</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">imagePullPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Never</span><span class="w">
</span></code></pre></div><h3 id="connection-to-external-database-on-localhost">Connection to external database on localhost</h3>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">env</span><span class="p">:</span><span class="w">
</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATASOURCE_URL</span><span class="w">
</span><span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">"jdbc:mysql://host.minikube.internal:3306/projectName"</span><span class="w">
</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATASOURCE_USERNAME</span><span class="w">
</span><span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">"projectName"</span><span class="w">
</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATASOURCE_PASSWORD</span><span class="w">
</span><span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">"projectName"</span><span class="w">
</span></code></pre></div><p>Notice <code>host.minikube.internal</code> this is host name for your host machine, localhost will point to the pod itself (to guest vm).</p>
<h3 id="result-should-look-like-this">Result should look like this</h3>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">creationTimestamp</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
</span><span class="w"> </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span><span class="w"> </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"> </span><span class="nt">strategy</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span><span class="w"> </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">creationTimestamp</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
</span><span class="w"> </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"> </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span><span class="w"> </span>- <span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">projectName:1.0-SNAPSHOT</span><span class="w">
</span><span class="w"> </span><span class="nt">imagePullPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Never</span><span class="w">
</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATASOURCE_URL</span><span class="w">
</span><span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">"jdbc:mysql://host.minikube.internal:3306/projectName"</span><span class="w">
</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATASOURCE_USERNAME</span><span class="w">
</span><span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">"projectName"</span><span class="w">
</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATASOURCE_PASSWORD</span><span class="w">
</span><span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">"projectName"</span><span class="w">
</span><span class="w"> </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span>}<span class="w">
</span><span class="w"></span><span class="nt">status</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span><span class="w"></span><span class="nn">---</span><span class="w">
</span><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">creationTimestamp</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
</span><span class="w"> </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="m">8080-8080</span><span class="w">
</span><span class="w"> </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span><span class="w"> </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span><span class="w"> </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span><span class="w"> </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">projectName</span><span class="w">
</span><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span><span class="w">
</span><span class="w"></span><span class="nt">status</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">loadBalancer</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></code></pre></div><h2 id="apply-projectnameyaml">Apply projectName.yaml</h2>
<pre><code>kubectl apply -f projectName.yaml
</code></pre><p>After that <code>kubectl get pods</code> should return list of pods with state <code>Running</code> next to <code>projectName-XXXXXXX-XXX</code></p>
<h2 id="view-logs">View logs</h2>
<pre><code>kubectl logs POD_NAME
</code></pre><p>Will shouw you console output, like</p>
<pre><code> . ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.4)
...
2022-11-20 06:00:24.718 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-11-20 06:00:24.730 INFO 1 --- [ main] org.Application : Started Application in 4.718 seconds (JVM running for 4.967)
</code></pre><p>Or some errors if any.</p>
<p>Now pod is running, but it lives iside the cluster, so we need to get access from the outside.</p>
<h2 id="access-service-through-proxy">Access service through proxy</h2>
<pre><code>kubectl port-forward svc/projectName 8080:8080
</code></pre><p>Now you should be able to connect to your spring-boot application using http://localhost:8080</p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2022-11-20T10:20:49+07:00">November 20, 2022</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/kubernetes/" rel="tag">Kubernetes</a>,
<a class="meta-tags__link" href="/tags/java/" rel="tag">Java</a>,
<a class="meta-tags__link" href="/tags/spring/" rel="tag">Spring</a>,
<a class="meta-tags__link" href="/tags/microservice/" rel="tag">Microservice</a>
</span>
</div>
</div>
</article>
</div>
<div class="card card--1col">
<article class="entry card__box block">
<h1 class="entry__title title-excerpt"><a class="entry__title-link" href="/post/how-to-start-with-unittesting-python/">How to start with unit testing in python</a></h1>
<div class="entry__content"><blockquote>
<p>Sponsored repty to Vasilina and Nazar</p>
</blockquote>
<h2 id="general-sequence-for-anything-in-test-driving-development">General sequence for <em>anything</em> in Test Driving Development</h2>
<ol>
<li>Try and Fail</li>
<li>Fix and Confirm</li>
<li>Polish</li>
<li>Repeat</li>
</ol>
<p>Also knows as:</p>
<p>Red - Green - Refactoring - Repeat
(Red/Green refer to test frameworks colored output)</p>
<h3 id="so-lets-try-and-fail-first">So lets try and fail first</h3>
<p>We start with nothing: no tests, no code. Just <em><strong>empty</strong></em> project folder.</p>
<p>Let’s run tests. What we expect from running tests in empty folder?
We expect message that there are zero tests and there is nothing to run. If we get this message - that count as success.</p>
<p>Let’s try:</p>
<pre><code>$ pytest
</code></pre><p>And the output is:</p>
<pre><code>pytest: No such file or directory
</code></pre><p>We are at <code>RED</code> state here, we tried and failed.</p>
<h3 id="lets-fix-that-and-get-green-state">Let’s fix that and get GREEN state</h3>
<p>Install pytest:</p>
<pre><code>$ pip install pytest
</code></pre><p>Output:</p>
<pre><code>Installing collected packages: pytest
Successfully installed pytest-7.2.0
</code></pre><h3 id="confirm-green-state">Confirm green state</h3>
<p>Now, lets run tests again</p>
<pre><code>$ pytest
</code></pre><p>And now we get</p>
<pre><code>$ pytest
============================== test session starts ==============================
platform linux -- Python 3.10.1, pytest-7.2.0, pluggy-1.0.0
rootdir: /home/snowyurik/tmp/10
asyncio: mode=strict
collected 0 items
============================= no tests ran in 0.00s =============================
</code></pre><h3 id="polish">Polish</h3>
<p>Let’s look into our code: do we have any <em>Code Smells</em>?<br>
(“code smells” or “antipatterns” are rules to detect bad code, most notable are “duplicated code” and “magic” aka “hardcode”)</p>
<p>No code = no code smells, excellent!<br>
Refactoring is done 😎</p>
<h3 id="repeat">Repeat</h3>
<p>To start next <em>iteration</em> we have to change our expectations.<br>
Let’s expect now, that we run at least one test</p>
<h3 id="try-and-fail">Try and fail</h3>
<p>Btw, I advice you to really do so and I’m actually doing such “pointless” actions on everyday basis.</p>
<pre><code>$ pytest
</code></pre><p>And we get</p>
<pre><code>$ pytest
============================== test session starts ==============================
platform linux -- Python 3.10.1, pytest-7.2.0, pluggy-1.0.0
rootdir: /home/snowyurik/tmp/10
collected 0 items
============================= no tests ran in 0.00s =============================
</code></pre><p>Same message, but as for as our expectation changed, we can’t be satisfied with it anymore.
From now we will interpret <code> no tests ran in 0.00s</code> as <code>RED</code> state.</p>
<h3 id="lets-fix-that-and-confirm-the-fix-">Let’s fix that and confirm the fix 😊</h3>
<p>Add simplest possible test.<br>
Here are some official docs <a href="https://docs.pytest.org/en/7.2.x/getting-started.html">https://docs.pytest.org/en/7.2.x/getting-started.html</a> but who have time to read everything?<br>
That will be just empty file, test.py (by the way, that’s not correct name)</p>
<pre><code>$ touch test.py
</code></pre><p>Does it work?</p>
<pre><code>$ pytest
============================== test session starts ==============================
..
collected 0 items
============================= no tests ran in 0.00s =============================
</code></pre><p>No</p>
<p>Why?</p>
<p>Let’s have a quick look at the docs… maybe our test file was not found automatically, so lets rename it exactly like in example:</p>
<pre><code>mv test.py test_sample.py
</code></pre><p>Run <code>pytest</code> again, does it work now? No. So we need more, let’s copy-paste code from tutorial, this will be our <code>test_sample.py</code> content</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="c1"># content of test_sample.py</span>
<span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">def</span> <span class="nf">test_answer</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">func</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">==</span> <span class="mi">5</span>
</code></pre></div><p>What does this code mean? <em><strong>We don’t care</strong></em><br>
Right now all we want is to get message from <code>pytest</code> that at least one test was executed, no more, no less.</p>
<pre><code>$ pytest
============================= test session starts ==============================
...
collected 1 item
test_sample.py F [100%]
=================================== FAILURES ===================================
_________________________________ test_answer __________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_sample.py:7: AssertionError
=========================== short test summary info ============================
FAILED test_sample.py::test_answer - assert 4 == 5
============================== 1 failed in 0.05s ===============================
</code></pre><p>Success!<br>
Yep, it’s colored red, but we see that tests are executed.<br>
And pytest shows us where exactly test failed.</p>
<h3 id="lets-polish-that-and-make-our-green-state-actually-green">Let’s polish that and make our green state actually green</h3>
<p>Here is our test</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_answer</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">func</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">==</span> <span class="mi">5</span>
</code></pre></div><p>What’s going on here?<br>
Pytest (by the way, unittest for python act the same) will find functions which started with <code>test_</code> and execute them.</p>
<p>Ok, what is <code>assert</code>?<br>
<code>assert</code> equals “we excepect that the following statement is true”. So <code>assert func(3) == 5</code> mean “we expect that <code>func(3)</code> will return 5”.<br>
But it does not. So lets modify <code>func()</code></p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">2</span>
</code></pre></div><p>And run pytest again</p>
<pre><code>$ pytest
============================== test session starts ==============================
...
collected 1 item
test_sample.py . [100%]
=============================== 1 passed in 0.01s ==============================
</code></pre><p>Now our green state is actually green, nice 🙂</p>
<p>Lets see if we have some code smells here and mark them with <code>/// TODO</code></p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">x</span><span class="p">):</span> <span class="o">///</span> <span class="n">TODO</span> <span class="n">function</span> <span class="n">name</span> <span class="n">does</span> <span class="ow">not</span> <span class="n">describe</span> <span class="n">function</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">///</span> <span class="n">TODO</span> <span class="n">hardcoded</span> <span class="n">value</span> <span class="mi">2</span>
<span class="k">def</span> <span class="nf">test_answer</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">func</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">==</span> <span class="mi">5</span> <span class="o">///</span> <span class="n">TODO</span> <span class="n">hardcoded</span> <span class="n">values</span> <span class="mi">3</span> <span class="ow">and</span> <span class="mi">5</span>
</code></pre></div><p>We can’t leave it like that. Why do we have this code at first place? We just copied example. It make no sense in our project and it should not.<br>
What are we testing at first place?<br>
We are testing ability to run tests. Let’s modify the test so it will server only intendent purpose:<br>
We do not need <code>def func(x)</code> at all, so we remove that and run pytest again:</p>
<pre><code>E NameError: name 'func' is not defined
</code></pre><p>Hmm.. let’s replace <code>func(3)</code> with it’s result:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_answer</span><span class="p">():</span>
<span class="k">assert</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">5</span>
</code></pre></div><p>Green state. But we still have hardcoded 5. Lets just use <code>True</code>:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_answer</span><span class="p">():</span>
<span class="k">assert</span> <span class="bp">True</span>
</code></pre></div><p>And make test name self-explanatory</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_ifWeCanExecuteTests</span><span class="p">():</span>
<span class="k">assert</span> <span class="bp">True</span>
</code></pre></div><p>Run pytest. State is green. What else we have? Oh yes, filename. As you remember, test.py did not work, but was it filename issue or it was lack of test functions inside? We can check:</p>
<pre><code>$ mv test_sample.py test.py
$ pytest
...
============================= no tests ran in 0.00s =============================
</code></pre><p>Looks like filename should be like <code>test<undescore><something>.py</code>. Let’s try and make is self-explanatory</p>
<pre><code>$ mv test.py test_application.py
$ pytest
... [100%]
=============================== 1 passed in 0.00s ===============================
</code></pre><p>Refactoring done 🙄</p>
<h3 id="repeat-">Repeat 😅</h3>
<p>Now we are ready to implement something real. For that we need the task.<br>
Let it be:<br>
“Create validator for phone number”\</p>
<h3 id="try-and-fail-1">Try and fail</h3>
<p>We start from wring another test <em><strong>before</strong></em> any implementation</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_validatePhome</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"+1(111)11-11-111"</span><span class="p">)</span>
</code></pre></div><p>And run test</p>
<pre><code>E NameError: name 'isValid' is not defined
</code></pre><p>We are at <code>RED</code> state.</p>
<h3 id="fix-and-confirm">Fix and confirm</h3>
<p>Now our goal is to achieve GREEN state in the simplest possible way.</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">isValid</span><span class="p">():</span>
<span class="k">return</span> <span class="bp">True</span>
<span class="k">def</span> <span class="nf">test_validatePhome</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"+1(111)11-11-111"</span><span class="p">)</span>
</code></pre></div><p>Now it’s green.</p>
<h3 id="refactor">Refactor</h3>
<p>Yep, we can do that, but let’s assume we are lazy and see if TDD will force use to do that.</p>
<h3 id="repeat-1">Repeat</h3>
<p>Let’s add another test, we need <code>RED</code> state, remember.</p>
<h3 id="try-and-fail-2">Try and fail</h3>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_validatePhome</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"+1(111)11-11-111"</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"+2(222)22-22-222"</span><span class="p">)</span>
</code></pre></div><p>Pytest and.. still green. We need <code>red</code>. ANY new test make no sence. As for we need <code>red</code> we have to write test which will fail.<br>
How about that one</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_validatePhome</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"+1(111)11-11-111"</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"this is definitely not valid phone number"</span><span class="p">)</span> <span class="o">==</span> <span class="bp">False</span>
</code></pre></div><p>Pytest:</p>
<pre><code>FAILED test_application.py::test_validatePhome - AssertionError: assert True == False
</code></pre><p>Nice, <code>RED</code> state</p>
<h3 id="fix-and-confirm-1">Fix and confirm</h3>
<p>Why second “phone number” is not correct? There are many answers. Let’s say it’s too long. Looks like valid phone numbers can contain maximum 15 digits.
So let’s check that inside <code>isValid</code></p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">isValid</span><span class="p">(</span><span class="n">phone</span><span class="p">):</span>
<span class="k">if</span><span class="p">(</span> <span class="nb">len</span><span class="p">(</span><span class="n">phone</span><span class="p">)</span> <span class="o">></span> <span class="mi">15</span> <span class="p">):</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="bp">True</span>
</code></pre></div><p>And.. our first asserting failed. Because <code>len("+1(111)11-11-111")</code> is actually 16. So we see that we can’t just count symbols. We need to count digits.</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">isValid</span><span class="p">(</span><span class="n">phone</span><span class="p">):</span>
<span class="n">digits</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">symbol</span><span class="o">.</span><span class="n">isdigit</span><span class="p">()</span> <span class="k">for</span> <span class="n">symbol</span> <span class="ow">in</span> <span class="n">phone</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span> <span class="n">digits</span> <span class="o">></span> <span class="mi">15</span> <span class="p">):</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="bp">True</span>
</code></pre></div><p>Does it work? First assertion is passed, but “this is definitely not valid phone number” actually has zero digits. Which is also not correct, so:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">isValid</span><span class="p">(</span><span class="n">phone</span><span class="p">):</span>
<span class="n">digits</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">symbol</span><span class="o">.</span><span class="n">isdigit</span><span class="p">()</span> <span class="k">for</span> <span class="n">symbol</span> <span class="ow">in</span> <span class="n">phone</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span> <span class="n">digits</span> <span class="o">></span> <span class="mi">15</span> <span class="ow">or</span> <span class="n">digits</span> <span class="o"><</span> <span class="mi">6</span> <span class="p">):</span> <span class="o">//</span> <span class="mi">6</span> <span class="ow">is</span> <span class="n">minimal</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="bp">True</span>
</code></pre></div><p>And now its green 🤗</p>
<h3 id="polish-1">Polish</h3>
<p>Obviously 15 and 6 are “magic numbers” aka “hardcode”. We can turm them into constants with self-explanatory names:
And also move them to separate file together with isValid function, because it’s a mess now. Let’s say <code>phoneValidator.py</code></p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="n">MIN_PHONE_NUMBER_LENGTH</span> <span class="o">=</span> <span class="mi">6</span>
<span class="n">MAX_PHONE_NUMBER_LENGTH</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">def</span> <span class="nf">isValid</span><span class="p">(</span><span class="n">phone</span><span class="p">):</span>
<span class="n">digits</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">symbol</span><span class="o">.</span><span class="n">isdigit</span><span class="p">()</span> <span class="k">for</span> <span class="n">symbol</span> <span class="ow">in</span> <span class="n">phone</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span> <span class="n">digits</span> <span class="o">></span> <span class="n">MAX_PHONE_NUMBER_LENGTH</span> <span class="ow">or</span> <span class="n">digits</span> <span class="o"><</span> <span class="n">MIN_PHONE_NUMBER_LENGTH</span> <span class="p">):</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="bp">True</span>
</code></pre></div><p>And for our test to work we need import it, so:</p>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">phoneValidator</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">def</span> <span class="nf">test_ifWeCanExecuteTests</span><span class="p">():</span>
<span class="k">assert</span> <span class="bp">True</span>
<span class="k">def</span> <span class="nf">test_validatePhome</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"+1(111)11-11-111"</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">isValid</span><span class="p">(</span><span class="s2">"this is definitely not valid phone number"</span><span class="p">)</span> <span class="o">==</span> <span class="bp">False</span>
</code></pre></div><p>Pytest.. green 🥳</p>
<h3 id="continue-like-that">Continue like that</h3>
<p>Can you imagine another wrong phone number which will pass the test? Create the test and change implementation.<br>
No? Can you imagine correct phone number which won’t pass the test? Crete the test for false-negative result and change implementation.</p>
<p>With iterative process like that, you will get closer and closer to ideal solution.</p>
<p>Can you do it faster on-sight? Maybe. If you are good at regular expressions and phone standarts you might not need tests.</p>
<p>But to create something with test you do not have to be good at anything including test themselfves!<br>
Just keep it simple, keep it iterative, do small steps, always refactor old code and you will create <em><strong>anything</strong></em>. There is no limit.</p></div>
<div class="entry__meta meta mt tar">
<time class="entry__meta-published meta-published" datetime="2022-11-17T17:54:10+07:00">November 17, 2022</time>
<div class="entry__meta-tags meta-tags">
<span class="meta-tags__list">Tags:
<a class="meta-tags__link" href="/tags/pyhton/" rel="tag">Pyhton</a>,
<a class="meta-tags__link" href="/tags/test-driving-development/" rel="tag">Test Driving Development</a>,
<a class="meta-tags__link" href="/tags/pytest/" rel="tag">Pytest</a>
</span>
</div>
</div>
</article>
</div>
</div>
</main>
<div class="pagination block">
<span class="pagination__item pagination__item--active" data-total="2">1</span>
<a class="pagination__item pagination__item--desktop" href="/page/2/">2</a>
</div>
</div>
<footer class="footer">
<div class="footer__social social">
<a class="social__link" target="_blank" rel="noopener noreferrer" href="https://github.com/snowyurik">
<svg class="social__icon" aria-label="Github" role="img" width="32" height="32" viewBox="0 0 512 512"><path d="M335 499c14 0 12 17 12 17H165s-2-17 12-17c13 0 16-6 16-12l-1-50c-71 16-86-28-86-28-12-30-28-37-28-37-24-16 1-16 1-16 26 2 40 26 40 26 22 39 59 28 74 22 2-17 9-28 16-35-57-6-116-28-116-126 0-28 10-51 26-69-3-6-11-32 3-67 0 0 21-7 70 26 42-12 86-12 128 0 49-33 70-26 70-26 14 35 6 61 3 67 16 18 26 41 26 69 0 98-60 120-117 126 10 8 18 24 18 48l-1 70c0 6 3 12 16 12z"/></svg>
</a>
</div>
<div class="footer__copyright">© 2023 Snowyurik's Blog. <span class="footer__copyright-credits">Powered by <a href="https://gohugo.io/" rel="nofollow noopener" target="_blank">Hugo</a> and <a href="https://github.com/vimux/binario" rel="nofollow noopener" target="_blank">Binario</a> theme.</span></div>
</footer>
<script src="/js/menu.js"></script>
</body>
</html>