[{"data":1,"prerenderedAt":2679},["ShallowReactive",2],{"navigation":3,"index-en":31,"index-blogs-en":196},[4,19],{"title":5,"path":6,"stem":7,"children":8,"page":18},"En","\u002Fen","en",[9],{"title":10,"path":11,"stem":12,"children":13,"page":18},"Blog","\u002Fen\u002Fblog","en\u002Fblog",[14],{"title":15,"path":16,"stem":17},"TCR: test && commit || revert","\u002Fen\u002Fblog\u002Ftest-commit-revert","en\u002Fblog\u002Ftest-commit-revert",false,{"title":20,"path":21,"stem":22,"children":23,"page":18},"Es","\u002Fes","es",[24],{"title":10,"path":25,"stem":26,"children":27,"page":18},"\u002Fes\u002Fblog","es\u002Fblog",[28],{"title":15,"path":29,"stem":30},"\u002Fes\u002Fblog\u002Ftest-commit-revert","es\u002Fblog\u002Ftest-commit-revert",{"id":32,"title":33,"about":34,"blog":37,"body":40,"contact":41,"description":44,"education":45,"experience":76,"extension":123,"faq":124,"hero":128,"meta":165,"navigation":166,"opensource":167,"path":6,"seo":190,"stem":193,"testimonials":194,"__hash__":195},"index\u002Fen\u002Findex.yml","Hey, I'm Miguel Fernández Senior Frontend Developer & AI Engineer",{"title":35,"description":36},"About Me","I'm a senior frontend developer with 4+ years of experience building web applications and shipping products. I specialize in React, Next.js, Vue, and TypeScript, with a strong focus on AI-powered development.\n\nI recently completed a Master's in AI Development at BIG school, covering LLMs, RAG, AI agents and orchestration. Previously I worked at INDITEX Group improving performance, at Doctomatic building full features from REST API endpoints to UI, and I also created and monetized Chrome extensions.\n\nI hold a Bachelor's in Computer Engineering from Universidad de Alicante, with an Erasmus+ semester at Cracow University of Technology. I create open-source translations for Vitest, React, Vite, MDN, and Astro docs.",{"title":38,"description":39},"Latest Articles","Some of my recent thoughts",null,{"title":42,"description":43},"Get in touch","Interested in working together? Feel free to reach out.","I build web applications and ship products. Specialized in React, Vue, Next.js, and TypeScript, with a focus on AI-powered solutions. Based in Alicante, Spain.",{"title":46,"description":47,"items":48},"Education","Where I've studied and what I've learned.",[49,58,67],{"date":50,"title":51,"institution":52,"description":57},"Oct 2025 - Jan 2026","Master's in AI Development",{"name":53,"url":54,"logo":55,"color":56},"BIG school","#","i-lucide-brain","#8B5CF6","Intensive, hands-on program focused on building products with AI. Key topics: LLM fundamentals and generative AI, prompt engineering, RAG with vector databases and semantic search, AI agents and orchestration, process automation, and deployment.",{"date":59,"title":60,"institution":61,"description":66},"Sep 2017 - Feb 2022","Bachelor's in Computer Engineering",{"name":62,"url":63,"logo":64,"color":65},"Universidad de Alicante","https:\u002F\u002Fwww.ua.es","i-lucide-graduation-cap","#3B82F6","Software Engineering specialization. High Academic Performance Group in English. Cambridge C1 Advanced certification.",{"date":68,"title":69,"institution":70,"description":75},"2019 - 2020","Erasmus+ Exchange — Computer Science",{"name":71,"url":72,"logo":73,"color":74},"Cracow University of Technology","https:\u002F\u002Fwww.pk.edu.pl","i-lucide-plane","#EF4444","Semester abroad focused on Neural Networks and Artificial Intelligence.",{"title":77,"items":78},"Work Experience",[79,88,97,106,114],{"position":80,"date":81,"company":82,"description":87},"Senior Frontend Developer at","Oct 2024 - Present",{"name":83,"url":84,"logo":85,"color":86},"Token City","https:\u002F\u002Ftoken-city.com","i-lucide-building","#10B981","Owning frontend implementation and maintenance across all projects. Migrating the main SaaS dashboard from Bootstrap to multitenant SCSS architecture. Building client landing pages in Vue, Nuxt and Vite from Figma designs.",{"position":89,"date":90,"company":91,"description":96},"Freelance Web Developer at","Sep 2024 - Present",{"name":92,"url":93,"logo":94,"color":95},"Self-employed","https:\u002F\u002Fmiguelfernandez.dev","i-lucide-laptop","#6366F1","Shipping AI-powered SaaS products, client websites, and a monetized Chrome extension. Projects include Healthspan, PhotoHouseAI, Game Portrait, DeskBreak, and FotoCV.",{"position":98,"date":99,"company":100,"description":105},"Senior Front-end Developer at","Feb 2023 - Jun 2024",{"name":101,"url":102,"logo":103,"color":104},"Tempe — INDITEX Group","https:\u002F\u002Fwww.tempe.es","i-simple-icons-inditex","#000000","Restructured the main screen layout, refactored code into React Hooks for performance, and migrated legacy projects to current versions. Implemented unit tests with Jest and React Testing Library. Mentored junior developers.",{"position":107,"date":108,"company":109,"description":113},"Full Stack Developer at","Feb 2022 - Jan 2023",{"name":110,"url":111,"logo":112,"color":65},"Doctomatic","https:\u002F\u002Fwww.doctomatic.com","i-lucide-stethoscope","Built the testing environment (unit, e2e, UI). Developed full features from REST API endpoints to UI. Containerized APIs, databases and external service mocks with Docker.",{"position":115,"date":116,"company":117,"description":122},"Front-end Developer Intern at","May - Sep 2021",{"name":118,"url":119,"logo":120,"color":121},"Aire Networks","https:\u002F\u002Fwww.airenetworks.es","i-lucide-wifi","#F59E0B","Built responsive layouts with HTML5 and CSS3. Migrated components from Vanilla JS to React, implemented new views in an internal web app.","yml",{"title":125,"description":126,"categories":127},"FAQ","",[],{"links":129,"images":140},[130,134],{"label":42,"to":131,"color":132,"icon":133},"mailto:miguel@miguelfernandez.dev","primary","i-lucide-mail",{"label":135,"to":136,"color":137,"variant":138,"icon":139},"Download CV","\u002Fcv\u002Fmiguel-fernandez-cv-en.pdf","neutral","outline","i-lucide-file-down",[141,144,147,150,153,156,159,162],{"src":142,"alt":143},"\u002Fprojects\u002Fhealthspan.webp","Healthspan",{"src":145,"alt":146},"\u002Fprojects\u002Fdeskbreak.webp","DeskBreak",{"src":148,"alt":149},"\u002Fprojects\u002Fphotohouseai.webp","PhotoHouseAI",{"src":151,"alt":152},"\u002Fprojects\u002Fgame-portrait.webp","Game Portrait",{"src":154,"alt":155},"\u002Fprojects\u002Fvuewiki.webp","VueWiki",{"src":157,"alt":158},"\u002Fprojects\u002Ffotocv.webp","FotoCV",{"src":160,"alt":161},"\u002Fprojects\u002Ftourguideai.webp","TourGuideAI",{"src":163,"alt":164},"\u002Fprojects\u002Findie-hacker-game.webp","Indie Hacker Game",{},true,{"title":168,"description":169,"contributions":170},"Open Source","Contributions and community projects.",[171,175,179,182,186],{"name":172,"description":173,"url":174},"Vitest","Spanish translation (own initiative)","https:\u002F\u002Fgithub.com\u002Fmiguelfernandezdev\u002Fvitest-docs-es",{"name":176,"description":177,"url":178},"React","Docs translation","https:\u002F\u002Fgithub.com\u002Freactjs\u002Fes.react.dev",{"name":180,"description":177,"url":181},"Vite","https:\u002F\u002Fgithub.com\u002Fmiguelfernandezdev\u002Fvitejs-docs-es",{"name":183,"description":184,"url":185},"MDN Web Docs","Translated content","https:\u002F\u002Fgithub.com\u002Fmdn\u002Ftranslated-content",{"name":187,"description":188,"url":189},"Astro","Docs contributions","https:\u002F\u002Fgithub.com\u002Fwithastro\u002Fdocs",{"title":191,"description":192},"Miguel Fernández — Senior Frontend Developer & AI Engineer","Portfolio of Miguel Fernandez. Senior frontend developer and AI engineer building products that ship.","en\u002Findex",[],"xTKmP6vg79FnoD5zWd1yxTHBMm3eAG29Y5w1nSrTd24",[197],{"id":198,"title":15,"author":199,"body":202,"date":2673,"description":2674,"extension":2675,"image":413,"meta":2676,"minRead":700,"navigation":166,"path":16,"seo":2677,"stem":17,"__hash__":2678},"blog\u002Fen\u002Fblog\u002Ftest-commit-revert.md",{"name":200,"username":201},"Miguel Fernandez","miguelfernandezdev",{"type":203,"value":204,"toc":2645},"minimark",[205,210,233,236,252,259,263,279,288,291,297,300,310,313,318,323,327,335,342,362,366,371,374,394,398,405,408,414,417,421,425,432,440,443,473,477,480,489,491,524,528,531,546,548,578,582,585,610,614,617,744,748,751,790,792,851,855,864,902,904,958,962,965,992,994,1033,1037,1047,1089,1091,1146,1150,1153,1162,1223,1227,1231,1239,1246,1424,1431,1440,1447,1451,1459,1462,1466,1475,1509,1775,1782,1785,1794,1797,1812,1819,1827,1830,1844,1850,1857,1862,1876,1881,1900,1905,1928,1934,1958,1961,2465,2469,2472,2477,2480,2492,2499,2508,2514,2523,2527,2532,2544,2549,2575,2580,2612,2617,2627,2632,2641],[206,207,209],"h2",{"id":208},"introduction","Introduction",[211,212,213,214,218,219,226,227,232],"p",{},"Kent Beck first introduced the idea of ",[215,216,217],"em",{},"test && commit"," in his article ",[220,221,225],"a",{"href":222,"rel":223},"https:\u002F\u002Fmedium.com\u002F@kentbeck_7670\u002Flimbo-on-the-cheap-e4cfae840330",[224],"nofollow","Limbo on the Cheap",". In the article, he discusses how, in order to test a technique for scaling collaboration in software projects called ",[220,228,231],{"href":229,"rel":230},"https:\u002F\u002Fmedium.com\u002F@kentbeck_7670\u002Flimbo-scaling-software-collaboration-afd4f00db4b",[224],"Limbo",", he carried out its implementation and experimentation.",[211,234,235],{},"It's then that he mentions that to ensure neither developer breaks the other's code when propagating changes, he uses the following command, which runs a script that builds the system and executes the tests:",[237,238,242],"pre",{"className":239,"code":240,"language":241,"meta":126,"style":126},"language-shell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","$ .\u002Ftest && commit -am working\n","shell",[243,244,245],"code",{"__ignoreMap":126},[246,247,250],"span",{"class":248,"line":249},"line",1,[246,251,240],{},[211,253,254,255,258],{},"Although it doesn't include ",[215,256,257],{},"revert",", he indicates that if the tests failed, the changes were reverted, but he explains this more thoroughly in another article.",[206,260,262],{"id":261},"development","Development",[211,264,265,266,271,272,274,275,278],{},"The main article dedicated entirely to this technique is ",[220,267,270],{"href":268,"rel":269},"https:\u002F\u002Fmedium.com\u002F@kentbeck_7670\u002Ftest-commit-revert-870bbd756864",[224],"test && commit || revert",". In it, the previous idea of ",[215,273,217],{}," is expanded with the ",[215,276,277],{},"reset --hard"," command, meaning that if the tests fail, not only is the commit prevented but the changes are also reverted.",[237,280,282],{"className":239,"code":281,"language":241,"meta":126,"style":126},"$ .\u002Ftest && git commit -am working || git reset --hard\n",[243,283,284],{"__ignoreMap":126},[246,285,286],{"class":248,"line":249},[246,287,281],{},[211,289,290],{},"At first glance, this may seem like a very risky technique, and even Kent Beck himself was skeptical initially:",[292,293,294],"blockquote",{},[211,295,296],{},"\"Oddmund Strømme, the first programmer I've found as obsessed with symmetry as I am, suggested that if the tests failed the code should be reverted. I hated the idea so I had to try it.\"",[211,298,299],{},"In general, most people ask some of these questions (and it's completely normal) as soon as they learn what this technique is about:",[301,302,303,307],"ul",{},[304,305,306],"li",{},"How are you going to make progress if the tests always have to pass? Don't you ever fail?",[304,308,309],{},"What if you write a lot of code and it gets deleted? Doesn't that frustrate you?",[211,311,312],{},"And the best answer to these questions, although we'll discuss and explain it more thoroughly later, is the following:",[301,314,315],{},[304,316,317],{},"If you don't want a lot of incorrect code to be deleted, DON'T write a lot of code — learn to take small steps in the right direction.",[292,319,320],{},[211,321,322],{},"\"If you don't want a bunch of code wiped out then don't write a bunch of code between greens.\"",[206,324,326],{"id":325},"increments","Increments",[211,328,329,330,334],{},"The foundation of TCR is increments, as it helps find an ",[331,332,333],"strong",{},"incremental"," way to make the same change, in a better and safer way, keeping all tests \"green\". But of course, is it possible to solve big problems in a small step? The answer is that most of the time this isn't possible, which is why TCR proposes the following approach:",[211,336,337],{},[338,339],"img",{"alt":340,"src":341},"Increments in TCR","\u002Fblog\u002Ftcr\u002Fincrements-in-tcr.jpeg",[301,343,344,350,356],{},[304,345,346,349],{},[331,347,348],{},"Add a test and make it pass",": every implemented idea must have an associated test, as soon as possible, even if it's not exhaustive or only tests part of the functionality, or even if it simply passes.",[304,351,352,355],{},[331,353,354],{},"Make it pass better",": gradually, once the test passes, replace the incomplete implementation step by step with the real one.",[304,357,358,361],{},[331,359,360],{},"Make hard changes easy",": don't carry out a large number of simultaneous changes — it's better to make changes one by one safely, using for example a helper function that returns the expected value, and step by step converting the implementation into the real one.",[206,363,365],{"id":364},"comparison-with-tdd","Comparison with TDD",[367,368,370],"h3",{"id":369},"similarities","Similarities",[211,372,373],{},"TCR helps practice TDD in the following ways:",[301,375,376,382,388],{},[304,377,378,381],{},[331,379,380],{},"Don't write code that doesn't help pass a failing test",": this can't be fully controlled, since you can write code that isn't checked by the test. To control this, you should consider test coverage and aim for 100%.",[304,383,384,387],{},[331,385,386],{},"Don't write more than one unit test at a time",": this isn't possible because if it happened, you'd be forced to also implement more code to make the test pass, which would mean a much greater risk of deletion.",[304,389,390,393],{},[331,391,392],{},"Don't write more code than necessary to pass the failing test",": if you write more than necessary, you risk having it deleted.",[367,395,397],{"id":396},"differences","Differences",[211,399,400,401,404],{},"The key difference between TDD and TCR is found in the step where the test needs to pass: ",[331,402,403],{},"if the test fails, the attempt is reset",".",[211,406,407],{},"Therefore, in TCR you never reach the state where the test is \"red\", because if that happens, you return to the \"green\" state.",[211,409,410],{},[338,411],{"alt":412,"src":413},"TCR vs TDD","\u002Fblog\u002Ftcr\u002Ftcr-vs-tdd.jpeg",[211,415,416],{},"The conclusion is that TDD allows us to stay in the \"red\" phase and pass the tests comfortably, while TCR tries to never reach that phase. In a sense, TCR can be considered a version of TDD without the \"red\" state.",[206,418,420],{"id":419},"variations","Variations",[367,422,424],{"id":423},"original","Original",[211,426,427,428,431],{},"Kent Beck's original ",[215,429,430],{},"test && commit || reset --hard"," version. Among its disadvantages is, for example, the deletion of the test itself when the execution fails or a compilation error occurs.",[237,433,434],{"className":239,"code":281,"language":241,"meta":126,"style":126},[243,435,436],{"__ignoreMap":126},[246,437,438],{"class":248,"line":249},[246,439,281],{},[211,441,442],{},"In pseudocode:",[237,444,448],{"className":445,"code":446,"language":447,"meta":126,"style":126},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","if(test().success)\n    commit()\nelse\n    revert()\n","python",[243,449,450,455,461,467],{"__ignoreMap":126},[246,451,452],{"class":248,"line":249},[246,453,454],{},"if(test().success)\n",[246,456,458],{"class":248,"line":457},2,[246,459,460],{},"    commit()\n",[246,462,464],{"class":248,"line":463},3,[246,465,466],{},"else\n",[246,468,470],{"class":248,"line":469},4,[246,471,472],{},"    revert()\n",[367,474,476],{"id":475},"btcr","BTCR",[211,478,479],{},"This variant attempts to solve the original disadvantage of deletion in case of compilation failure by performing the build first.",[237,481,483],{"className":239,"code":482,"language":241,"meta":126,"style":126},"$ .\u002FbuildIt && (.\u002Ftest && git commit -am working || git reset --hard)\n",[243,484,485],{"__ignoreMap":126},[246,486,487],{"class":248,"line":249},[246,488,482],{},[211,490,442],{},[237,492,494],{"className":445,"code":493,"language":447,"meta":126,"style":126},"if(build().failed)\n    return\nif(test().success)\n    commit()\nelse\n    revert()\n",[243,495,496,501,506,510,514,519],{"__ignoreMap":126},[246,497,498],{"class":248,"line":249},[246,499,500],{},"if(build().failed)\n",[246,502,503],{"class":248,"line":457},[246,504,505],{},"    return\n",[246,507,508],{"class":248,"line":463},[246,509,454],{},[246,511,512],{"class":248,"line":469},[246,513,460],{},[246,515,517],{"class":248,"line":516},5,[246,518,466],{},[246,520,522],{"class":248,"line":521},6,[246,523,472],{},[367,525,527],{"id":526},"the-relaxed","The Relaxed",[211,529,530],{},"This variant also attempts to solve the other major disadvantage of deleting the test itself when execution fails. It's based on only removing changes in the source code, not in the directory where the tests are located.",[237,532,534],{"className":239,"code":533,"language":241,"meta":126,"style":126},"$ git checkout HEAD -- src\u002Fmain\u002F\n$ .\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[243,535,536,541],{"__ignoreMap":126},[246,537,538],{"class":248,"line":249},[246,539,540],{},"$ git checkout HEAD -- src\u002Fmain\u002F\n",[246,542,543],{"class":248,"line":457},[246,544,545],{},"$ .\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[211,547,442],{},[237,549,551],{"className":445,"code":550,"language":447,"meta":126,"style":126},"if(build().failed)\n    return\nif(test().success)\n    commit()\nelse\n    revert('src\u002Fmain')\n",[243,552,553,557,561,565,569,573],{"__ignoreMap":126},[246,554,555],{"class":248,"line":249},[246,556,500],{},[246,558,559],{"class":248,"line":457},[246,560,505],{},[246,562,563],{"class":248,"line":463},[246,564,454],{},[246,566,567],{"class":248,"line":469},[246,568,460],{},[246,570,571],{"class":248,"line":516},[246,572,466],{},[246,574,575],{"class":248,"line":521},[246,576,577],{},"    revert('src\u002Fmain')\n",[367,579,581],{"id":580},"the-gentle","The Gentle",[211,583,584],{},"This variant aims to somehow save the previously introduced changes, so they can be recovered in case of failure and the bug can be found.",[237,586,588],{"className":239,"code":587,"language":241,"meta":126,"style":126},"$ git stash drop 0 2&>\u002Fdev\u002Fnull; git add -A && git stash push\n$ .\u002FbuildIt && (git stash drop 0 2&>\u002Fdev\u002Fnull; git add -A && git stash push)\n# TCR kicks and reverts our code\n$ git stash apply\n",[243,589,590,595,600,605],{"__ignoreMap":126},[246,591,592],{"class":248,"line":249},[246,593,594],{},"$ git stash drop 0 2&>\u002Fdev\u002Fnull; git add -A && git stash push\n",[246,596,597],{"class":248,"line":457},[246,598,599],{},"$ .\u002FbuildIt && (git stash drop 0 2&>\u002Fdev\u002Fnull; git add -A && git stash push)\n",[246,601,602],{"class":248,"line":463},[246,603,604],{},"# TCR kicks and reverts our code\n",[246,606,607],{"class":248,"line":469},[246,608,609],{},"$ git stash apply\n",[367,611,613],{"id":612},"the-split","The Split",[211,615,616],{},"This consists of splitting the scripts that build the system, run tests, etc. into different files in separate directories.",[237,618,620],{"className":239,"code":619,"language":241,"meta":126,"style":126},"$ cat .\u002Ftest\n.\u002Fscripts\u002FbuildIt && (.\u002Fscripts\u002FrunTests && .\u002Fscripts\u002Fcommit || .\u002Fscripts\u002Frevert)\n\n$ tree script\nscripts\u002F\n├── buildIt\n├── commit\n├── revert\n└── runTests\n\n$ cat scripts\u002FbuildIt\n.\u002Fgradlew build -x test\n\n$ cat scripts\u002Fcommit\ngit commit -am working\n\n$ cat scripts\u002Frevert\n# git reset --hard\ngit checkout HEAD -- src\u002Fmain\u002F\n\n$ cat scripts\u002FrunTests\n.\u002Fgradlew test\n",[243,621,622,627,632,637,642,647,652,658,664,670,675,681,687,692,698,704,709,715,721,727,732,738],{"__ignoreMap":126},[246,623,624],{"class":248,"line":249},[246,625,626],{},"$ cat .\u002Ftest\n",[246,628,629],{"class":248,"line":457},[246,630,631],{},".\u002Fscripts\u002FbuildIt && (.\u002Fscripts\u002FrunTests && .\u002Fscripts\u002Fcommit || .\u002Fscripts\u002Frevert)\n",[246,633,634],{"class":248,"line":463},[246,635,636],{"emptyLinePlaceholder":166},"\n",[246,638,639],{"class":248,"line":469},[246,640,641],{},"$ tree script\n",[246,643,644],{"class":248,"line":516},[246,645,646],{},"scripts\u002F\n",[246,648,649],{"class":248,"line":521},[246,650,651],{},"├── buildIt\n",[246,653,655],{"class":248,"line":654},7,[246,656,657],{},"├── commit\n",[246,659,661],{"class":248,"line":660},8,[246,662,663],{},"├── revert\n",[246,665,667],{"class":248,"line":666},9,[246,668,669],{},"└── runTests\n",[246,671,673],{"class":248,"line":672},10,[246,674,636],{"emptyLinePlaceholder":166},[246,676,678],{"class":248,"line":677},11,[246,679,680],{},"$ cat scripts\u002FbuildIt\n",[246,682,684],{"class":248,"line":683},12,[246,685,686],{},".\u002Fgradlew build -x test\n",[246,688,690],{"class":248,"line":689},13,[246,691,636],{"emptyLinePlaceholder":166},[246,693,695],{"class":248,"line":694},14,[246,696,697],{},"$ cat scripts\u002Fcommit\n",[246,699,701],{"class":248,"line":700},15,[246,702,703],{},"git commit -am working\n",[246,705,707],{"class":248,"line":706},16,[246,708,636],{"emptyLinePlaceholder":166},[246,710,712],{"class":248,"line":711},17,[246,713,714],{},"$ cat scripts\u002Frevert\n",[246,716,718],{"class":248,"line":717},18,[246,719,720],{},"# git reset --hard\n",[246,722,724],{"class":248,"line":723},19,[246,725,726],{},"git checkout HEAD -- src\u002Fmain\u002F\n",[246,728,730],{"class":248,"line":729},20,[246,731,636],{"emptyLinePlaceholder":166},[246,733,735],{"class":248,"line":734},21,[246,736,737],{},"$ cat scripts\u002FrunTests\n",[246,739,741],{"class":248,"line":740},22,[246,742,743],{},".\u002Fgradlew test\n",[367,745,747],{"id":746},"the-buddy-continuous-tcr","The Buddy - Continuous TCR",[211,749,750],{},"Do you think running TCR manually feels too much like TDD? This variant tries to solve exactly that: as soon as an error is introduced in your code, it removes the change.",[237,752,754],{"className":239,"code":753,"language":241,"meta":126,"style":126},"while true\ndo\n    .\u002Ftcr\ndone\n\n$ cat tcr\n.\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[243,755,756,761,766,771,776,780,785],{"__ignoreMap":126},[246,757,758],{"class":248,"line":249},[246,759,760],{},"while true\n",[246,762,763],{"class":248,"line":457},[246,764,765],{},"do\n",[246,767,768],{"class":248,"line":463},[246,769,770],{},"    .\u002Ftcr\n",[246,772,773],{"class":248,"line":469},[246,774,775],{},"done\n",[246,777,778],{"class":248,"line":516},[246,779,636],{"emptyLinePlaceholder":166},[246,781,782],{"class":248,"line":521},[246,783,784],{},"$ cat tcr\n",[246,786,787],{"class":248,"line":654},[246,788,789],{},".\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[211,791,442],{},[237,793,795],{"className":445,"code":794,"language":447,"meta":126,"style":126},"while(true) {\n    tcr()\n}\nfunction tcr() {\n    if(build().failed)\n        return\n    if(test().success)\n        commit()\n    else\n        revert()\n}\n",[243,796,797,802,807,812,817,822,827,832,837,842,847],{"__ignoreMap":126},[246,798,799],{"class":248,"line":249},[246,800,801],{},"while(true) {\n",[246,803,804],{"class":248,"line":457},[246,805,806],{},"    tcr()\n",[246,808,809],{"class":248,"line":463},[246,810,811],{},"}\n",[246,813,814],{"class":248,"line":469},[246,815,816],{},"function tcr() {\n",[246,818,819],{"class":248,"line":516},[246,820,821],{},"    if(build().failed)\n",[246,823,824],{"class":248,"line":521},[246,825,826],{},"        return\n",[246,828,829],{"class":248,"line":654},[246,830,831],{},"    if(test().success)\n",[246,833,834],{"class":248,"line":660},[246,835,836],{},"        commit()\n",[246,838,839],{"class":248,"line":666},[246,840,841],{},"    else\n",[246,843,844],{"class":248,"line":672},[246,845,846],{},"        revert()\n",[246,848,849],{"class":248,"line":677},[246,850,811],{},[367,852,854],{"id":853},"the-watch-buddy","The Watch Buddy",[211,856,857,858,863],{},"This variant is very similar to the previous one, with the difference that it doesn't run in an infinite loop — it waits for a change in the source code directory. It was introduced by Alejandro Marcu in a ",[220,859,862],{"href":860,"rel":861},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=l5R05yMluxw&feature=youtu.be",[224],"video"," where he experiments with TCR.",[237,865,867],{"className":239,"code":866,"language":241,"meta":126,"style":126},"while true\ndo\n    inotifywait -r -e modify .\n    .\u002Ftcr\ndone\n\n$ cat tcr\n.\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[243,868,869,873,877,882,886,890,894,898],{"__ignoreMap":126},[246,870,871],{"class":248,"line":249},[246,872,760],{},[246,874,875],{"class":248,"line":457},[246,876,765],{},[246,878,879],{"class":248,"line":463},[246,880,881],{},"    inotifywait -r -e modify .\n",[246,883,884],{"class":248,"line":469},[246,885,770],{},[246,887,888],{"class":248,"line":516},[246,889,775],{},[246,891,892],{"class":248,"line":521},[246,893,636],{"emptyLinePlaceholder":166},[246,895,896],{"class":248,"line":654},[246,897,784],{},[246,899,900],{"class":248,"line":660},[246,901,789],{},[211,903,442],{},[237,905,907],{"className":445,"code":906,"language":447,"meta":126,"style":126},"while(true) {\n    block_until_change_in_directory('src')\n    tcr()\n}\nfunction tcr() {\n    if(build().failed)\n        return\n    if(test().success)\n        commit()\n    else\n        revert()\n}\n",[243,908,909,913,918,922,926,930,934,938,942,946,950,954],{"__ignoreMap":126},[246,910,911],{"class":248,"line":249},[246,912,801],{},[246,914,915],{"class":248,"line":457},[246,916,917],{},"    block_until_change_in_directory('src')\n",[246,919,920],{"class":248,"line":463},[246,921,806],{},[246,923,924],{"class":248,"line":469},[246,925,811],{},[246,927,928],{"class":248,"line":516},[246,929,816],{},[246,931,932],{"class":248,"line":521},[246,933,821],{},[246,935,936],{"class":248,"line":654},[246,937,826],{},[246,939,940],{"class":248,"line":660},[246,941,831],{},[246,943,944],{"class":248,"line":666},[246,945,836],{},[246,947,948],{"class":248,"line":672},[246,949,841],{},[246,951,952],{"class":248,"line":677},[246,953,846],{},[246,955,956],{"class":248,"line":683},[246,957,811],{},[367,959,961],{"id":960},"the-collaborator","The Collaborator",[211,963,964],{},"This variant includes a new script that handles pushing changes and synchronizing teammates' changes when applicable.",[237,966,968],{"className":239,"code":967,"language":241,"meta":126,"style":126},"while true\ndo\n    git pull --rebase\n    git push origin master\ndone\n",[243,969,970,974,978,983,988],{"__ignoreMap":126},[246,971,972],{"class":248,"line":249},[246,973,760],{},[246,975,976],{"class":248,"line":457},[246,977,765],{},[246,979,980],{"class":248,"line":463},[246,981,982],{},"    git pull --rebase\n",[246,984,985],{"class":248,"line":469},[246,986,987],{},"    git push origin master\n",[246,989,990],{"class":248,"line":516},[246,991,775],{},[211,993,442],{},[237,995,997],{"className":445,"code":996,"language":447,"meta":126,"style":126},"async {\n    while(true) {\n        Git.pull('rebase')\n        Git.push('origin', 'master')\n    }\n}\n.\u002Ftcr\n",[243,998,999,1004,1009,1014,1019,1024,1028],{"__ignoreMap":126},[246,1000,1001],{"class":248,"line":249},[246,1002,1003],{},"async {\n",[246,1005,1006],{"class":248,"line":457},[246,1007,1008],{},"    while(true) {\n",[246,1010,1011],{"class":248,"line":463},[246,1012,1013],{},"        Git.pull('rebase')\n",[246,1015,1016],{"class":248,"line":469},[246,1017,1018],{},"        Git.push('origin', 'master')\n",[246,1020,1021],{"class":248,"line":516},[246,1022,1023],{},"    }\n",[246,1025,1026],{"class":248,"line":521},[246,1027,811],{},[246,1029,1030],{"class":248,"line":654},[246,1031,1032],{},".\u002Ftcr\n",[367,1034,1036],{"id":1035},"local-buddy-remote-team","Local Buddy, Remote Team",[211,1038,1039,1040,1042,1043,1046],{},"A combination of ",[215,1041,961],{}," and ",[215,1044,1045],{},"The Buddy"," variants:",[237,1048,1050],{"className":239,"code":1049,"language":241,"meta":126,"style":126},"do\n    git pull --rebase\n    git push origin master\ndone\n## Open new Tab\nwhile true\ndo\n    .\u002Ftcr\ndone\n",[243,1051,1052,1056,1060,1064,1068,1073,1077,1081,1085],{"__ignoreMap":126},[246,1053,1054],{"class":248,"line":249},[246,1055,765],{},[246,1057,1058],{"class":248,"line":457},[246,1059,982],{},[246,1061,1062],{"class":248,"line":463},[246,1063,987],{},[246,1065,1066],{"class":248,"line":469},[246,1067,775],{},[246,1069,1070],{"class":248,"line":516},[246,1071,1072],{},"## Open new Tab\n",[246,1074,1075],{"class":248,"line":521},[246,1076,760],{},[246,1078,1079],{"class":248,"line":654},[246,1080,765],{},[246,1082,1083],{"class":248,"line":660},[246,1084,770],{},[246,1086,1087],{"class":248,"line":666},[246,1088,775],{},[211,1090,442],{},[237,1092,1094],{"className":445,"code":1093,"language":447,"meta":126,"style":126},"async {\n    while(true) {\n        Git.pull('rebase')\n        Git.push('origin', 'master')\n    }\n}\nfunction tcr() { ... }\nasync {\n    while(true) {\n        tcr()\n    }\n}\n",[243,1095,1096,1100,1104,1108,1112,1116,1120,1125,1129,1133,1138,1142],{"__ignoreMap":126},[246,1097,1098],{"class":248,"line":249},[246,1099,1003],{},[246,1101,1102],{"class":248,"line":457},[246,1103,1008],{},[246,1105,1106],{"class":248,"line":463},[246,1107,1013],{},[246,1109,1110],{"class":248,"line":469},[246,1111,1018],{},[246,1113,1114],{"class":248,"line":516},[246,1115,1023],{},[246,1117,1118],{"class":248,"line":521},[246,1119,811],{},[246,1121,1122],{"class":248,"line":654},[246,1123,1124],{},"function tcr() { ... }\n",[246,1126,1127],{"class":248,"line":660},[246,1128,1003],{},[246,1130,1131],{"class":248,"line":666},[246,1132,1008],{},[246,1134,1135],{"class":248,"line":672},[246,1136,1137],{},"        tcr()\n",[246,1139,1140],{"class":248,"line":677},[246,1141,1023],{},[246,1143,1144],{"class":248,"line":683},[246,1145,811],{},[367,1147,1149],{"id":1148},"the-storyteller-beyond-the-buddy","The Storyteller: Beyond the Buddy",[211,1151,1152],{},"The goal of this variant is to, instead of simply reverting all changes that caused the tests to fail, communicate the error by indicating the line, file, etc. and asking the user whether they want to revert their changes or not.",[211,1154,1155,1156,1161],{},"Thomas Deniffel explains this variant in an ",[220,1157,1160],{"href":1158,"rel":1159},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-variant-the-storyteller-32c8fdb146f0",[224],"article",". In it, besides explaining this TCR variant, he mentions that his goal is to create an implementation that works with a chat or even a voice interface.",[237,1163,1165],{"className":239,"code":1164,"language":241,"meta":126,"style":126},"# The Buddy\nwhile true\ndo\n    build && ( test && commit || revert )\ndone\n\n# Communicate in 'revert'\n$ cat revert\nmessageContent = Git.getDiffAndWhatToReset\nmessage = MessageGenerator.generateWith(messageContent)\nGit.reset\nprint message\n",[243,1166,1167,1172,1176,1180,1185,1189,1193,1198,1203,1208,1213,1218],{"__ignoreMap":126},[246,1168,1169],{"class":248,"line":249},[246,1170,1171],{},"# The Buddy\n",[246,1173,1174],{"class":248,"line":457},[246,1175,760],{},[246,1177,1178],{"class":248,"line":463},[246,1179,765],{},[246,1181,1182],{"class":248,"line":469},[246,1183,1184],{},"    build && ( test && commit || revert )\n",[246,1186,1187],{"class":248,"line":516},[246,1188,775],{},[246,1190,1191],{"class":248,"line":521},[246,1192,636],{"emptyLinePlaceholder":166},[246,1194,1195],{"class":248,"line":654},[246,1196,1197],{},"# Communicate in 'revert'\n",[246,1199,1200],{"class":248,"line":660},[246,1201,1202],{},"$ cat revert\n",[246,1204,1205],{"class":248,"line":666},[246,1206,1207],{},"messageContent = Git.getDiffAndWhatToReset\n",[246,1209,1210],{"class":248,"line":672},[246,1211,1212],{},"message = MessageGenerator.generateWith(messageContent)\n",[246,1214,1215],{"class":248,"line":677},[246,1216,1217],{},"Git.reset\n",[246,1219,1220],{"class":248,"line":683},[246,1221,1222],{},"print message\n",[206,1224,1226],{"id":1225},"example","Example",[367,1228,1230],{"id":1229},"initial-setup","Initial Setup",[211,1232,1233,1234,404],{},"Install the Visual Studio Code extension ",[220,1235,1238],{"href":1236,"rel":1237},"https:\u002F\u002Fgithub.com\u002Femeraldwalk\u002Fvscode-runonsave",[224],"Run on Save",[211,1240,1241,1242,1245],{},"In the workspace configuration file ",[243,1243,1244],{},"rope.code-workspace"," or in the global VSCode settings, add the command to the extension configuration:",[237,1247,1251],{"className":1248,"code":1249,"language":1250,"meta":126,"style":126},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"folders\": [\n    {\n      \"path\": \".\"\n    }\n  ],\n  \"settings\": {\n    \"emeraldwalk.runonsave\": {\n      \"commands\": [\n        {\n          \"match\": \".*py\",\n          \"cmd\": \"cd ${workspaceFolder} && python ${file} && git commit -am working || git reset --hard\"\n        }\n      ]\n    }\n  }\n}\n","json",[243,1252,1253,1259,1277,1282,1304,1308,1313,1327,1341,1355,1360,1383,1401,1406,1411,1415,1420],{"__ignoreMap":126},[246,1254,1255],{"class":248,"line":249},[246,1256,1258],{"class":1257},"sMK4o","{\n",[246,1260,1261,1264,1268,1271,1274],{"class":248,"line":457},[246,1262,1263],{"class":1257},"  \"",[246,1265,1267],{"class":1266},"spNyl","folders",[246,1269,1270],{"class":1257},"\"",[246,1272,1273],{"class":1257},":",[246,1275,1276],{"class":1257}," [\n",[246,1278,1279],{"class":248,"line":463},[246,1280,1281],{"class":1257},"    {\n",[246,1283,1284,1287,1291,1293,1295,1298,1301],{"class":248,"line":469},[246,1285,1286],{"class":1257},"      \"",[246,1288,1290],{"class":1289},"sBMFI","path",[246,1292,1270],{"class":1257},[246,1294,1273],{"class":1257},[246,1296,1297],{"class":1257}," \"",[246,1299,404],{"class":1300},"sfazB",[246,1302,1303],{"class":1257},"\"\n",[246,1305,1306],{"class":248,"line":516},[246,1307,1023],{"class":1257},[246,1309,1310],{"class":248,"line":521},[246,1311,1312],{"class":1257},"  ],\n",[246,1314,1315,1317,1320,1322,1324],{"class":248,"line":654},[246,1316,1263],{"class":1257},[246,1318,1319],{"class":1266},"settings",[246,1321,1270],{"class":1257},[246,1323,1273],{"class":1257},[246,1325,1326],{"class":1257}," {\n",[246,1328,1329,1332,1335,1337,1339],{"class":248,"line":660},[246,1330,1331],{"class":1257},"    \"",[246,1333,1334],{"class":1289},"emeraldwalk.runonsave",[246,1336,1270],{"class":1257},[246,1338,1273],{"class":1257},[246,1340,1326],{"class":1257},[246,1342,1343,1345,1349,1351,1353],{"class":248,"line":666},[246,1344,1286],{"class":1257},[246,1346,1348],{"class":1347},"sbssI","commands",[246,1350,1270],{"class":1257},[246,1352,1273],{"class":1257},[246,1354,1276],{"class":1257},[246,1356,1357],{"class":248,"line":672},[246,1358,1359],{"class":1257},"        {\n",[246,1361,1362,1365,1369,1371,1373,1375,1378,1380],{"class":248,"line":677},[246,1363,1364],{"class":1257},"          \"",[246,1366,1368],{"class":1367},"swJcz","match",[246,1370,1270],{"class":1257},[246,1372,1273],{"class":1257},[246,1374,1297],{"class":1257},[246,1376,1377],{"class":1300},".*py",[246,1379,1270],{"class":1257},[246,1381,1382],{"class":1257},",\n",[246,1384,1385,1387,1390,1392,1394,1396,1399],{"class":248,"line":683},[246,1386,1364],{"class":1257},[246,1388,1389],{"class":1367},"cmd",[246,1391,1270],{"class":1257},[246,1393,1273],{"class":1257},[246,1395,1297],{"class":1257},[246,1397,1398],{"class":1300},"cd ${workspaceFolder} && python ${file} && git commit -am working || git reset --hard",[246,1400,1303],{"class":1257},[246,1402,1403],{"class":248,"line":689},[246,1404,1405],{"class":1257},"        }\n",[246,1407,1408],{"class":248,"line":694},[246,1409,1410],{"class":1257},"      ]\n",[246,1412,1413],{"class":248,"line":700},[246,1414,1023],{"class":1257},[246,1416,1417],{"class":248,"line":706},[246,1418,1419],{"class":1257},"  }\n",[246,1421,1422],{"class":248,"line":711},[246,1423,811],{"class":1257},[211,1425,1426,1427,1430],{},"The command will execute every time a file with the ",[243,1428,1429],{},".py"," extension (Python file) is saved in the project.",[237,1432,1434],{"className":239,"code":1433,"language":241,"meta":126,"style":126},"cd ${workspaceFolder} && python ${file}\n",[243,1435,1436],{"__ignoreMap":126},[246,1437,1438],{"class":248,"line":249},[246,1439,1433],{},[211,1441,1442,1443,1446],{},"It's necessary to navigate to the workspace project directory. Also, in this case, we'll execute the source code with Python instead of a ",[243,1444,1445],{},"test"," script since the tests are at the end of the file.",[367,1448,1450],{"id":1449},"github-repository","GitHub Repository",[211,1452,1453,1454,404],{},"I've created a public repository on GitHub with the complete example code that Kent Beck demonstrates in the ",[220,1455,1458],{"href":1456,"rel":1457},"https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLlmVY7qtgT_nhLyIbeAaUlFOWbWT5y53t",[224],"TCR Rope in Python Playlist",[211,1460,1461],{},"After the initial setup, the following section demonstrates how TCR works through selected steps from Kent Beck's tutorial.",[367,1463,1465],{"id":1464},"rope-in-python","Rope in Python",[211,1467,1468,1469,1474],{},"Initially, we have the following code, corresponding to the ",[220,1470,1473],{"href":1471,"rel":1472},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRope_(data_structure)",[224],"Rope"," data structure, with:",[301,1476,1477,1484,1497,1503],{},[304,1478,1479,1480,1483],{},"the ",[331,1481,1482],{},"to_rope(string)"," method, which converts a Rope to the String data type.",[304,1485,1479,1486,1489,1490,1042,1493,1496],{},[331,1487,1488],{},"Rope class"," with the ",[215,1491,1492],{},"substring",[215,1494,1495],{},"concatenate"," methods already implemented.",[304,1498,1479,1499,1502],{},[331,1500,1501],{},"String, Substring and Concatenation classes"," with their constructors and toString methods.",[304,1504,1479,1505,1508],{},[331,1506,1507],{},"equals(rope, expected)"," method and the tests that have already been verified.",[237,1510,1512],{"className":445,"code":1511,"language":447,"meta":126,"style":126},"# Rope\n\n# to do\n# insert\n# delete\n\n# API\ndef to_rope(string):\n    return String(string)\n\nclass Rope:\n    def substring(self, start, length):\n        return Substring(self, start, length)\n    def concatenate(self, right):\n        return Concatenation(self, right)\n\nclass String(Rope):\n    def __init__(self, string):\n        self.string = string\n    def __str__(self):\n        return self.string\n\nclass Substring(Rope):\n    def __init__(self, rope, start, length):\n        self.rope = rope\n        self.start = start\n        self.length = length\n    def __str__(self):\n        return str(self.rope)[self.start : self.start + self.length]\n\nclass Concatenation(Rope):\n    def __init__(self, left, right):\n        self.left = left\n        self.right = right\n    def __str__(self):\n        return str(self.left) + str(self.right)\n\n# Testing Framework\ndef equals(rope, expected):\n    actual = str(rope)\n    if actual == expected:\n        return\n    print(actual, \" didn't equal \", expected)\n    raise Exception()\n\nequals(to_rope(\"abc\"), \"abc\")\nequals(to_rope(\"abcde\").substring(1, 3), \"bcd\")\nequals(to_rope(\"abcde\").substring(1, 3).substring(1,1), \"c\")\nequals(to_rope(\"abc\").concatenate(to_rope(\"de\")), \"abcde\")\n",[243,1513,1514,1519,1523,1528,1533,1538,1542,1547,1552,1557,1561,1566,1571,1576,1581,1586,1590,1595,1600,1605,1610,1615,1619,1625,1631,1637,1643,1649,1654,1660,1665,1671,1677,1683,1689,1694,1700,1705,1711,1717,1723,1729,1734,1740,1746,1751,1757,1763,1769],{"__ignoreMap":126},[246,1515,1516],{"class":248,"line":249},[246,1517,1518],{},"# Rope\n",[246,1520,1521],{"class":248,"line":457},[246,1522,636],{"emptyLinePlaceholder":166},[246,1524,1525],{"class":248,"line":463},[246,1526,1527],{},"# to do\n",[246,1529,1530],{"class":248,"line":469},[246,1531,1532],{},"# insert\n",[246,1534,1535],{"class":248,"line":516},[246,1536,1537],{},"# delete\n",[246,1539,1540],{"class":248,"line":521},[246,1541,636],{"emptyLinePlaceholder":166},[246,1543,1544],{"class":248,"line":654},[246,1545,1546],{},"# API\n",[246,1548,1549],{"class":248,"line":660},[246,1550,1551],{},"def to_rope(string):\n",[246,1553,1554],{"class":248,"line":666},[246,1555,1556],{},"    return String(string)\n",[246,1558,1559],{"class":248,"line":672},[246,1560,636],{"emptyLinePlaceholder":166},[246,1562,1563],{"class":248,"line":677},[246,1564,1565],{},"class Rope:\n",[246,1567,1568],{"class":248,"line":683},[246,1569,1570],{},"    def substring(self, start, length):\n",[246,1572,1573],{"class":248,"line":689},[246,1574,1575],{},"        return Substring(self, start, length)\n",[246,1577,1578],{"class":248,"line":694},[246,1579,1580],{},"    def concatenate(self, right):\n",[246,1582,1583],{"class":248,"line":700},[246,1584,1585],{},"        return Concatenation(self, right)\n",[246,1587,1588],{"class":248,"line":706},[246,1589,636],{"emptyLinePlaceholder":166},[246,1591,1592],{"class":248,"line":711},[246,1593,1594],{},"class String(Rope):\n",[246,1596,1597],{"class":248,"line":717},[246,1598,1599],{},"    def __init__(self, string):\n",[246,1601,1602],{"class":248,"line":723},[246,1603,1604],{},"        self.string = string\n",[246,1606,1607],{"class":248,"line":729},[246,1608,1609],{},"    def __str__(self):\n",[246,1611,1612],{"class":248,"line":734},[246,1613,1614],{},"        return self.string\n",[246,1616,1617],{"class":248,"line":740},[246,1618,636],{"emptyLinePlaceholder":166},[246,1620,1622],{"class":248,"line":1621},23,[246,1623,1624],{},"class Substring(Rope):\n",[246,1626,1628],{"class":248,"line":1627},24,[246,1629,1630],{},"    def __init__(self, rope, start, length):\n",[246,1632,1634],{"class":248,"line":1633},25,[246,1635,1636],{},"        self.rope = rope\n",[246,1638,1640],{"class":248,"line":1639},26,[246,1641,1642],{},"        self.start = start\n",[246,1644,1646],{"class":248,"line":1645},27,[246,1647,1648],{},"        self.length = length\n",[246,1650,1652],{"class":248,"line":1651},28,[246,1653,1609],{},[246,1655,1657],{"class":248,"line":1656},29,[246,1658,1659],{},"        return str(self.rope)[self.start : self.start + self.length]\n",[246,1661,1663],{"class":248,"line":1662},30,[246,1664,636],{"emptyLinePlaceholder":166},[246,1666,1668],{"class":248,"line":1667},31,[246,1669,1670],{},"class Concatenation(Rope):\n",[246,1672,1674],{"class":248,"line":1673},32,[246,1675,1676],{},"    def __init__(self, left, right):\n",[246,1678,1680],{"class":248,"line":1679},33,[246,1681,1682],{},"        self.left = left\n",[246,1684,1686],{"class":248,"line":1685},34,[246,1687,1688],{},"        self.right = right\n",[246,1690,1692],{"class":248,"line":1691},35,[246,1693,1609],{},[246,1695,1697],{"class":248,"line":1696},36,[246,1698,1699],{},"        return str(self.left) + str(self.right)\n",[246,1701,1703],{"class":248,"line":1702},37,[246,1704,636],{"emptyLinePlaceholder":166},[246,1706,1708],{"class":248,"line":1707},38,[246,1709,1710],{},"# Testing Framework\n",[246,1712,1714],{"class":248,"line":1713},39,[246,1715,1716],{},"def equals(rope, expected):\n",[246,1718,1720],{"class":248,"line":1719},40,[246,1721,1722],{},"    actual = str(rope)\n",[246,1724,1726],{"class":248,"line":1725},41,[246,1727,1728],{},"    if actual == expected:\n",[246,1730,1732],{"class":248,"line":1731},42,[246,1733,826],{},[246,1735,1737],{"class":248,"line":1736},43,[246,1738,1739],{},"    print(actual, \" didn't equal \", expected)\n",[246,1741,1743],{"class":248,"line":1742},44,[246,1744,1745],{},"    raise Exception()\n",[246,1747,1749],{"class":248,"line":1748},45,[246,1750,636],{"emptyLinePlaceholder":166},[246,1752,1754],{"class":248,"line":1753},46,[246,1755,1756],{},"equals(to_rope(\"abc\"), \"abc\")\n",[246,1758,1760],{"class":248,"line":1759},47,[246,1761,1762],{},"equals(to_rope(\"abcde\").substring(1, 3), \"bcd\")\n",[246,1764,1766],{"class":248,"line":1765},48,[246,1767,1768],{},"equals(to_rope(\"abcde\").substring(1, 3).substring(1,1), \"c\")\n",[246,1770,1772],{"class":248,"line":1771},49,[246,1773,1774],{},"equals(to_rope(\"abc\").concatenate(to_rope(\"de\")), \"abcde\")\n",[211,1776,1777,1778,1781],{},"We're going to implement the ",[331,1779,1780],{},"Delete"," method.",[211,1783,1784],{},"First, we create the new test:",[237,1786,1788],{"className":445,"code":1787,"language":447,"meta":126,"style":126},"equals(to_rope(\"abcde\").delete(1, 3), \"ae\")\n",[243,1789,1790],{"__ignoreMap":126},[246,1791,1792],{"class":248,"line":249},[246,1793,1787],{},[211,1795,1796],{},"Next, we define the method as simply as possible, even if the implementation is fake:",[237,1798,1800],{"className":445,"code":1799,"language":447,"meta":126,"style":126},"def delete(self, start, length):\n    \"ae\"\n",[243,1801,1802,1807],{"__ignoreMap":126},[246,1803,1804],{"class":248,"line":249},[246,1805,1806],{},"def delete(self, start, length):\n",[246,1808,1809],{"class":248,"line":457},[246,1810,1811],{},"    \"ae\"\n",[211,1813,1814,1815,1818],{},"Since we forgot to return the result with ",[215,1816,1817],{},"return",", TCR deletes both the test and the new code. In the console, we see the following error:",[237,1820,1825],{"className":1821,"code":1823,"language":1824},[1822],"language-text","None didn't equal ae\nTraceback (most recent call last):\n    File \"D:\u002Frepos\u002Ftcr-rope\u002Frope.py\", line 64, in \u003Cmodule>\n        equals(to_rope(\"abcde\").delete(1, 3), \"ae\")\n    File \"D:\u002Frepos\u002Ftcr-rope\u002Frope.py\", line 56, in equals\n        raise Exception()\nException\nHEAD is now at a2bf467 working\n","text",[243,1826,1823],{"__ignoreMap":126},[211,1828,1829],{},"After rewriting the test, this time we write the code correctly:",[237,1831,1833],{"className":445,"code":1832,"language":447,"meta":126,"style":126},"def delete(self, start, length):\n    return \"ae\"\n",[243,1834,1835,1839],{"__ignoreMap":126},[246,1836,1837],{"class":248,"line":249},[246,1838,1806],{},[246,1840,1841],{"class":248,"line":457},[246,1842,1843],{},"    return \"ae\"\n",[237,1845,1848],{"className":1846,"code":1847,"language":1824},[1822],"[master e1db4d5] working\n 1 file changed, 4 insertions(+), 1 deletion(-)\n",[243,1849,1847],{"__ignoreMap":126},[211,1851,1852,1853,1856],{},"Then, we make small incremental changes until the entire ",[215,1854,1855],{},"delete"," code is implemented, saving frequently to verify the test passes:",[211,1858,1859],{},[331,1860,1861],{},"Step 1:",[237,1863,1865],{"className":445,"code":1864,"language":447,"meta":126,"style":126},"def delete(self, start, length):\n    return \"a\" + \"e\"\n",[243,1866,1867,1871],{"__ignoreMap":126},[246,1868,1869],{"class":248,"line":249},[246,1870,1806],{},[246,1872,1873],{"class":248,"line":457},[246,1874,1875],{},"    return \"a\" + \"e\"\n",[211,1877,1878],{},[331,1879,1880],{},"Step 2:",[237,1882,1884],{"className":445,"code":1883,"language":447,"meta":126,"style":126},"def delete(self, start, length):\n    left = \"a\"\n    return left + \"e\"\n",[243,1885,1886,1890,1895],{"__ignoreMap":126},[246,1887,1888],{"class":248,"line":249},[246,1889,1806],{},[246,1891,1892],{"class":248,"line":457},[246,1893,1894],{},"    left = \"a\"\n",[246,1896,1897],{"class":248,"line":463},[246,1898,1899],{},"    return left + \"e\"\n",[211,1901,1902],{},[331,1903,1904],{},"Step 3:",[237,1906,1908],{"className":445,"code":1907,"language":447,"meta":126,"style":126},"def delete(self, start, length):\n    left = \"a\"\n    right = \"e\"\n    return left + right\n",[243,1909,1910,1914,1918,1923],{"__ignoreMap":126},[246,1911,1912],{"class":248,"line":249},[246,1913,1806],{},[246,1915,1916],{"class":248,"line":457},[246,1917,1894],{},[246,1919,1920],{"class":248,"line":463},[246,1921,1922],{},"    right = \"e\"\n",[246,1924,1925],{"class":248,"line":469},[246,1926,1927],{},"    return left + right\n",[211,1929,1930,1931,1933],{},"And so on until reaching the final implementation of the ",[215,1932,1855],{}," method:",[237,1935,1937],{"className":445,"code":1936,"language":447,"meta":126,"style":126},"def delete(self, start, length):\n    left = self.substring(0, start)\n    right = self.substring(start + length, len(self.string) - start - length)\n    return left.concatenate(right)\n",[243,1938,1939,1943,1948,1953],{"__ignoreMap":126},[246,1940,1941],{"class":248,"line":249},[246,1942,1806],{},[246,1944,1945],{"class":248,"line":457},[246,1946,1947],{},"    left = self.substring(0, start)\n",[246,1949,1950],{"class":248,"line":463},[246,1951,1952],{},"    right = self.substring(start + length, len(self.string) - start - length)\n",[246,1954,1955],{"class":248,"line":469},[246,1956,1957],{},"    return left.concatenate(right)\n",[211,1959,1960],{},"The final result, applying TCR to the complete Rope implementation, is as follows:",[237,1962,1964],{"className":445,"code":1963,"language":447,"meta":126,"style":126},"# Rope\n\n# API\ndef to_rope(string):\n    return String(string)\n\n# Implementation\nclass Rope:\n    def delete(self, start, length):\n        left = self[0:start]\n        right = self[start + length : len(self)]\n        return left + right\n\n    def insert(self, rope, start):\n        left = self[0:start]\n        right = self[start : len(self)]\n        return left + rope + right\n\n    def __add__(self, addend):\n        return Concatenation(self, addend)\n\n    def __getitem__(self, index):\n        if type(index) == int:\n            return self.__get_single_item__(index)\n        return Substring(self, index.start, index.stop - index.start)\n\n    def __len__(self):\n        raise Exception(\"Should have been overriden\")\n\n    def __get_single_item__(self, index):\n        raise Exception(\"Should have been overriden\")\n\n\nclass String(Rope):\n    def __init__(self, string):\n        self.string = string\n\n    def __str__(self):\n        return self.string\n\n    def __len__(self):\n        return len(self.string)\n\n    def __get_single_item__(self, index):\n        return self.string[index]\n\nclass Substring(Rope):\n    def __init__(self, rope, start, length):\n        self.rope = rope\n        self.start = start\n        self.leng = length\n\n    def __str__(self):\n        return str(self.rope)[self.start : self.start + self.leng]\n\n    def __len__(self):\n        return self.leng\n\n    def __get_single_item__(self, index):\n        return self.rope[index + self.start]\n\nclass Concatenation(Rope):\n    def __init__(self, left, right):\n        self.left = left\n        self.right = right\n\n    def __str__(self):\n        return str(self.left) + str(self.right)\n\n    def __len__(self):\n        return len(self.left) + len(self.right)\n\n    def __get_single_item__(self, index):\n        if index \u003C len(self.left):\n            return self.left[index]\n        else:\n            return self.right[index - len(self.left)]\n\n\n# Testing Framework\ndef equals(rope, expected):\n    actual = str(rope)\n    if actual == expected:\n        return\n    print(actual, \"didn't equal\", expected)\n    raise Exception()\n\n\nequals(to_rope(\"abc\"), \"abc\")\nequals(to_rope(\"abcde\")[1:4], \"bcd\")\nequals(to_rope(\"abcde\")[1:4][1:2], \"c\")\nequals(to_rope(\"abc\") + to_rope(\"de\"), \"abcde\")\nequals(to_rope(\"abcde\").delete(1, 3), \"ae\")\n\nassert len(to_rope(\"abcde\")[1:4]) == 3\nassert len(to_rope(\"abc\") + to_rope(\"de\")) == 5\n\nequals(to_rope(\"abe\").insert(to_rope(\"cd\"), 2), \"abcde\")\n\nequals(to_rope(\"abcde\")[3], \"d\")\nequals((to_rope(\"abc\") + to_rope(\"de\"))[3], \"d\")\nequals(to_rope(\"abcde\")[0:4][3], \"d\")\n",[243,1965,1966,1970,1974,1978,1982,1986,1990,1995,1999,2004,2009,2014,2019,2023,2028,2032,2037,2042,2046,2051,2056,2060,2065,2070,2075,2080,2084,2089,2094,2098,2103,2107,2111,2115,2119,2123,2127,2131,2135,2139,2143,2147,2152,2156,2160,2165,2169,2173,2177,2181,2186,2192,2197,2202,2208,2213,2218,2224,2229,2234,2240,2245,2250,2255,2260,2265,2270,2275,2280,2285,2290,2296,2301,2306,2312,2318,2324,2330,2335,2340,2345,2350,2355,2360,2365,2371,2376,2381,2386,2391,2397,2403,2409,2414,2419,2425,2431,2436,2442,2447,2453,2459],{"__ignoreMap":126},[246,1967,1968],{"class":248,"line":249},[246,1969,1518],{},[246,1971,1972],{"class":248,"line":457},[246,1973,636],{"emptyLinePlaceholder":166},[246,1975,1976],{"class":248,"line":463},[246,1977,1546],{},[246,1979,1980],{"class":248,"line":469},[246,1981,1551],{},[246,1983,1984],{"class":248,"line":516},[246,1985,1556],{},[246,1987,1988],{"class":248,"line":521},[246,1989,636],{"emptyLinePlaceholder":166},[246,1991,1992],{"class":248,"line":654},[246,1993,1994],{},"# Implementation\n",[246,1996,1997],{"class":248,"line":660},[246,1998,1565],{},[246,2000,2001],{"class":248,"line":666},[246,2002,2003],{},"    def delete(self, start, length):\n",[246,2005,2006],{"class":248,"line":672},[246,2007,2008],{},"        left = self[0:start]\n",[246,2010,2011],{"class":248,"line":677},[246,2012,2013],{},"        right = self[start + length : len(self)]\n",[246,2015,2016],{"class":248,"line":683},[246,2017,2018],{},"        return left + right\n",[246,2020,2021],{"class":248,"line":689},[246,2022,636],{"emptyLinePlaceholder":166},[246,2024,2025],{"class":248,"line":694},[246,2026,2027],{},"    def insert(self, rope, start):\n",[246,2029,2030],{"class":248,"line":700},[246,2031,2008],{},[246,2033,2034],{"class":248,"line":706},[246,2035,2036],{},"        right = self[start : len(self)]\n",[246,2038,2039],{"class":248,"line":711},[246,2040,2041],{},"        return left + rope + right\n",[246,2043,2044],{"class":248,"line":717},[246,2045,636],{"emptyLinePlaceholder":166},[246,2047,2048],{"class":248,"line":723},[246,2049,2050],{},"    def __add__(self, addend):\n",[246,2052,2053],{"class":248,"line":729},[246,2054,2055],{},"        return Concatenation(self, addend)\n",[246,2057,2058],{"class":248,"line":734},[246,2059,636],{"emptyLinePlaceholder":166},[246,2061,2062],{"class":248,"line":740},[246,2063,2064],{},"    def __getitem__(self, index):\n",[246,2066,2067],{"class":248,"line":1621},[246,2068,2069],{},"        if type(index) == int:\n",[246,2071,2072],{"class":248,"line":1627},[246,2073,2074],{},"            return self.__get_single_item__(index)\n",[246,2076,2077],{"class":248,"line":1633},[246,2078,2079],{},"        return Substring(self, index.start, index.stop - index.start)\n",[246,2081,2082],{"class":248,"line":1639},[246,2083,636],{"emptyLinePlaceholder":166},[246,2085,2086],{"class":248,"line":1645},[246,2087,2088],{},"    def __len__(self):\n",[246,2090,2091],{"class":248,"line":1651},[246,2092,2093],{},"        raise Exception(\"Should have been overriden\")\n",[246,2095,2096],{"class":248,"line":1656},[246,2097,636],{"emptyLinePlaceholder":166},[246,2099,2100],{"class":248,"line":1662},[246,2101,2102],{},"    def __get_single_item__(self, index):\n",[246,2104,2105],{"class":248,"line":1667},[246,2106,2093],{},[246,2108,2109],{"class":248,"line":1673},[246,2110,636],{"emptyLinePlaceholder":166},[246,2112,2113],{"class":248,"line":1679},[246,2114,636],{"emptyLinePlaceholder":166},[246,2116,2117],{"class":248,"line":1685},[246,2118,1594],{},[246,2120,2121],{"class":248,"line":1691},[246,2122,1599],{},[246,2124,2125],{"class":248,"line":1696},[246,2126,1604],{},[246,2128,2129],{"class":248,"line":1702},[246,2130,636],{"emptyLinePlaceholder":166},[246,2132,2133],{"class":248,"line":1707},[246,2134,1609],{},[246,2136,2137],{"class":248,"line":1713},[246,2138,1614],{},[246,2140,2141],{"class":248,"line":1719},[246,2142,636],{"emptyLinePlaceholder":166},[246,2144,2145],{"class":248,"line":1725},[246,2146,2088],{},[246,2148,2149],{"class":248,"line":1731},[246,2150,2151],{},"        return len(self.string)\n",[246,2153,2154],{"class":248,"line":1736},[246,2155,636],{"emptyLinePlaceholder":166},[246,2157,2158],{"class":248,"line":1742},[246,2159,2102],{},[246,2161,2162],{"class":248,"line":1748},[246,2163,2164],{},"        return self.string[index]\n",[246,2166,2167],{"class":248,"line":1753},[246,2168,636],{"emptyLinePlaceholder":166},[246,2170,2171],{"class":248,"line":1759},[246,2172,1624],{},[246,2174,2175],{"class":248,"line":1765},[246,2176,1630],{},[246,2178,2179],{"class":248,"line":1771},[246,2180,1636],{},[246,2182,2184],{"class":248,"line":2183},50,[246,2185,1642],{},[246,2187,2189],{"class":248,"line":2188},51,[246,2190,2191],{},"        self.leng = length\n",[246,2193,2195],{"class":248,"line":2194},52,[246,2196,636],{"emptyLinePlaceholder":166},[246,2198,2200],{"class":248,"line":2199},53,[246,2201,1609],{},[246,2203,2205],{"class":248,"line":2204},54,[246,2206,2207],{},"        return str(self.rope)[self.start : self.start + self.leng]\n",[246,2209,2211],{"class":248,"line":2210},55,[246,2212,636],{"emptyLinePlaceholder":166},[246,2214,2216],{"class":248,"line":2215},56,[246,2217,2088],{},[246,2219,2221],{"class":248,"line":2220},57,[246,2222,2223],{},"        return self.leng\n",[246,2225,2227],{"class":248,"line":2226},58,[246,2228,636],{"emptyLinePlaceholder":166},[246,2230,2232],{"class":248,"line":2231},59,[246,2233,2102],{},[246,2235,2237],{"class":248,"line":2236},60,[246,2238,2239],{},"        return self.rope[index + self.start]\n",[246,2241,2243],{"class":248,"line":2242},61,[246,2244,636],{"emptyLinePlaceholder":166},[246,2246,2248],{"class":248,"line":2247},62,[246,2249,1670],{},[246,2251,2253],{"class":248,"line":2252},63,[246,2254,1676],{},[246,2256,2258],{"class":248,"line":2257},64,[246,2259,1682],{},[246,2261,2263],{"class":248,"line":2262},65,[246,2264,1688],{},[246,2266,2268],{"class":248,"line":2267},66,[246,2269,636],{"emptyLinePlaceholder":166},[246,2271,2273],{"class":248,"line":2272},67,[246,2274,1609],{},[246,2276,2278],{"class":248,"line":2277},68,[246,2279,1699],{},[246,2281,2283],{"class":248,"line":2282},69,[246,2284,636],{"emptyLinePlaceholder":166},[246,2286,2288],{"class":248,"line":2287},70,[246,2289,2088],{},[246,2291,2293],{"class":248,"line":2292},71,[246,2294,2295],{},"        return len(self.left) + len(self.right)\n",[246,2297,2299],{"class":248,"line":2298},72,[246,2300,636],{"emptyLinePlaceholder":166},[246,2302,2304],{"class":248,"line":2303},73,[246,2305,2102],{},[246,2307,2309],{"class":248,"line":2308},74,[246,2310,2311],{},"        if index \u003C len(self.left):\n",[246,2313,2315],{"class":248,"line":2314},75,[246,2316,2317],{},"            return self.left[index]\n",[246,2319,2321],{"class":248,"line":2320},76,[246,2322,2323],{},"        else:\n",[246,2325,2327],{"class":248,"line":2326},77,[246,2328,2329],{},"            return self.right[index - len(self.left)]\n",[246,2331,2333],{"class":248,"line":2332},78,[246,2334,636],{"emptyLinePlaceholder":166},[246,2336,2338],{"class":248,"line":2337},79,[246,2339,636],{"emptyLinePlaceholder":166},[246,2341,2343],{"class":248,"line":2342},80,[246,2344,1710],{},[246,2346,2348],{"class":248,"line":2347},81,[246,2349,1716],{},[246,2351,2353],{"class":248,"line":2352},82,[246,2354,1722],{},[246,2356,2358],{"class":248,"line":2357},83,[246,2359,1728],{},[246,2361,2363],{"class":248,"line":2362},84,[246,2364,826],{},[246,2366,2368],{"class":248,"line":2367},85,[246,2369,2370],{},"    print(actual, \"didn't equal\", expected)\n",[246,2372,2374],{"class":248,"line":2373},86,[246,2375,1745],{},[246,2377,2379],{"class":248,"line":2378},87,[246,2380,636],{"emptyLinePlaceholder":166},[246,2382,2384],{"class":248,"line":2383},88,[246,2385,636],{"emptyLinePlaceholder":166},[246,2387,2389],{"class":248,"line":2388},89,[246,2390,1756],{},[246,2392,2394],{"class":248,"line":2393},90,[246,2395,2396],{},"equals(to_rope(\"abcde\")[1:4], \"bcd\")\n",[246,2398,2400],{"class":248,"line":2399},91,[246,2401,2402],{},"equals(to_rope(\"abcde\")[1:4][1:2], \"c\")\n",[246,2404,2406],{"class":248,"line":2405},92,[246,2407,2408],{},"equals(to_rope(\"abc\") + to_rope(\"de\"), \"abcde\")\n",[246,2410,2412],{"class":248,"line":2411},93,[246,2413,1787],{},[246,2415,2417],{"class":248,"line":2416},94,[246,2418,636],{"emptyLinePlaceholder":166},[246,2420,2422],{"class":248,"line":2421},95,[246,2423,2424],{},"assert len(to_rope(\"abcde\")[1:4]) == 3\n",[246,2426,2428],{"class":248,"line":2427},96,[246,2429,2430],{},"assert len(to_rope(\"abc\") + to_rope(\"de\")) == 5\n",[246,2432,2434],{"class":248,"line":2433},97,[246,2435,636],{"emptyLinePlaceholder":166},[246,2437,2439],{"class":248,"line":2438},98,[246,2440,2441],{},"equals(to_rope(\"abe\").insert(to_rope(\"cd\"), 2), \"abcde\")\n",[246,2443,2445],{"class":248,"line":2444},99,[246,2446,636],{"emptyLinePlaceholder":166},[246,2448,2450],{"class":248,"line":2449},100,[246,2451,2452],{},"equals(to_rope(\"abcde\")[3], \"d\")\n",[246,2454,2456],{"class":248,"line":2455},101,[246,2457,2458],{},"equals((to_rope(\"abc\") + to_rope(\"de\"))[3], \"d\")\n",[246,2460,2462],{"class":248,"line":2461},102,[246,2463,2464],{},"equals(to_rope(\"abcde\")[0:4][3], \"d\")\n",[206,2466,2468],{"id":2467},"conclusions","Conclusions",[211,2470,2471],{},"Overall, I've observed that TCR is very useful for learning to implement changes and refactor step by step. It may seem a bit tedious at first to have to rewrite code, but that's precisely why you end up writing less code, optimizing and valuing tests. As Kent Beck says in his famous quote:",[292,2473,2474],{},[211,2475,2476],{},"\"Make the change easy and then make the easy change\"",[211,2478,2479],{},"Therefore, I would recommend everyone to try this technique — the original version — whether with a simple example or a more complex one, as I believe it also helps understand part of TDD by changing your perspective, perhaps in a more forceful way, but this can be mitigated with the variations as we've seen.",[211,2481,2482,2483,2485,2486,2488,2489,2491],{},"As for me and the effect of TCR, I'd say that trying the original is necessary, but for day-to-day work I would use, for example, the ",[331,2484,527],{}," or ",[331,2487,581],{}," variation, maybe even ",[331,2490,1045],{}," in one of its versions.",[211,2493,2494,2495,2498],{},"For example, while working through the example I noticed that I sometimes found myself pressing ",[243,2496,2497],{},"Ctrl+Z"," to get back the code that failed and continue from there.",[211,2500,2501,2502,2507],{},"In fact, Kent Beck himself in another TCR example, ",[220,2503,2506],{"href":2504,"rel":2505},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=ZrHBVTCbcE0",[224],"substring, TCR style",", creates a file where he saves the error messages each time it resets to keep the output and error logs and thus find the bug in his code.",[211,2509,2510,2511,1273],{},"He uses the following command with the Run on Save extension, which adds a redirect from stderr to stdout and saves it to a file called ",[243,2512,2513],{},"tcrfeedback",[237,2515,2517],{"className":239,"code":2516,"language":241,"meta":126,"style":126},"cd ${workspaceFolder} && python ${file} > ..\u002Ftcrfeedback 2>&1 && git commit -am working || git reset --hard\n",[243,2518,2519],{"__ignoreMap":126},[246,2520,2521],{"class":248,"line":249},[246,2522,2516],{},[206,2524,2526],{"id":2525},"references","References",[211,2528,2529],{},[331,2530,2531],{},"Articles by Kent Beck:",[301,2533,2534,2539],{},[304,2535,2536],{},[220,2537,270],{"href":268,"rel":2538},[224],[304,2540,2541],{},[220,2542,225],{"href":222,"rel":2543},[224],[211,2545,2546],{},[331,2547,2548],{},"YouTube Videos:",[301,2550,2551,2556,2561,2568],{},[304,2552,2553],{},[220,2554,1458],{"href":1456,"rel":2555},[224],[304,2557,2558],{},[220,2559,2506],{"href":2504,"rel":2560},[224],[304,2562,2563],{},[220,2564,2567],{"href":2565,"rel":2566},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=IIKndRX5qHw",[224],"TCR in VSCode",[304,2569,2570],{},[220,2571,2574],{"href":2572,"rel":2573},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=FFzHOyFeovE",[224],"Understanding Legacy Code with TCR",[211,2576,2577],{},[331,2578,2579],{},"Other Articles:",[301,2581,2582,2590,2597,2604],{},[304,2583,2584,2589],{},[220,2585,2588],{"href":2586,"rel":2587},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-test-commit-revert-a-test-alternative-to-tdd-6e6b03c22bec",[224],"TCR. How to use? Alternative to TDD?"," — Thomas Deniffel",[304,2591,2592,2589],{},[220,2593,2596],{"href":2594,"rel":2595},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-tool-test-commit-revert-8aa91d26e61f",[224],"TCR Tool",[304,2598,2599,2589],{},[220,2600,2603],{"href":2601,"rel":2602},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-variants-test-commit-revert-bf6bd84b17d3",[224],"TCR Variants",[304,2605,2606,2611],{},[220,2607,2610],{"href":2608,"rel":2609},"https:\u002F\u002Fwww.honeybadger.io\u002Fblog\u002Fruby-tcr-test-commit-revert\u002F",[224],"Test && Commit || Revert (TCR)"," — David Tanzer",[211,2613,2614],{},[331,2615,2616],{},"Podcast:",[301,2618,2619],{},[304,2620,2621,2626],{},[220,2622,2625],{"href":2623,"rel":2624},"https:\u002F\u002Fwww.hanselminutes.com\u002F663\u002Ftest-commit-revert-with-kent-beck",[224],"The HanselMinutes Podcast"," — Scott Hanselman",[211,2628,2629],{},[331,2630,2631],{},"Example Code:",[301,2633,2634],{},[304,2635,2636],{},[220,2637,2640],{"href":2638,"rel":2639},"https:\u002F\u002Fgithub.com\u002Fmiguelfernandezdev\u002Ftcr-rope",[224],"miguelfernandezdev\u002Ftcr-rope",[2642,2643,2644],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":126,"searchDepth":457,"depth":457,"links":2646},[2647,2648,2649,2650,2654,2666,2671,2672],{"id":208,"depth":457,"text":209},{"id":261,"depth":457,"text":262},{"id":325,"depth":457,"text":326},{"id":364,"depth":457,"text":365,"children":2651},[2652,2653],{"id":369,"depth":463,"text":370},{"id":396,"depth":463,"text":397},{"id":419,"depth":457,"text":420,"children":2655},[2656,2657,2658,2659,2660,2661,2662,2663,2664,2665],{"id":423,"depth":463,"text":424},{"id":475,"depth":463,"text":476},{"id":526,"depth":463,"text":527},{"id":580,"depth":463,"text":581},{"id":612,"depth":463,"text":613},{"id":746,"depth":463,"text":747},{"id":853,"depth":463,"text":854},{"id":960,"depth":463,"text":961},{"id":1035,"depth":463,"text":1036},{"id":1148,"depth":463,"text":1149},{"id":1225,"depth":457,"text":1226,"children":2667},[2668,2669,2670],{"id":1229,"depth":463,"text":1230},{"id":1449,"depth":463,"text":1450},{"id":1464,"depth":463,"text":1465},{"id":2467,"depth":457,"text":2468},{"id":2525,"depth":457,"text":2526},"2021-02-02","Exploring the TCR (test && commit || revert) technique introduced by Kent Beck, its variations, and a practical example with Rope in Python.","md",{},{"title":15,"description":2674},"nloPvNKA-1F7q4BnMeP6Jca5nPuD4mcLHE6vAgMsAd4",1782058040031]