[{"data":1,"prerenderedAt":2531},["ShallowReactive",2],{"navigation":3,"blog-page-es":31,"blogs-es":48},[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":10,"body":33,"description":34,"extension":35,"links":36,"meta":42,"navigation":43,"path":25,"seo":44,"stem":26,"__hash__":47},"pages\u002Fes\u002Fblog.yml",null,"Reflexiones sobre desarrollo web, ingeniería de IA y creación de productos.","yml",[37],{"label":38,"color":39,"variant":40,"icon":41},"Suscribirse a RSS","neutral","outline","i-lucide-rss",{},true,{"title":45,"description":46},"Blog -- Miguel Fernandez","Artículos sobre desarrollo web, ingeniería de IA y creación de productos.","Om7_8iD0dNyFykm2N6Stlaz2un7Z4NzncKMv7WwRWOM",[49],{"id":50,"title":15,"author":51,"body":54,"date":2525,"description":2526,"extension":2527,"image":266,"meta":2528,"minRead":553,"navigation":43,"path":29,"seo":2529,"stem":30,"__hash__":2530},"blog\u002Fes\u002Fblog\u002Ftest-commit-revert.md",{"name":52,"username":53},"Miguel Fernandez","miguelfernandezdev",{"type":55,"value":56,"toc":2497},"minimark",[57,62,85,88,105,112,116,132,141,144,150,153,163,166,171,176,180,188,195,215,219,224,227,247,251,258,261,267,270,274,278,285,293,296,326,330,333,342,344,377,381,384,399,401,431,435,438,463,467,470,597,601,604,643,645,704,708,717,755,757,811,815,818,845,847,886,890,900,942,944,999,1003,1006,1015,1076,1080,1084,1092,1099,1276,1283,1292,1299,1303,1311,1314,1318,1327,1363,1629,1635,1638,1647,1650,1665,1672,1680,1683,1697,1703,1710,1715,1729,1734,1753,1758,1781,1786,1810,1813,2317,2321,2324,2329,2332,2344,2351,2360,2366,2375,2379,2384,2396,2401,2427,2432,2464,2469,2479,2484,2493],[58,59,61],"h2",{"id":60},"introducción","Introducción",[63,64,65,66,70,71,78,79,84],"p",{},"Kent Beck introdujo por primera vez la idea de ",[67,68,69],"em",{},"test && commit"," en su artículo ",[72,73,77],"a",{"href":74,"rel":75},"https:\u002F\u002Fmedium.com\u002F@kentbeck_7670\u002Flimbo-on-the-cheap-e4cfae840330",[76],"nofollow","Limbo on the Cheap",". En el artículo comenta que con el objetivo de probar una técnica para escalar la colaboración en los proyectos software llamada ",[72,80,83],{"href":81,"rel":82},"https:\u002F\u002Fmedium.com\u002F@kentbeck_7670\u002Flimbo-scaling-software-collaboration-afd4f00db4b",[76],"Limbo"," lleva a cabo su implementación y experimentación.",[63,86,87],{},"Es entonces cuando comenta que para asegurarse de que cada uno no rompa el código del otro al propagar los cambios, utiliza el siguiente código, el cual ejecuta un script que realiza la construcción del sistema y ejecuta los tests:",[89,90,95],"pre",{"className":91,"code":92,"language":93,"meta":94,"style":94},"language-shell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","$ .\u002Ftest && commit -am working\n","shell","",[96,97,98],"code",{"__ignoreMap":94},[99,100,103],"span",{"class":101,"line":102},"line",1,[99,104,92],{},[63,106,107,108,111],{},"Aunque no incluye el ",[67,109,110],{},"revert",", indica que si los tests fallaban, se revertían los cambios, pero lo explica más en profundidad en otro artículo.",[58,113,115],{"id":114},"desarrollo","Desarrollo",[63,117,118,119,124,125,127,128,131],{},"El artículo principal dedicado íntegramente a esta técnica es ",[72,120,123],{"href":121,"rel":122},"https:\u002F\u002Fmedium.com\u002F@kentbeck_7670\u002Ftest-commit-revert-870bbd756864",[76],"test && commit || revert",". En él, se amplía la idea anterior de ",[67,126,69],{},", con el comando ",[67,129,130],{},"reset --hard",", provocando por lo tanto que en el caso en el que los tests fallen, no solo no se realice el commit de los cambios sino que estos se vean revertidos.",[89,133,135],{"className":91,"code":134,"language":93,"meta":94,"style":94},"$ .\u002Ftest && git commit -am working || git reset --hard\n",[96,136,137],{"__ignoreMap":94},[99,138,139],{"class":101,"line":102},[99,140,134],{},[63,142,143],{},"En principio, puede parecer una técnica muy arriesgada, e incluso el propio Kent Beck se muestra escéptico en un principio:",[145,146,147],"blockquote",{},[63,148,149],{},"\"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.\"",[63,151,152],{},"En general, la mayoría de la gente se hace alguna de estas preguntas (y es completamente normal) en cuanto se le explica en qué consiste esta técnica:",[154,155,156,160],"ul",{},[157,158,159],"li",{},"¿Cómo vas a progresar si los tests tienen que funcionar siempre? ¿A caso no fallas nunca?",[157,161,162],{},"¿Y si escribes un montón de código y se borra? ¿No te frustras?",[63,164,165],{},"Y la mejor respuesta a esta pregunta, aunque posteriormente la debatiremos y explicaremos más en profundidad, es la siguiente:",[154,167,168],{},[157,169,170],{},"Si no quieres que se borre un montón de código erróneo, NO escribas un montón de código, es decir, aprende a dar pasos pequeños y en la buena dirección.",[145,172,173],{},[63,174,175],{},"\"If you don't want a bunch of code wiped out then don't write a bunch of code between greens.\"",[58,177,179],{"id":178},"incrementos","Incrementos",[63,181,182,183,187],{},"La base de TCR son los incrementos, ya que ayuda a encontrar una forma ",[184,185,186],"strong",{},"incremental"," de realizar el mismo cambio, de una forma mejor y más segura, manteniendo todos los tests \"en verde\". Pero claro, ¿es posible solucionar grandes problemas en un paso pequeño? La respuesta es que la mayoría de las veces esto no es posible, por lo que TCR plantea su funcionamiento en los siguientes pasos:",[63,189,190],{},[191,192],"img",{"alt":193,"src":194},"Incrementos en TCR","\u002Fblog\u002Ftcr\u002Fincrements-in-tcr.jpeg",[154,196,197,203,209],{},[157,198,199,202],{},[184,200,201],{},"Añadir un test y que pase",": cada idea implementada tiene que tener asociada un test, lo más pronto posible, aunque no sea exhaustivo o incluso que pruebe únicamente una parte de la funcionalidad, o que simplemente pase.",[157,204,205,208],{},[184,206,207],{},"Que pase mejor",": poco a poco, una vez que el test ya pase, hay que reemplazar paso a paso la implementación que habíamos dejado a medias por la real.",[157,210,211,214],{},[184,212,213],{},"Hacer fáciles los cambios difíciles",": no hay que llevar a cabo una gran cantidad de cambios simultáneos, es mejor realizar los cambios uno a uno de forma segura, haciendo uso de por ejemplo una función auxiliar que devuelva el valor esperado e ir paso a paso convirtiendo la implementación en la real.",[58,216,218],{"id":217},"comparación-con-tdd","Comparación con TDD",[220,221,223],"h3",{"id":222},"similitudes","Similitudes",[63,225,226],{},"TCR ayuda a utilizar TDD en los siguientes aspectos:",[154,228,229,235,241],{},[157,230,231,234],{},[184,232,233],{},"No escribir código que no ayude a que pase un test que falla",": esto no se puede controlar del todo, ya que puedes escribir código que no se compruebe en el test. Para controlar esto se debería tener en cuenta la cobertura de los tests e intentar que sea del 100%.",[157,236,237,240],{},[184,238,239],{},"No escribir más de un test unitario a la vez",": no es posible ya que si esto sucediera te verías obligado a implementar también más código para que el test pase, el cual significaría un riesgo de borrado mucho mayor.",[157,242,243,246],{},[184,244,245],{},"No escribir más código del necesario para pasar el test que falla",": si escribes más del necesario, te arriesgas a que se elimine.",[220,248,250],{"id":249},"diferencias","Diferencias",[63,252,253,254,257],{},"La gran diferencia entre TDD y TCR se encuentra en el paso en el que hay que hacer que el test pase: ",[184,255,256],{},"si al pasar el test falla, se reinicia el intento",".",[63,259,260],{},"Por lo tanto en TCR nunca se llega al estado en el que el test está \"en rojo\", ya que de ser así se vuelve al estado \"en verde\".",[63,262,263],{},[191,264],{"alt":265,"src":266},"TCR vs TDD","\u002Fblog\u002Ftcr\u002Ftcr-vs-tdd.jpeg",[63,268,269],{},"La conclusión es que TDD nos permite quedarnos en la fase \"en rojo\" y pasar los tests cómodamente, mientras que TCR se intenta no llegar nunca a esa fase, por lo que en parte se puede considerar TCR una versión de TDD sin el estado \"en rojo\".",[58,271,273],{"id":272},"variaciones","Variaciones",[220,275,277],{"id":276},"original","Original",[63,279,280,281,284],{},"La versión original ",[67,282,283],{},"test && commit || reset --hard"," de Kent Beck. Entre sus desventajas tiene por ejemplo el borrado del propio test al fallar en la ejecución u obtener un error de compilación.",[89,286,287],{"className":91,"code":134,"language":93,"meta":94,"style":94},[96,288,289],{"__ignoreMap":94},[99,290,291],{"class":101,"line":102},[99,292,134],{},[63,294,295],{},"En pseudocódigo:",[89,297,301],{"className":298,"code":299,"language":300,"meta":94,"style":94},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","if(test().success)\n    commit()\nelse\n    revert()\n","python",[96,302,303,308,314,320],{"__ignoreMap":94},[99,304,305],{"class":101,"line":102},[99,306,307],{},"if(test().success)\n",[99,309,311],{"class":101,"line":310},2,[99,312,313],{},"    commit()\n",[99,315,317],{"class":101,"line":316},3,[99,318,319],{},"else\n",[99,321,323],{"class":101,"line":322},4,[99,324,325],{},"    revert()\n",[220,327,329],{"id":328},"btcr","BTCR",[63,331,332],{},"Esta variante intenta resolver la desventaja original del borrado en caso de fallo de compilación realizando la construcción primero.",[89,334,336],{"className":91,"code":335,"language":93,"meta":94,"style":94},"$ .\u002FbuildIt && (.\u002Ftest && git commit -am working || git reset --hard)\n",[96,337,338],{"__ignoreMap":94},[99,339,340],{"class":101,"line":102},[99,341,335],{},[63,343,295],{},[89,345,347],{"className":298,"code":346,"language":300,"meta":94,"style":94},"if(build().failed)\n    return\nif(test().success)\n    commit()\nelse\n    revert()\n",[96,348,349,354,359,363,367,372],{"__ignoreMap":94},[99,350,351],{"class":101,"line":102},[99,352,353],{},"if(build().failed)\n",[99,355,356],{"class":101,"line":310},[99,357,358],{},"    return\n",[99,360,361],{"class":101,"line":316},[99,362,307],{},[99,364,365],{"class":101,"line":322},[99,366,313],{},[99,368,370],{"class":101,"line":369},5,[99,371,319],{},[99,373,375],{"class":101,"line":374},6,[99,376,325],{},[220,378,380],{"id":379},"the-relaxed","The Relaxed",[63,382,383],{},"Esta variante intenta resolver también la otra gran desventaja del borrado del propio test al fallar la ejecución. Se basa en eliminar únicamente los cambios en el código fuente, y no en el directorio donde se encuentran los tests.",[89,385,387],{"className":91,"code":386,"language":93,"meta":94,"style":94},"$ git checkout HEAD -- src\u002Fmain\u002F\n$ .\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[96,388,389,394],{"__ignoreMap":94},[99,390,391],{"class":101,"line":102},[99,392,393],{},"$ git checkout HEAD -- src\u002Fmain\u002F\n",[99,395,396],{"class":101,"line":310},[99,397,398],{},"$ .\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[63,400,295],{},[89,402,404],{"className":298,"code":403,"language":300,"meta":94,"style":94},"if(build().failed)\n    return\nif(test().success)\n    commit()\nelse\n    revert('src\u002Fmain')\n",[96,405,406,410,414,418,422,426],{"__ignoreMap":94},[99,407,408],{"class":101,"line":102},[99,409,353],{},[99,411,412],{"class":101,"line":310},[99,413,358],{},[99,415,416],{"class":101,"line":316},[99,417,307],{},[99,419,420],{"class":101,"line":322},[99,421,313],{},[99,423,424],{"class":101,"line":369},[99,425,319],{},[99,427,428],{"class":101,"line":374},[99,429,430],{},"    revert('src\u002Fmain')\n",[220,432,434],{"id":433},"the-gentle","The Gentle",[63,436,437],{},"Esta variante tiene como objetivo guardar de alguna forma los cambios introducidos anteriormente, para poder así recuperarlos en caso de fallo e intentar encontrar el fallo.",[89,439,441],{"className":91,"code":440,"language":93,"meta":94,"style":94},"$ 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",[96,442,443,448,453,458],{"__ignoreMap":94},[99,444,445],{"class":101,"line":102},[99,446,447],{},"$ git stash drop 0 2&>\u002Fdev\u002Fnull; git add -A && git stash push\n",[99,449,450],{"class":101,"line":310},[99,451,452],{},"$ .\u002FbuildIt && (git stash drop 0 2&>\u002Fdev\u002Fnull; git add -A && git stash push)\n",[99,454,455],{"class":101,"line":316},[99,456,457],{},"# TCR kicks and reverts our code\n",[99,459,460],{"class":101,"line":322},[99,461,462],{},"$ git stash apply\n",[220,464,466],{"id":465},"the-split","The Split",[63,468,469],{},"Consiste en dividir los scripts que realizan la construcción del sistema, ejecutan los tests, etc. en distintos archivos en directorios diferentes.",[89,471,473],{"className":91,"code":472,"language":93,"meta":94,"style":94},"$ 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",[96,474,475,480,485,490,495,500,505,511,517,523,528,534,540,545,551,557,562,568,574,580,585,591],{"__ignoreMap":94},[99,476,477],{"class":101,"line":102},[99,478,479],{},"$ cat .\u002Ftest\n",[99,481,482],{"class":101,"line":310},[99,483,484],{},".\u002Fscripts\u002FbuildIt && (.\u002Fscripts\u002FrunTests && .\u002Fscripts\u002Fcommit || .\u002Fscripts\u002Frevert)\n",[99,486,487],{"class":101,"line":316},[99,488,489],{"emptyLinePlaceholder":43},"\n",[99,491,492],{"class":101,"line":322},[99,493,494],{},"$ tree script\n",[99,496,497],{"class":101,"line":369},[99,498,499],{},"scripts\u002F\n",[99,501,502],{"class":101,"line":374},[99,503,504],{},"├── buildIt\n",[99,506,508],{"class":101,"line":507},7,[99,509,510],{},"├── commit\n",[99,512,514],{"class":101,"line":513},8,[99,515,516],{},"├── revert\n",[99,518,520],{"class":101,"line":519},9,[99,521,522],{},"└── runTests\n",[99,524,526],{"class":101,"line":525},10,[99,527,489],{"emptyLinePlaceholder":43},[99,529,531],{"class":101,"line":530},11,[99,532,533],{},"$ cat scripts\u002FbuildIt\n",[99,535,537],{"class":101,"line":536},12,[99,538,539],{},".\u002Fgradlew build -x test\n",[99,541,543],{"class":101,"line":542},13,[99,544,489],{"emptyLinePlaceholder":43},[99,546,548],{"class":101,"line":547},14,[99,549,550],{},"$ cat scripts\u002Fcommit\n",[99,552,554],{"class":101,"line":553},15,[99,555,556],{},"git commit -am working\n",[99,558,560],{"class":101,"line":559},16,[99,561,489],{"emptyLinePlaceholder":43},[99,563,565],{"class":101,"line":564},17,[99,566,567],{},"$ cat scripts\u002Frevert\n",[99,569,571],{"class":101,"line":570},18,[99,572,573],{},"# git reset --hard\n",[99,575,577],{"class":101,"line":576},19,[99,578,579],{},"git checkout HEAD -- src\u002Fmain\u002F\n",[99,581,583],{"class":101,"line":582},20,[99,584,489],{"emptyLinePlaceholder":43},[99,586,588],{"class":101,"line":587},21,[99,589,590],{},"$ cat scripts\u002FrunTests\n",[99,592,594],{"class":101,"line":593},22,[99,595,596],{},".\u002Fgradlew test\n",[220,598,600],{"id":599},"the-buddy-continuous-tcr","The Buddy - Continuous TCR",[63,602,603],{},"¿Crees que ejecutar manualmente TCR se parece demasiado a TDD? Esta variante intenta solucionar esto mismo: en cuanto se introduce un error en tu código, elimina el cambio.",[89,605,607],{"className":91,"code":606,"language":93,"meta":94,"style":94},"while true\ndo\n    .\u002Ftcr\ndone\n\n$ cat tcr\n.\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[96,608,609,614,619,624,629,633,638],{"__ignoreMap":94},[99,610,611],{"class":101,"line":102},[99,612,613],{},"while true\n",[99,615,616],{"class":101,"line":310},[99,617,618],{},"do\n",[99,620,621],{"class":101,"line":316},[99,622,623],{},"    .\u002Ftcr\n",[99,625,626],{"class":101,"line":322},[99,627,628],{},"done\n",[99,630,631],{"class":101,"line":369},[99,632,489],{"emptyLinePlaceholder":43},[99,634,635],{"class":101,"line":374},[99,636,637],{},"$ cat tcr\n",[99,639,640],{"class":101,"line":507},[99,641,642],{},".\u002FbuildIt && (.\u002Ftest && git commit -am working || git checkout HEAD -- src\u002Fmain\u002F)\n",[63,644,295],{},[89,646,648],{"className":298,"code":647,"language":300,"meta":94,"style":94},"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",[96,649,650,655,660,665,670,675,680,685,690,695,700],{"__ignoreMap":94},[99,651,652],{"class":101,"line":102},[99,653,654],{},"while(true) {\n",[99,656,657],{"class":101,"line":310},[99,658,659],{},"    tcr()\n",[99,661,662],{"class":101,"line":316},[99,663,664],{},"}\n",[99,666,667],{"class":101,"line":322},[99,668,669],{},"function tcr() {\n",[99,671,672],{"class":101,"line":369},[99,673,674],{},"    if(build().failed)\n",[99,676,677],{"class":101,"line":374},[99,678,679],{},"        return\n",[99,681,682],{"class":101,"line":507},[99,683,684],{},"    if(test().success)\n",[99,686,687],{"class":101,"line":513},[99,688,689],{},"        commit()\n",[99,691,692],{"class":101,"line":519},[99,693,694],{},"    else\n",[99,696,697],{"class":101,"line":525},[99,698,699],{},"        revert()\n",[99,701,702],{"class":101,"line":530},[99,703,664],{},[220,705,707],{"id":706},"the-watch-buddy","The Watch Buddy",[63,709,710,711,716],{},"Esta variante es muy similar a la anterior, con la diferencia de que no se ejecuta en un bucle infinito, se espera a un cambio en el directorio del código fuente. La introduce Alejandro Marcu en un ",[72,712,715],{"href":713,"rel":714},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=l5R05yMluxw&feature=youtu.be",[76],"vídeo"," en el que realiza pruebas con TCR.",[89,718,720],{"className":91,"code":719,"language":93,"meta":94,"style":94},"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",[96,721,722,726,730,735,739,743,747,751],{"__ignoreMap":94},[99,723,724],{"class":101,"line":102},[99,725,613],{},[99,727,728],{"class":101,"line":310},[99,729,618],{},[99,731,732],{"class":101,"line":316},[99,733,734],{},"    inotifywait -r -e modify .\n",[99,736,737],{"class":101,"line":322},[99,738,623],{},[99,740,741],{"class":101,"line":369},[99,742,628],{},[99,744,745],{"class":101,"line":374},[99,746,489],{"emptyLinePlaceholder":43},[99,748,749],{"class":101,"line":507},[99,750,637],{},[99,752,753],{"class":101,"line":513},[99,754,642],{},[63,756,295],{},[89,758,760],{"className":298,"code":759,"language":300,"meta":94,"style":94},"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",[96,761,762,766,771,775,779,783,787,791,795,799,803,807],{"__ignoreMap":94},[99,763,764],{"class":101,"line":102},[99,765,654],{},[99,767,768],{"class":101,"line":310},[99,769,770],{},"    block_until_change_in_directory('src')\n",[99,772,773],{"class":101,"line":316},[99,774,659],{},[99,776,777],{"class":101,"line":322},[99,778,664],{},[99,780,781],{"class":101,"line":369},[99,782,669],{},[99,784,785],{"class":101,"line":374},[99,786,674],{},[99,788,789],{"class":101,"line":507},[99,790,679],{},[99,792,793],{"class":101,"line":513},[99,794,684],{},[99,796,797],{"class":101,"line":519},[99,798,689],{},[99,800,801],{"class":101,"line":525},[99,802,694],{},[99,804,805],{"class":101,"line":530},[99,806,699],{},[99,808,809],{"class":101,"line":536},[99,810,664],{},[220,812,814],{"id":813},"the-collaborator","The Collaborator",[63,816,817],{},"Esta variante incluye un nuevo script, el cual se encarga de subir los cambios y sincronizar los de los compañeros de equipo en caso de que los haya.",[89,819,821],{"className":91,"code":820,"language":93,"meta":94,"style":94},"while true\ndo\n    git pull --rebase\n    git push origin master\ndone\n",[96,822,823,827,831,836,841],{"__ignoreMap":94},[99,824,825],{"class":101,"line":102},[99,826,613],{},[99,828,829],{"class":101,"line":310},[99,830,618],{},[99,832,833],{"class":101,"line":316},[99,834,835],{},"    git pull --rebase\n",[99,837,838],{"class":101,"line":322},[99,839,840],{},"    git push origin master\n",[99,842,843],{"class":101,"line":369},[99,844,628],{},[63,846,295],{},[89,848,850],{"className":298,"code":849,"language":300,"meta":94,"style":94},"async {\n    while(true) {\n        Git.pull('rebase')\n        Git.push('origin', 'master')\n    }\n}\n.\u002Ftcr\n",[96,851,852,857,862,867,872,877,881],{"__ignoreMap":94},[99,853,854],{"class":101,"line":102},[99,855,856],{},"async {\n",[99,858,859],{"class":101,"line":310},[99,860,861],{},"    while(true) {\n",[99,863,864],{"class":101,"line":316},[99,865,866],{},"        Git.pull('rebase')\n",[99,868,869],{"class":101,"line":322},[99,870,871],{},"        Git.push('origin', 'master')\n",[99,873,874],{"class":101,"line":369},[99,875,876],{},"    }\n",[99,878,879],{"class":101,"line":374},[99,880,664],{},[99,882,883],{"class":101,"line":507},[99,884,885],{},".\u002Ftcr\n",[220,887,889],{"id":888},"local-buddy-remote-team","Local Buddy, Remote Team",[63,891,892,893,895,896,899],{},"Combinación de las variantes ",[67,894,814],{}," y ",[67,897,898],{},"The Buddy",":",[89,901,903],{"className":91,"code":902,"language":93,"meta":94,"style":94},"do\n    git pull --rebase\n    git push origin master\ndone\n## Open new Tab\nwhile true\ndo\n    .\u002Ftcr\ndone\n",[96,904,905,909,913,917,921,926,930,934,938],{"__ignoreMap":94},[99,906,907],{"class":101,"line":102},[99,908,618],{},[99,910,911],{"class":101,"line":310},[99,912,835],{},[99,914,915],{"class":101,"line":316},[99,916,840],{},[99,918,919],{"class":101,"line":322},[99,920,628],{},[99,922,923],{"class":101,"line":369},[99,924,925],{},"## Open new Tab\n",[99,927,928],{"class":101,"line":374},[99,929,613],{},[99,931,932],{"class":101,"line":507},[99,933,618],{},[99,935,936],{"class":101,"line":513},[99,937,623],{},[99,939,940],{"class":101,"line":519},[99,941,628],{},[63,943,295],{},[89,945,947],{"className":298,"code":946,"language":300,"meta":94,"style":94},"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",[96,948,949,953,957,961,965,969,973,978,982,986,991,995],{"__ignoreMap":94},[99,950,951],{"class":101,"line":102},[99,952,856],{},[99,954,955],{"class":101,"line":310},[99,956,861],{},[99,958,959],{"class":101,"line":316},[99,960,866],{},[99,962,963],{"class":101,"line":322},[99,964,871],{},[99,966,967],{"class":101,"line":369},[99,968,876],{},[99,970,971],{"class":101,"line":374},[99,972,664],{},[99,974,975],{"class":101,"line":507},[99,976,977],{},"function tcr() { ... }\n",[99,979,980],{"class":101,"line":513},[99,981,856],{},[99,983,984],{"class":101,"line":519},[99,985,861],{},[99,987,988],{"class":101,"line":525},[99,989,990],{},"        tcr()\n",[99,992,993],{"class":101,"line":530},[99,994,876],{},[99,996,997],{"class":101,"line":536},[99,998,664],{},[220,1000,1002],{"id":1001},"the-storyteller-beyond-the-buddy","The Storyteller: Beyond the Buddy",[63,1004,1005],{},"El objetivo de esta variante es el de conseguir que en vez de simplemente revertir todos los cambios que han provocado que los tests fallen, comunicar el error indicando la línea, el archivo, etc. y preguntando al usuario si desea revertir o no sus cambios.",[63,1007,1008,1009,1014],{},"Thomas Deniffel explica esta variante en un ",[72,1010,1013],{"href":1011,"rel":1012},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-variant-the-storyteller-32c8fdb146f0",[76],"artículo",". En él, además de explicar esta variante de TCR, comenta que su objetivo es el de realizar una implementación que funcione con un chat o incluso con interfaz de voz.",[89,1016,1018],{"className":91,"code":1017,"language":93,"meta":94,"style":94},"# 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",[96,1019,1020,1025,1029,1033,1038,1042,1046,1051,1056,1061,1066,1071],{"__ignoreMap":94},[99,1021,1022],{"class":101,"line":102},[99,1023,1024],{},"# The Buddy\n",[99,1026,1027],{"class":101,"line":310},[99,1028,613],{},[99,1030,1031],{"class":101,"line":316},[99,1032,618],{},[99,1034,1035],{"class":101,"line":322},[99,1036,1037],{},"    build && ( test && commit || revert )\n",[99,1039,1040],{"class":101,"line":369},[99,1041,628],{},[99,1043,1044],{"class":101,"line":374},[99,1045,489],{"emptyLinePlaceholder":43},[99,1047,1048],{"class":101,"line":507},[99,1049,1050],{},"# Communicate in 'revert'\n",[99,1052,1053],{"class":101,"line":513},[99,1054,1055],{},"$ cat revert\n",[99,1057,1058],{"class":101,"line":519},[99,1059,1060],{},"messageContent = Git.getDiffAndWhatToReset\n",[99,1062,1063],{"class":101,"line":525},[99,1064,1065],{},"message = MessageGenerator.generateWith(messageContent)\n",[99,1067,1068],{"class":101,"line":530},[99,1069,1070],{},"Git.reset\n",[99,1072,1073],{"class":101,"line":536},[99,1074,1075],{},"print message\n",[58,1077,1079],{"id":1078},"ejemplo","Ejemplo",[220,1081,1083],{"id":1082},"configuración-inicial","Configuración inicial",[63,1085,1086,1087,257],{},"Instalamos la Extensión de Visual Studio Code ",[72,1088,1091],{"href":1089,"rel":1090},"https:\u002F\u002Fgithub.com\u002Femeraldwalk\u002Fvscode-runonsave",[76],"Run on Save",[63,1093,1094,1095,1098],{},"En el archivo de configuración del workspace ",[96,1096,1097],{},"rope.code-workspace"," o en la configuración global de VSCode, añadimos el comando en la configuración de la extensión:",[89,1100,1104],{"className":1101,"code":1102,"language":1103,"meta":94,"style":94},"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",[96,1105,1106,1112,1129,1134,1156,1160,1165,1179,1193,1207,1212,1235,1253,1258,1263,1267,1272],{"__ignoreMap":94},[99,1107,1108],{"class":101,"line":102},[99,1109,1111],{"class":1110},"sMK4o","{\n",[99,1113,1114,1117,1121,1124,1126],{"class":101,"line":310},[99,1115,1116],{"class":1110},"  \"",[99,1118,1120],{"class":1119},"spNyl","folders",[99,1122,1123],{"class":1110},"\"",[99,1125,899],{"class":1110},[99,1127,1128],{"class":1110}," [\n",[99,1130,1131],{"class":101,"line":316},[99,1132,1133],{"class":1110},"    {\n",[99,1135,1136,1139,1143,1145,1147,1150,1153],{"class":101,"line":322},[99,1137,1138],{"class":1110},"      \"",[99,1140,1142],{"class":1141},"sBMFI","path",[99,1144,1123],{"class":1110},[99,1146,899],{"class":1110},[99,1148,1149],{"class":1110}," \"",[99,1151,257],{"class":1152},"sfazB",[99,1154,1155],{"class":1110},"\"\n",[99,1157,1158],{"class":101,"line":369},[99,1159,876],{"class":1110},[99,1161,1162],{"class":101,"line":374},[99,1163,1164],{"class":1110},"  ],\n",[99,1166,1167,1169,1172,1174,1176],{"class":101,"line":507},[99,1168,1116],{"class":1110},[99,1170,1171],{"class":1119},"settings",[99,1173,1123],{"class":1110},[99,1175,899],{"class":1110},[99,1177,1178],{"class":1110}," {\n",[99,1180,1181,1184,1187,1189,1191],{"class":101,"line":513},[99,1182,1183],{"class":1110},"    \"",[99,1185,1186],{"class":1141},"emeraldwalk.runonsave",[99,1188,1123],{"class":1110},[99,1190,899],{"class":1110},[99,1192,1178],{"class":1110},[99,1194,1195,1197,1201,1203,1205],{"class":101,"line":519},[99,1196,1138],{"class":1110},[99,1198,1200],{"class":1199},"sbssI","commands",[99,1202,1123],{"class":1110},[99,1204,899],{"class":1110},[99,1206,1128],{"class":1110},[99,1208,1209],{"class":101,"line":525},[99,1210,1211],{"class":1110},"        {\n",[99,1213,1214,1217,1221,1223,1225,1227,1230,1232],{"class":101,"line":530},[99,1215,1216],{"class":1110},"          \"",[99,1218,1220],{"class":1219},"swJcz","match",[99,1222,1123],{"class":1110},[99,1224,899],{"class":1110},[99,1226,1149],{"class":1110},[99,1228,1229],{"class":1152},".*py",[99,1231,1123],{"class":1110},[99,1233,1234],{"class":1110},",\n",[99,1236,1237,1239,1242,1244,1246,1248,1251],{"class":101,"line":536},[99,1238,1216],{"class":1110},[99,1240,1241],{"class":1219},"cmd",[99,1243,1123],{"class":1110},[99,1245,899],{"class":1110},[99,1247,1149],{"class":1110},[99,1249,1250],{"class":1152},"cd ${workspaceFolder} && python ${file} && git commit -am working || git reset --hard",[99,1252,1155],{"class":1110},[99,1254,1255],{"class":101,"line":542},[99,1256,1257],{"class":1110},"        }\n",[99,1259,1260],{"class":101,"line":547},[99,1261,1262],{"class":1110},"      ]\n",[99,1264,1265],{"class":101,"line":553},[99,1266,876],{"class":1110},[99,1268,1269],{"class":101,"line":559},[99,1270,1271],{"class":1110},"  }\n",[99,1273,1274],{"class":101,"line":564},[99,1275,664],{"class":1110},[63,1277,1278,1279,1282],{},"El comando se ejecutará cada vez que se detecte el guardado de un archivo en el proyecto con la extensión ",[96,1280,1281],{},".py"," (archivo de Python).",[89,1284,1286],{"className":91,"code":1285,"language":93,"meta":94,"style":94},"cd ${workspaceFolder} && python ${file}\n",[96,1287,1288],{"__ignoreMap":94},[99,1289,1290],{"class":101,"line":102},[99,1291,1285],{},[63,1293,1294,1295,1298],{},"Es necesario acceder al directorio correspondiente al workspace del proyecto. Además, en este caso, ejecutaremos el código fuente con python en vez de un script ",[96,1296,1297],{},"test"," ya que los tests se encuentran al final del archivo.",[220,1300,1302],{"id":1301},"repositorio-en-github","Repositorio en GitHub",[63,1304,1305,1306,257],{},"He creado un repositorio público en GitHub con el código del ejemplo completo que Kent Beck realiza en la ",[72,1307,1310],{"href":1308,"rel":1309},"https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLlmVY7qtgT_nhLyIbeAaUlFOWbWT5y53t",[76],"Playlist TCR Rope in Python",[63,1312,1313],{},"Después de realizar la configuración previa, en el siguiente apartado se ejemplifica el funcionamiento de TCR mediante únicamente unos pasos seleccionados del tutorial de Kent Beck.",[220,1315,1317],{"id":1316},"rope-in-python","Rope in Python",[63,1319,1320,1321,1326],{},"Inicialmente, disponemos del siguiente código, correspondiente a la estructura de datos ",[72,1322,1325],{"href":1323,"rel":1324},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRope_(data_structure)",[76],"Rope",", con:",[154,1328,1329,1336,1350,1357],{},[157,1330,1331,1332,1335],{},"el método ",[184,1333,1334],{},"to_rope(string)",", el cual convierte un Rope al tipo de dato String.",[157,1337,1338,1339,1342,1343,895,1346,1349],{},"la ",[184,1340,1341],{},"clase Rope"," con los métodos ",[67,1344,1345],{},"substring",[67,1347,1348],{},"concatenate"," ya implementados.",[157,1351,1352,1353,1356],{},"las ",[184,1354,1355],{},"clases String, Substring y Concatenation"," con sus constructores y métodos toString correspondientes.",[157,1358,1331,1359,1362],{},[184,1360,1361],{},"equals(rope, expected)"," y los tests que ya han sido probados.",[89,1364,1366],{"className":298,"code":1365,"language":300,"meta":94,"style":94},"# 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",[96,1367,1368,1373,1377,1382,1387,1392,1396,1401,1406,1411,1415,1420,1425,1430,1435,1440,1444,1449,1454,1459,1464,1469,1473,1479,1485,1491,1497,1503,1508,1514,1519,1525,1531,1537,1543,1548,1554,1559,1565,1571,1577,1583,1588,1594,1600,1605,1611,1617,1623],{"__ignoreMap":94},[99,1369,1370],{"class":101,"line":102},[99,1371,1372],{},"# Rope\n",[99,1374,1375],{"class":101,"line":310},[99,1376,489],{"emptyLinePlaceholder":43},[99,1378,1379],{"class":101,"line":316},[99,1380,1381],{},"# to do\n",[99,1383,1384],{"class":101,"line":322},[99,1385,1386],{},"# insert\n",[99,1388,1389],{"class":101,"line":369},[99,1390,1391],{},"# delete\n",[99,1393,1394],{"class":101,"line":374},[99,1395,489],{"emptyLinePlaceholder":43},[99,1397,1398],{"class":101,"line":507},[99,1399,1400],{},"# API\n",[99,1402,1403],{"class":101,"line":513},[99,1404,1405],{},"def to_rope(string):\n",[99,1407,1408],{"class":101,"line":519},[99,1409,1410],{},"    return String(string)\n",[99,1412,1413],{"class":101,"line":525},[99,1414,489],{"emptyLinePlaceholder":43},[99,1416,1417],{"class":101,"line":530},[99,1418,1419],{},"class Rope:\n",[99,1421,1422],{"class":101,"line":536},[99,1423,1424],{},"    def substring(self, start, length):\n",[99,1426,1427],{"class":101,"line":542},[99,1428,1429],{},"        return Substring(self, start, length)\n",[99,1431,1432],{"class":101,"line":547},[99,1433,1434],{},"    def concatenate(self, right):\n",[99,1436,1437],{"class":101,"line":553},[99,1438,1439],{},"        return Concatenation(self, right)\n",[99,1441,1442],{"class":101,"line":559},[99,1443,489],{"emptyLinePlaceholder":43},[99,1445,1446],{"class":101,"line":564},[99,1447,1448],{},"class String(Rope):\n",[99,1450,1451],{"class":101,"line":570},[99,1452,1453],{},"    def __init__(self, string):\n",[99,1455,1456],{"class":101,"line":576},[99,1457,1458],{},"        self.string = string\n",[99,1460,1461],{"class":101,"line":582},[99,1462,1463],{},"    def __str__(self):\n",[99,1465,1466],{"class":101,"line":587},[99,1467,1468],{},"        return self.string\n",[99,1470,1471],{"class":101,"line":593},[99,1472,489],{"emptyLinePlaceholder":43},[99,1474,1476],{"class":101,"line":1475},23,[99,1477,1478],{},"class Substring(Rope):\n",[99,1480,1482],{"class":101,"line":1481},24,[99,1483,1484],{},"    def __init__(self, rope, start, length):\n",[99,1486,1488],{"class":101,"line":1487},25,[99,1489,1490],{},"        self.rope = rope\n",[99,1492,1494],{"class":101,"line":1493},26,[99,1495,1496],{},"        self.start = start\n",[99,1498,1500],{"class":101,"line":1499},27,[99,1501,1502],{},"        self.length = length\n",[99,1504,1506],{"class":101,"line":1505},28,[99,1507,1463],{},[99,1509,1511],{"class":101,"line":1510},29,[99,1512,1513],{},"        return str(self.rope)[self.start : self.start + self.length]\n",[99,1515,1517],{"class":101,"line":1516},30,[99,1518,489],{"emptyLinePlaceholder":43},[99,1520,1522],{"class":101,"line":1521},31,[99,1523,1524],{},"class Concatenation(Rope):\n",[99,1526,1528],{"class":101,"line":1527},32,[99,1529,1530],{},"    def __init__(self, left, right):\n",[99,1532,1534],{"class":101,"line":1533},33,[99,1535,1536],{},"        self.left = left\n",[99,1538,1540],{"class":101,"line":1539},34,[99,1541,1542],{},"        self.right = right\n",[99,1544,1546],{"class":101,"line":1545},35,[99,1547,1463],{},[99,1549,1551],{"class":101,"line":1550},36,[99,1552,1553],{},"        return str(self.left) + str(self.right)\n",[99,1555,1557],{"class":101,"line":1556},37,[99,1558,489],{"emptyLinePlaceholder":43},[99,1560,1562],{"class":101,"line":1561},38,[99,1563,1564],{},"# Testing Framework\n",[99,1566,1568],{"class":101,"line":1567},39,[99,1569,1570],{},"def equals(rope, expected):\n",[99,1572,1574],{"class":101,"line":1573},40,[99,1575,1576],{},"    actual = str(rope)\n",[99,1578,1580],{"class":101,"line":1579},41,[99,1581,1582],{},"    if actual == expected:\n",[99,1584,1586],{"class":101,"line":1585},42,[99,1587,679],{},[99,1589,1591],{"class":101,"line":1590},43,[99,1592,1593],{},"    print(actual, \" didn't equal \", expected)\n",[99,1595,1597],{"class":101,"line":1596},44,[99,1598,1599],{},"    raise Exception()\n",[99,1601,1603],{"class":101,"line":1602},45,[99,1604,489],{"emptyLinePlaceholder":43},[99,1606,1608],{"class":101,"line":1607},46,[99,1609,1610],{},"equals(to_rope(\"abc\"), \"abc\")\n",[99,1612,1614],{"class":101,"line":1613},47,[99,1615,1616],{},"equals(to_rope(\"abcde\").substring(1, 3), \"bcd\")\n",[99,1618,1620],{"class":101,"line":1619},48,[99,1621,1622],{},"equals(to_rope(\"abcde\").substring(1, 3).substring(1,1), \"c\")\n",[99,1624,1626],{"class":101,"line":1625},49,[99,1627,1628],{},"equals(to_rope(\"abc\").concatenate(to_rope(\"de\")), \"abcde\")\n",[63,1630,1631,1632,257],{},"Vamos a implementar en este caso el método ",[184,1633,1634],{},"Delete",[63,1636,1637],{},"En primer lugar, creamos el nuevo test:",[89,1639,1641],{"className":298,"code":1640,"language":300,"meta":94,"style":94},"equals(to_rope(\"abcde\").delete(1, 3), \"ae\")\n",[96,1642,1643],{"__ignoreMap":94},[99,1644,1645],{"class":101,"line":102},[99,1646,1640],{},[63,1648,1649],{},"A continuación definimos el método lo más simple posible, aunque la implementación sea falsa:",[89,1651,1653],{"className":298,"code":1652,"language":300,"meta":94,"style":94},"def delete(self, start, length):\n    \"ae\"\n",[96,1654,1655,1660],{"__ignoreMap":94},[99,1656,1657],{"class":101,"line":102},[99,1658,1659],{},"def delete(self, start, length):\n",[99,1661,1662],{"class":101,"line":310},[99,1663,1664],{},"    \"ae\"\n",[63,1666,1667,1668,1671],{},"Como hemos olvidado devolver el resultado con ",[67,1669,1670],{},"return",", TCR elimina tanto el test como el nuevo código escrito. En la consola, observamos el siguiente error:",[89,1673,1678],{"className":1674,"code":1676,"language":1677},[1675],"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",[96,1679,1676],{"__ignoreMap":94},[63,1681,1682],{},"Después de volver a escribir el test, esta vez escribimos el código correctamente:",[89,1684,1686],{"className":298,"code":1685,"language":300,"meta":94,"style":94},"def delete(self, start, length):\n    return \"ae\"\n",[96,1687,1688,1692],{"__ignoreMap":94},[99,1689,1690],{"class":101,"line":102},[99,1691,1659],{},[99,1693,1694],{"class":101,"line":310},[99,1695,1696],{},"    return \"ae\"\n",[89,1698,1701],{"className":1699,"code":1700,"language":1677},[1675],"[master e1db4d5] working\n 1 file changed, 4 insertions(+), 1 deletion(-)\n",[96,1702,1700],{"__ignoreMap":94},[63,1704,1705,1706,1709],{},"Posteriormente, vamos realizando cambios pequeños incrementalmente, hasta implementar todo el código de ",[67,1707,1708],{},"delete",", guardando frecuentemente para comprobar que el test pasa:",[63,1711,1712],{},[184,1713,1714],{},"Paso 1:",[89,1716,1718],{"className":298,"code":1717,"language":300,"meta":94,"style":94},"def delete(self, start, length):\n    return \"a\" + \"e\"\n",[96,1719,1720,1724],{"__ignoreMap":94},[99,1721,1722],{"class":101,"line":102},[99,1723,1659],{},[99,1725,1726],{"class":101,"line":310},[99,1727,1728],{},"    return \"a\" + \"e\"\n",[63,1730,1731],{},[184,1732,1733],{},"Paso 2:",[89,1735,1737],{"className":298,"code":1736,"language":300,"meta":94,"style":94},"def delete(self, start, length):\n    left = \"a\"\n    return left + \"e\"\n",[96,1738,1739,1743,1748],{"__ignoreMap":94},[99,1740,1741],{"class":101,"line":102},[99,1742,1659],{},[99,1744,1745],{"class":101,"line":310},[99,1746,1747],{},"    left = \"a\"\n",[99,1749,1750],{"class":101,"line":316},[99,1751,1752],{},"    return left + \"e\"\n",[63,1754,1755],{},[184,1756,1757],{},"Paso 3:",[89,1759,1761],{"className":298,"code":1760,"language":300,"meta":94,"style":94},"def delete(self, start, length):\n    left = \"a\"\n    right = \"e\"\n    return left + right\n",[96,1762,1763,1767,1771,1776],{"__ignoreMap":94},[99,1764,1765],{"class":101,"line":102},[99,1766,1659],{},[99,1768,1769],{"class":101,"line":310},[99,1770,1747],{},[99,1772,1773],{"class":101,"line":316},[99,1774,1775],{},"    right = \"e\"\n",[99,1777,1778],{"class":101,"line":322},[99,1779,1780],{},"    return left + right\n",[63,1782,1783,1784,899],{},"Y así sucesivamente hasta llegar a la implementación final del método ",[67,1785,1708],{},[89,1787,1789],{"className":298,"code":1788,"language":300,"meta":94,"style":94},"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",[96,1790,1791,1795,1800,1805],{"__ignoreMap":94},[99,1792,1793],{"class":101,"line":102},[99,1794,1659],{},[99,1796,1797],{"class":101,"line":310},[99,1798,1799],{},"    left = self.substring(0, start)\n",[99,1801,1802],{"class":101,"line":316},[99,1803,1804],{},"    right = self.substring(start + length, len(self.string) - start - length)\n",[99,1806,1807],{"class":101,"line":322},[99,1808,1809],{},"    return left.concatenate(right)\n",[63,1811,1812],{},"El resultado final, aplicando TCR de la implementación completa de Rope es el siguiente:",[89,1814,1816],{"className":298,"code":1815,"language":300,"meta":94,"style":94},"# 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",[96,1817,1818,1822,1826,1830,1834,1838,1842,1847,1851,1856,1861,1866,1871,1875,1880,1884,1889,1894,1898,1903,1908,1912,1917,1922,1927,1932,1936,1941,1946,1950,1955,1959,1963,1967,1971,1975,1979,1983,1987,1991,1995,1999,2004,2008,2012,2017,2021,2025,2029,2033,2038,2044,2049,2054,2060,2065,2070,2076,2081,2086,2092,2097,2102,2107,2112,2117,2122,2127,2132,2137,2142,2148,2153,2158,2164,2170,2176,2182,2187,2192,2197,2202,2207,2212,2217,2223,2228,2233,2238,2243,2249,2255,2261,2266,2271,2277,2283,2288,2294,2299,2305,2311],{"__ignoreMap":94},[99,1819,1820],{"class":101,"line":102},[99,1821,1372],{},[99,1823,1824],{"class":101,"line":310},[99,1825,489],{"emptyLinePlaceholder":43},[99,1827,1828],{"class":101,"line":316},[99,1829,1400],{},[99,1831,1832],{"class":101,"line":322},[99,1833,1405],{},[99,1835,1836],{"class":101,"line":369},[99,1837,1410],{},[99,1839,1840],{"class":101,"line":374},[99,1841,489],{"emptyLinePlaceholder":43},[99,1843,1844],{"class":101,"line":507},[99,1845,1846],{},"# Implementation\n",[99,1848,1849],{"class":101,"line":513},[99,1850,1419],{},[99,1852,1853],{"class":101,"line":519},[99,1854,1855],{},"    def delete(self, start, length):\n",[99,1857,1858],{"class":101,"line":525},[99,1859,1860],{},"        left = self[0:start]\n",[99,1862,1863],{"class":101,"line":530},[99,1864,1865],{},"        right = self[start + length : len(self)]\n",[99,1867,1868],{"class":101,"line":536},[99,1869,1870],{},"        return left + right\n",[99,1872,1873],{"class":101,"line":542},[99,1874,489],{"emptyLinePlaceholder":43},[99,1876,1877],{"class":101,"line":547},[99,1878,1879],{},"    def insert(self, rope, start):\n",[99,1881,1882],{"class":101,"line":553},[99,1883,1860],{},[99,1885,1886],{"class":101,"line":559},[99,1887,1888],{},"        right = self[start : len(self)]\n",[99,1890,1891],{"class":101,"line":564},[99,1892,1893],{},"        return left + rope + right\n",[99,1895,1896],{"class":101,"line":570},[99,1897,489],{"emptyLinePlaceholder":43},[99,1899,1900],{"class":101,"line":576},[99,1901,1902],{},"    def __add__(self, addend):\n",[99,1904,1905],{"class":101,"line":582},[99,1906,1907],{},"        return Concatenation(self, addend)\n",[99,1909,1910],{"class":101,"line":587},[99,1911,489],{"emptyLinePlaceholder":43},[99,1913,1914],{"class":101,"line":593},[99,1915,1916],{},"    def __getitem__(self, index):\n",[99,1918,1919],{"class":101,"line":1475},[99,1920,1921],{},"        if type(index) == int:\n",[99,1923,1924],{"class":101,"line":1481},[99,1925,1926],{},"            return self.__get_single_item__(index)\n",[99,1928,1929],{"class":101,"line":1487},[99,1930,1931],{},"        return Substring(self, index.start, index.stop - index.start)\n",[99,1933,1934],{"class":101,"line":1493},[99,1935,489],{"emptyLinePlaceholder":43},[99,1937,1938],{"class":101,"line":1499},[99,1939,1940],{},"    def __len__(self):\n",[99,1942,1943],{"class":101,"line":1505},[99,1944,1945],{},"        raise Exception(\"Should have been overriden\")\n",[99,1947,1948],{"class":101,"line":1510},[99,1949,489],{"emptyLinePlaceholder":43},[99,1951,1952],{"class":101,"line":1516},[99,1953,1954],{},"    def __get_single_item__(self, index):\n",[99,1956,1957],{"class":101,"line":1521},[99,1958,1945],{},[99,1960,1961],{"class":101,"line":1527},[99,1962,489],{"emptyLinePlaceholder":43},[99,1964,1965],{"class":101,"line":1533},[99,1966,489],{"emptyLinePlaceholder":43},[99,1968,1969],{"class":101,"line":1539},[99,1970,1448],{},[99,1972,1973],{"class":101,"line":1545},[99,1974,1453],{},[99,1976,1977],{"class":101,"line":1550},[99,1978,1458],{},[99,1980,1981],{"class":101,"line":1556},[99,1982,489],{"emptyLinePlaceholder":43},[99,1984,1985],{"class":101,"line":1561},[99,1986,1463],{},[99,1988,1989],{"class":101,"line":1567},[99,1990,1468],{},[99,1992,1993],{"class":101,"line":1573},[99,1994,489],{"emptyLinePlaceholder":43},[99,1996,1997],{"class":101,"line":1579},[99,1998,1940],{},[99,2000,2001],{"class":101,"line":1585},[99,2002,2003],{},"        return len(self.string)\n",[99,2005,2006],{"class":101,"line":1590},[99,2007,489],{"emptyLinePlaceholder":43},[99,2009,2010],{"class":101,"line":1596},[99,2011,1954],{},[99,2013,2014],{"class":101,"line":1602},[99,2015,2016],{},"        return self.string[index]\n",[99,2018,2019],{"class":101,"line":1607},[99,2020,489],{"emptyLinePlaceholder":43},[99,2022,2023],{"class":101,"line":1613},[99,2024,1478],{},[99,2026,2027],{"class":101,"line":1619},[99,2028,1484],{},[99,2030,2031],{"class":101,"line":1625},[99,2032,1490],{},[99,2034,2036],{"class":101,"line":2035},50,[99,2037,1496],{},[99,2039,2041],{"class":101,"line":2040},51,[99,2042,2043],{},"        self.leng = length\n",[99,2045,2047],{"class":101,"line":2046},52,[99,2048,489],{"emptyLinePlaceholder":43},[99,2050,2052],{"class":101,"line":2051},53,[99,2053,1463],{},[99,2055,2057],{"class":101,"line":2056},54,[99,2058,2059],{},"        return str(self.rope)[self.start : self.start + self.leng]\n",[99,2061,2063],{"class":101,"line":2062},55,[99,2064,489],{"emptyLinePlaceholder":43},[99,2066,2068],{"class":101,"line":2067},56,[99,2069,1940],{},[99,2071,2073],{"class":101,"line":2072},57,[99,2074,2075],{},"        return self.leng\n",[99,2077,2079],{"class":101,"line":2078},58,[99,2080,489],{"emptyLinePlaceholder":43},[99,2082,2084],{"class":101,"line":2083},59,[99,2085,1954],{},[99,2087,2089],{"class":101,"line":2088},60,[99,2090,2091],{},"        return self.rope[index + self.start]\n",[99,2093,2095],{"class":101,"line":2094},61,[99,2096,489],{"emptyLinePlaceholder":43},[99,2098,2100],{"class":101,"line":2099},62,[99,2101,1524],{},[99,2103,2105],{"class":101,"line":2104},63,[99,2106,1530],{},[99,2108,2110],{"class":101,"line":2109},64,[99,2111,1536],{},[99,2113,2115],{"class":101,"line":2114},65,[99,2116,1542],{},[99,2118,2120],{"class":101,"line":2119},66,[99,2121,489],{"emptyLinePlaceholder":43},[99,2123,2125],{"class":101,"line":2124},67,[99,2126,1463],{},[99,2128,2130],{"class":101,"line":2129},68,[99,2131,1553],{},[99,2133,2135],{"class":101,"line":2134},69,[99,2136,489],{"emptyLinePlaceholder":43},[99,2138,2140],{"class":101,"line":2139},70,[99,2141,1940],{},[99,2143,2145],{"class":101,"line":2144},71,[99,2146,2147],{},"        return len(self.left) + len(self.right)\n",[99,2149,2151],{"class":101,"line":2150},72,[99,2152,489],{"emptyLinePlaceholder":43},[99,2154,2156],{"class":101,"line":2155},73,[99,2157,1954],{},[99,2159,2161],{"class":101,"line":2160},74,[99,2162,2163],{},"        if index \u003C len(self.left):\n",[99,2165,2167],{"class":101,"line":2166},75,[99,2168,2169],{},"            return self.left[index]\n",[99,2171,2173],{"class":101,"line":2172},76,[99,2174,2175],{},"        else:\n",[99,2177,2179],{"class":101,"line":2178},77,[99,2180,2181],{},"            return self.right[index - len(self.left)]\n",[99,2183,2185],{"class":101,"line":2184},78,[99,2186,489],{"emptyLinePlaceholder":43},[99,2188,2190],{"class":101,"line":2189},79,[99,2191,489],{"emptyLinePlaceholder":43},[99,2193,2195],{"class":101,"line":2194},80,[99,2196,1564],{},[99,2198,2200],{"class":101,"line":2199},81,[99,2201,1570],{},[99,2203,2205],{"class":101,"line":2204},82,[99,2206,1576],{},[99,2208,2210],{"class":101,"line":2209},83,[99,2211,1582],{},[99,2213,2215],{"class":101,"line":2214},84,[99,2216,679],{},[99,2218,2220],{"class":101,"line":2219},85,[99,2221,2222],{},"    print(actual, \"didn't equal\", expected)\n",[99,2224,2226],{"class":101,"line":2225},86,[99,2227,1599],{},[99,2229,2231],{"class":101,"line":2230},87,[99,2232,489],{"emptyLinePlaceholder":43},[99,2234,2236],{"class":101,"line":2235},88,[99,2237,489],{"emptyLinePlaceholder":43},[99,2239,2241],{"class":101,"line":2240},89,[99,2242,1610],{},[99,2244,2246],{"class":101,"line":2245},90,[99,2247,2248],{},"equals(to_rope(\"abcde\")[1:4], \"bcd\")\n",[99,2250,2252],{"class":101,"line":2251},91,[99,2253,2254],{},"equals(to_rope(\"abcde\")[1:4][1:2], \"c\")\n",[99,2256,2258],{"class":101,"line":2257},92,[99,2259,2260],{},"equals(to_rope(\"abc\") + to_rope(\"de\"), \"abcde\")\n",[99,2262,2264],{"class":101,"line":2263},93,[99,2265,1640],{},[99,2267,2269],{"class":101,"line":2268},94,[99,2270,489],{"emptyLinePlaceholder":43},[99,2272,2274],{"class":101,"line":2273},95,[99,2275,2276],{},"assert len(to_rope(\"abcde\")[1:4]) == 3\n",[99,2278,2280],{"class":101,"line":2279},96,[99,2281,2282],{},"assert len(to_rope(\"abc\") + to_rope(\"de\")) == 5\n",[99,2284,2286],{"class":101,"line":2285},97,[99,2287,489],{"emptyLinePlaceholder":43},[99,2289,2291],{"class":101,"line":2290},98,[99,2292,2293],{},"equals(to_rope(\"abe\").insert(to_rope(\"cd\"), 2), \"abcde\")\n",[99,2295,2297],{"class":101,"line":2296},99,[99,2298,489],{"emptyLinePlaceholder":43},[99,2300,2302],{"class":101,"line":2301},100,[99,2303,2304],{},"equals(to_rope(\"abcde\")[3], \"d\")\n",[99,2306,2308],{"class":101,"line":2307},101,[99,2309,2310],{},"equals((to_rope(\"abc\") + to_rope(\"de\"))[3], \"d\")\n",[99,2312,2314],{"class":101,"line":2313},102,[99,2315,2316],{},"equals(to_rope(\"abcde\")[0:4][3], \"d\")\n",[58,2318,2320],{"id":2319},"conclusiones","Conclusiones",[63,2322,2323],{},"En general, he podido observar que TCR es muy útil para aprender a implementar cambios y a refactorizar paso a paso. Puede que al principio parezca un poco tedioso tener que reescribir el código, pero precisamente por eso es por lo que acabas escribiendo menos código, optimizando y dándole valor a los tests. Como dice Kent Beck en su famosa frase:",[145,2325,2326],{},[63,2327,2328],{},"\"Make the change easy and then make the easy change\"",[63,2330,2331],{},"Por lo tanto, recomendaría a todo el mundo que probase esta técnica, la original, bien sea con un ejemplo sencillo o no tanto, ya que considero que también ayuda a entender parte de TDD, cambiando tu punto de vista, puede que de una forma más forzosa, pero esto se puede mitigar con las variaciones como ya hemos visto.",[63,2333,2334,2335,2337,2338,2340,2341,2343],{},"En cuanto a mí y al efecto de TCR, decir que considero necesario probar el original pero para el día a día utilizaría por ejemplo la variación ",[184,2336,380],{}," o ",[184,2339,434],{},", puede que incluso ",[184,2342,898],{}," en alguna de sus versiones.",[63,2345,2346,2347,2350],{},"Por ejemplo realizando el ejemplo me he dado cuenta de que a veces me encontraba haciendo ",[96,2348,2349],{},"Ctrl+Z"," para volver a obtener el código que fallaba y continuar desde ahí.",[63,2352,2353,2354,2359],{},"De hecho el propio Kent Beck en otro ejemplo de TCR, el de ",[72,2355,2358],{"href":2356,"rel":2357},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=ZrHBVTCbcE0",[76],"substring, TCR style"," se crea un archivo en el que guarda los mensajes de error cada vez que reinicia para guardar el output y los logs de errores y así poder encontrar el fallo en su código.",[63,2361,2362,2363,899],{},"Utiliza el siguiente comando con la extensión Run on Save, en el cual añade una redirección del stderror al stdout y lo guarda en un fichero llamado ",[96,2364,2365],{},"tcrfeedback",[89,2367,2369],{"className":91,"code":2368,"language":93,"meta":94,"style":94},"cd ${workspaceFolder} && python ${file} > ..\u002Ftcrfeedback 2>&1 && git commit -am working || git reset --hard\n",[96,2370,2371],{"__ignoreMap":94},[99,2372,2373],{"class":101,"line":102},[99,2374,2368],{},[58,2376,2378],{"id":2377},"bibliografía","Bibliografía",[63,2380,2381],{},[184,2382,2383],{},"Artículos de Kent Beck:",[154,2385,2386,2391],{},[157,2387,2388],{},[72,2389,123],{"href":121,"rel":2390},[76],[157,2392,2393],{},[72,2394,77],{"href":74,"rel":2395},[76],[63,2397,2398],{},[184,2399,2400],{},"Vídeos de YouTube:",[154,2402,2403,2408,2413,2420],{},[157,2404,2405],{},[72,2406,1310],{"href":1308,"rel":2407},[76],[157,2409,2410],{},[72,2411,2358],{"href":2356,"rel":2412},[76],[157,2414,2415],{},[72,2416,2419],{"href":2417,"rel":2418},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=IIKndRX5qHw",[76],"TCR in VSCode",[157,2421,2422],{},[72,2423,2426],{"href":2424,"rel":2425},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=FFzHOyFeovE",[76],"Understanding Legacy Code with TCR",[63,2428,2429],{},[184,2430,2431],{},"Otros artículos:",[154,2433,2434,2442,2449,2456],{},[157,2435,2436,2441],{},[72,2437,2440],{"href":2438,"rel":2439},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-test-commit-revert-a-test-alternative-to-tdd-6e6b03c22bec",[76],"TCR. How to use? Alternative to TDD?"," -- Thomas Deniffel",[157,2443,2444,2441],{},[72,2445,2448],{"href":2446,"rel":2447},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-tool-test-commit-revert-8aa91d26e61f",[76],"TCR Tool",[157,2450,2451,2441],{},[72,2452,2455],{"href":2453,"rel":2454},"https:\u002F\u002Fmedium.com\u002F@tdeniffel\u002Ftcr-variants-test-commit-revert-bf6bd84b17d3",[76],"TCR Variants",[157,2457,2458,2463],{},[72,2459,2462],{"href":2460,"rel":2461},"https:\u002F\u002Fwww.honeybadger.io\u002Fblog\u002Fruby-tcr-test-commit-revert\u002F",[76],"Test && Commit || Revert (TCR)"," -- David Tanzer",[63,2465,2466],{},[184,2467,2468],{},"Podcast:",[154,2470,2471],{},[157,2472,2473,2478],{},[72,2474,2477],{"href":2475,"rel":2476},"https:\u002F\u002Fwww.hanselminutes.com\u002F663\u002Ftest-commit-revert-with-kent-beck",[76],"The HanselMinutes Podcast"," -- Scott Hanselman",[63,2480,2481],{},[184,2482,2483],{},"Código del ejemplo:",[154,2485,2486],{},[157,2487,2488],{},[72,2489,2492],{"href":2490,"rel":2491},"https:\u002F\u002Fgithub.com\u002Fmiguelfernandezdev\u002Ftcr-rope",[76],"miguelfernandezdev\u002Ftcr-rope",[2494,2495,2496],"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":94,"searchDepth":310,"depth":310,"links":2498},[2499,2500,2501,2502,2506,2518,2523,2524],{"id":60,"depth":310,"text":61},{"id":114,"depth":310,"text":115},{"id":178,"depth":310,"text":179},{"id":217,"depth":310,"text":218,"children":2503},[2504,2505],{"id":222,"depth":316,"text":223},{"id":249,"depth":316,"text":250},{"id":272,"depth":310,"text":273,"children":2507},[2508,2509,2510,2511,2512,2513,2514,2515,2516,2517],{"id":276,"depth":316,"text":277},{"id":328,"depth":316,"text":329},{"id":379,"depth":316,"text":380},{"id":433,"depth":316,"text":434},{"id":465,"depth":316,"text":466},{"id":599,"depth":316,"text":600},{"id":706,"depth":316,"text":707},{"id":813,"depth":316,"text":814},{"id":888,"depth":316,"text":889},{"id":1001,"depth":316,"text":1002},{"id":1078,"depth":310,"text":1079,"children":2519},[2520,2521,2522],{"id":1082,"depth":316,"text":1083},{"id":1301,"depth":316,"text":1302},{"id":1316,"depth":316,"text":1317},{"id":2319,"depth":310,"text":2320},{"id":2377,"depth":310,"text":2378},"2021-02-02","Exploración de la técnica TCR (test && commit || revert) introducida por Kent Beck, sus variaciones y un ejemplo práctico con Rope en Python.","md",{},{"title":15,"description":2526},"TGIYgA0RX4FXB5jfxNc3f2UydJX0V454QIBwTU12MdU",1782058039864]