A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 30KB

4 jaren geleden
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Our Development Philosophy (2): Collaboration & Testing (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://www.git-tower.com/blog/dev-philosophy-2">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Our Development Philosophy (2): Collaboration & Testing (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://www.git-tower.com/blog/dev-philosophy-2">Source originale du contenu</a></h3>
  445. <p>We learned in countless situations within our own team how important these skills really are: very often when things went wrong, it had been due to a lack of testing and/or collaboration. Here are some things we've learned while growing <a href="https://www.git-tower.com/" rel="noopener noreferrer" target="_blank">Tower</a> to serve almost 100,000 users.</p>
  446. <p><br/></p>
  447. <h2>All Code Belongs to the Team</h2>
  448. <figure class="cartoon"><img src="https://www.git-tower.com/blog/content/posts/103-dev-philosophy-2/cartoon-code-belongs-to-the-team.svg" alt=""/></figure>
  449. <p><strong>Code is teamwork, not the work of any individual developer.</strong> This also means that none of the code you've written belongs to you – it belongs to the team! Looking at code in this way is helpful and liberating for a couple of reasons:</p>
  450. <ul>
  451. <li>
  452. <p><strong>It doesn't have to go your way.</strong> After understanding that code always belongs to the team, it becomes obvious that we need to comply with coding standards and guidelines. Since it's not your code you can't just write it any way you fancy. It's a common code base, so common rules must be adhered.<br/>
  453. This should also make you more tolerant when looking at your colleagues' code. You might instinctively think of all the ways you would have done it differently. But again: it doesn't have to go your way, but your team's way. This can help a lot in making you more open-minded towards other solutions and approaches.</p>
  454. </li>
  455. <li>
  456. <p><strong>Everything is your problem.</strong> When you spot a potential problem in the code, it doesn't really matter if it was your fault for not. Since all of the project's code belongs to the team – and you are part of this team – weak code from any source becomes your problem.<br/>
  457. Contrast this to an atmosphere where people are detached from their teams and their shared code: "this is not my problem" is an attitude that will never produce great products.</p>
  458. </li>
  459. <li><strong>You. You. You.</strong> I learned this one from <a href="https://opbeat.com/" rel="noopener noreferrer" target="_blank">opbeat's</a> <a href="https://twitter.com/roncohen" rel="noopener noreferrer" target="_blank">Ron Cohen</a> when I heard him speak at a conference. It's a simple reminder of our responsibilities as software developers: <strong>You</strong> are responsible for writing code. Then, <strong>you</strong> are also responsible for making it shippable or production ready. And of course, <strong>you</strong> are responsible for fixing any problems with it.<br/>
  460. In our own team, where we don't have a dedicated quality assurance team, this reminded us that developers are not only responsible for writing down lines of code – but also for the quality of that code. This responsibility is nothing that you can duck out of or hand over to someone else.</li>
  461. </ul>
  462. <div class="canned-tweet"> <p class="tweet-button"/> <blockquote> <span>"Code is teamwork, not the work of any individual developer." </span> </blockquote> </div>
  463. <h2>Sharing Knowledge with Code Reviews</h2>
  464. <p>Code reviews have a huge potential to lift your development team to new heights.</p>
  465. <p>Most obviously, discussing code in a review session can lead to a better solution than the original developer had had on her own. Two minds working together on the same problem will always have the chance to produce higher quality code.</p>
  466. <p>But other potential benefits of code reviews might be even more interesting. Shared ownership, for example, is crucial to reduce knowledge silos: whenever you find that only a single developer in your team knows a certain piece of code, you are at risk. Whenever this developer goes on holidays, gets sick, or maybe even leaves the company you'll be in trouble.<br/>
  467. But even if none of these situations occur: the fact that your team doesn't seem to have a truly shared code base is already telling. Have a look at the previous section and you'll understand why.</p>
  468. <p>Additionally, code reviews can solve the hairy problem of knowledge exchange. If, like most teams, you have a mixture of junior and senior developers, you will certainly want those junior developers to benefit from your experts' experience and knowledge. Code reviews are an excellent way to pass on knowledge in situations like these.</p>
  469. <p>And to make the process worthwhile for both sides (juniors and seniors), make sure your senior developers understand that both parties – the student and the teacher – will learn something on the way!</p>
  470. <div class="canned-tweet"> <p class="tweet-button"/> <blockquote> <span>"Senior developers should understand that they, too, might learn something when mentoring a junior colleague!" </span> </blockquote> </div>
  471. <h2>Not Everything Needs Improvement</h2>
  472. <figure class="cartoon"><img src="https://www.git-tower.com/blog/content/posts/103-dev-philosophy-2/cartoon-fast-car.svg" alt=""/></figure>
  473. <p>Since there is no such thing as a perfect solution, you could argue that everything can be improved. And you're probably right. But the interesting question is not if something can be improved – but rather if it's worthwhile to improve a certain part in your application.</p>
  474. <p>We know where our own product, <a href="https://www.git-tower.com">Tower</a>, has its weak spots. And if time was infinite, we would probably polish and improve every single bit. But with time being a scarce resource for any team you will have to set priorities. If you don't, your product will not take the direction you want it to take - or it might very well take no direction at all.</p>
  475. <p>Being able to responsibly ponder if or if not to improve something really is an art. It takes years of experience and, in the best case, even conscious training. Saying no to a lot of things is a prerequisite for achieving anything of worth.</p>
  476. <div class="canned-tweet"> <p class="tweet-button"/> <blockquote> <span>"Saying no to a lot of things is a prerequisite for achieving anything of worth." </span> </blockquote> </div>
  477. <p>True in life, and true in software development.</p>
  478. <h2>Ask Questions</h2>
  479. <p>When reading other people's code it's all too easy to assume that you would have done it better. On first encounter, this or that line might seem unnecessarily complex or even superfluous. And you might of course be right. But it's very hard to say because of one simple fact: you didn't actually <em>write</em> the code.</p>
  480. <p>From our own experience, we know that even the simplest problem can really contain a surprising and infinite amount of complexity. Only when actually working on the problem – when picking it apart, thinking about possible solutions, side effects, etc. – will one be able to understand its scope.</p>
  481. <p>Therefore, when coming across a piece of code that makes you suspicious, it's a great ground rule to <strong>assume a good amount of hidden complexity</strong>. From this point of view you can either dive deep into the code and try to understand it and the underlying problem – or you simply get into a conversation with your teammate(s) and ask some humble questions.</p>
  482. <h2>Software is Only Complete with Tests</h2>
  483. <figure class="cartoon"><img src="https://www.git-tower.com/blog/content/posts/103-dev-philosophy-2/cartoon-developer-tasks.svg" alt=""/></figure>
  484. <p>Unit tests are sometimes regarded as the icing on the cake. More appropriately, however, they would be better described as the cake's <em>base</em>.</p>
  485. <p>In smaller projects, you might get away without writing unit tests. In any project with just a slight amount of complexity, however, you will not. And, just as a side note, it's remarkable to see how many projects started simple and very soon became complex. So please don't act surprised.</p>
  486. <p>There are indeed countless reasons why unit testing is essential for us:</p>
  487. <ul>
  488. <li><strong>Making big changes, safely and quickly.</strong> In every larger project, you will have to perform refactorings at some point. But refactoring code without a decent amount of unit tests in place is a very frustrating experience: While fixing something you will often and inevitably break something else. Unit tests let you know when this happens.</li>
  489. <li><strong>Releasing to the public, confidently.</strong> If your application is shipped to paying customers, not having a solid unit test coverage should make you shiver every time you make a release.</li>
  490. <li><strong>Better code through tests.</strong> Writing good tests is one of the best preconditions to have good code in the end. This is because tests help you understand the problem you're trying to solve and create a simple, modular architecture.</li>
  491. <li><strong>Up-to-date documentation.</strong> In an ideal world, every important part of an application would be nicely documented. We would have wonderful, clean, and beautifully formatted documents that are a joy to read. And most importantly, we would update the documentation every time something in the code changes. Well... this is of course a fairytale.<br/>
  492. But having up to date documentation in general does <em>not</em> have to be a fairytale: unit tests can serve this purpose. Although probably not as <em>pleasant</em> to read as dedicated documentation, they are (almost by definition) always up to date. This makes unit tests a very valuable and reliable source of documentation.</li>
  493. </ul>
  494. <div class="canned-tweet"> <p class="tweet-button"/> <blockquote> <span>"If you have paying customers, not having unit tests for your app should make you shiver every time you ship it." </span> </blockquote> </div>
  495. <p>Good test <em>quality</em> and good test <em>coverage</em> of important code parts are mandatory for larger projects. It made us much more confident that we would always be able to deliver <a href="https://www.git-tower.com">Tower</a> with a high quality and that we would always be able to make changes (big and small) safely and effectively.</p>
  496. <h2>Tests Make Development Faster</h2>
  497. <p>At first glance, using a test-driven approach would seem to be time-consuming and slow down your progress. And indeed, writing good unit tests takes time.</p>
  498. <p>But over time, you will find that unit testing will in fact make you faster! For one thing, after thinking about your code design while writing a test, you will need less time to write the actual implementation code. You will have done a lot of the heavy lifting already by writing the tests.</p>
  499. <p>The other benefit that will make you a lot more effective in the long run is stability: the likelihood of breaking existing code when making changes is significantly reduced through tests. You will save countless hours of bug hunting that you can now invest in something more useful.</p>
  500. <h2>Fixing by Testing</h2>
  501. <figure class="cartoon"><img src="https://www.git-tower.com/blog/content/posts/103-dev-philosophy-2/cartoon-bugs-computer.svg" alt=""/></figure>
  502. <p>Bug fixing should be inseparably connected with writing tests. Of course you could just track that bloody animal down and simply fix the code. However, I guess that a lot of you are painfully familiar with the term "regression": the annoying re-emergence of a bug that you were so confident to have fixed.</p>
  503. <p>If you want to make sure (and you should!) that a bug is fixed for good, there is only on proven way - by writing a test to safeguard this piece of code.</p>
  504. <h2>Test Low-Level Components</h2>
  505. <p>While producing a <a href="https://www.git-tower.com">software product that is in daily use by almost 100,000 people</a>, we learned a lot about errors. For one thing, we learned that they are an inevitable part of the business; especially with the diverse and complex system setups that Tower has to deal with, you cannot rule out and safeguard everything. But for another thing, we also learned <em>where</em> errors mostly occured.</p>
  506. <p>In Tower (and most likely in a lot of other larger applications), most errors tend to occur in low level components. The good thing about this is that such components lend themselves very well to unit testing. In this area, a test-driven approach offers a huge chance to improve your application's overall stability and quality with a reasonable investment – by thoroughly testing low-level components.</p>
  507. <h2>Efficient Testing</h2>
  508. <figure class="cartoon"><img src="https://www.git-tower.com/blog/content/posts/103-dev-philosophy-2/cartoon-assert-test.svg" alt=""/></figure>
  509. <p>In general, having a large amount of your code covered by unit tests is a good thing. However, if you ever hear a software developer brag about his "100% test coverage", you should be wary. Testing every single line of your application code might sound like something to strive for - but it really isn't!</p>
  510. <p>On the one hand, this would of course afford an infinite amount of time. Much more importantly, however, there <em>is</em> in fact something like writing too many tests! At the latest, you will learn this when making a bigger refectoring: not only your code, but also your tests have to be maintained. When your code changes, your tests need to change, too.</p>
  511. <p>The crucial criteria is <em>value</em>. Does the test bring you enough value to justify writing it? Typically, tests are of <em>low</em> value if they test only very simple logic. In these cases, the burden of having to maintain the test outweighs the little value that it brings.</p>
  512. <p>Writing tests <em>efficiently</em>, then, means preferring fewer high-value tests over many low value tests.</p>
  513. <h2>Test, Don't Run</h2>
  514. <p>Manually testing a certain scenario in your application can really take a huge amount of time: you have to start the app, navigate your way to a certain view, and perform various additional actions to reproduce a certain scenario.</p>
  515. <p>Writing a unit test for a scenario will indeed afford some time initially. But very quickly, probably already after having to test this only a couple of times, running the test will be much faster than starting the whole application and testing your way through it manually.</p>
  516. <p>Besides being faster, however, the test will also provide better results in terms of quality and stability. In most cases, the initial effort is worth the long time benefits.</p>
  517. </article>
  518. </section>
  519. <nav id="jumpto">
  520. <p>
  521. <a href="/david/blog/">Accueil du blog</a> |
  522. <a href="https://www.git-tower.com/blog/dev-philosophy-2">Source originale</a> |
  523. <a href="/david/stream/2019/">Accueil du flux</a>
  524. </p>
  525. </nav>
  526. <footer>
  527. <div>
  528. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  529. <p>
  530. Bonjour/Hi!
  531. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  532. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  533. </p>
  534. <p>
  535. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  536. </p>
  537. <p>
  538. Voici quelques articles choisis :
  539. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  540. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  541. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  542. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  543. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  544. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  545. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  546. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  547. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  548. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  549. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  550. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  551. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  552. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  553. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  554. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  555. </p>
  556. <p>
  557. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  558. </p>
  559. <p>
  560. Je ne traque pas ta navigation mais mon
  561. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  562. conserve des logs d’accès.
  563. </p>
  564. </div>
  565. </footer>
  566. <script type="text/javascript">
  567. ;(_ => {
  568. const jumper = document.getElementById('jumper')
  569. jumper.addEventListener('click', e => {
  570. e.preventDefault()
  571. const anchor = e.target.getAttribute('href')
  572. const targetEl = document.getElementById(anchor.substring(1))
  573. targetEl.scrollIntoView({behavior: 'smooth'})
  574. })
  575. })()
  576. </script>