- Tags : spock, testing

This article has been published on DZone with editor revision so I recommend you to read it there.

Introduction and motivation

Recently I gave a talk in my local Java User Group about unit testing. Some of the content of the talk was about some popular libraries you can use in your Java project. I’ve reviewed JUnit4, JUnit5 and Spock framework. Many of the attendees were quite surprised with the differences. In this post, I will summarize the most commented: assert, parametrized tests and mocking.

I always like to demonstrate the concepts with examples and live coding so I chose a simple algorithm: Fibonacci number calculator. If you don’t know it, it’s just to generate numbers which are the sum of the two previous ones in the series: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377.

I used the typical (and with very bad performance) implementation:

    private static int fibonacci(int n) {
        if (n <= 1) return n; else
            return fibonacci(n-1) + fibonacci(n-2);


I started explaining JUnit4. It makes sense because it’s the most popular library and the base for many others. I started explaining assertTrue and then, more advances usages, including assertEquals.

    public void improvedFibonacciTestSimple() {
        FibonacciWithJUnit4 f = new FibonacciWithJUnit4();
        assertEquals(f.fibonacci(4), 3);

If it’s false, it would give you an error like:

Expected :3
Actual   :2

Not very spectacular but quite useful.

The next thing was to show how to write a parametrized test, a nice feature very useful for test algorithms. In JUnit4 is quite tricky. You need to create a Collection with the annotation @Parameters.

    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}

Then we create some local variables and a constructor:

    private int fInput;
    private int fExpected;

    public ParametrizedFibonacciJUnit4(int input, int expected) {
        fInput = input;
        fExpected = expected;

and finally, we can use the assertEquals:

    public void test() {
        FibonacciWithJUnit4 f = new FibonacciWithJUnit4();
        assertEquals(fExpected, f.fibonacci(fInput));

It’s quite verbose and if the test fails, you would obtain a message which doesn’t indicate clearly the order or the parameters used (but your IDE probably will help on that):

Expected :0
Actual   :1

I didn’t explain mocking here because an external library as Mockito is usually required when you want to use mocking with JUnit4. Mockito is great but I didn’t have enough time to explain it.


Since September 2017, JUnit5 is considered stable and it should be your choice over JUnit4 for several reasons. It has better support for Java 8 and Lambdas, it’s compatible with JUnit4 (you can have both which it’s great to migrate in a progressive way) and it provides new runners and better integrations.

I repeated the same process. First, show an assertEquals:

    public void bestFibonacciTestSimple() {
        FibonacciWithJUnit5 f = new FibonacciWithJUnit5();
        Assertions.assertEquals(f.fibonacci(4), 3);

There are important advances in other assert, for instance the timeout, but for assertEqual with integers, it’s practically the same. Also the message is similar if there is an error:

Expected :3
Actual   :2

Where we can find important changes is in the parametrized test. First, we need to use the @ParametrizedTest annotation and we can specify a name using { and } to indicate important parameters as index as arguments.

    @ParameterizedTest(name = "run #{index} with [{arguments}]")

Now we can define our test. We start defining the entries to test function with the annotation @CsvSource. Each item will replace the parameters in the test function, in this case, input and expected.

    @CsvSource({"1, 1", "4, 3"})
        public void test2(int input , int expected) {
                FibonacciWithJUnit5 f = new FibonacciWithJUnit5();
             	Assertions.assertEquals(f.fibonacci(input), expected);

This is lot better than the JUnit4 implementation. Also, if there is a fail in the test, we obtain a better message indicating the difference, the index causing the fail and the used parameters.

Finally, it’s the same for mocking as JUnit4: you normally use an external library so I didn’t explained it.

Spock framework

The last one was the Spock framework. It’s based in Apache Groovy. If you don’t know Groovy, it’s a language which you can use with the JVM and it interact very well with Java. It allows write less code in a more clear way. It’s very powerful. Some years ago we started to use it for "non critical" development: tests, dependency management, Continuous Integration, load testing and in any place where we need some configuration file avoiding XML, JSON or any format like that. We continue to develop in Java the core of our software and it isn’t a problem because both languages play very well together. If you know Java, you know Groovy…​ so we have the best of both worlds.

Write a test in Spock is quite different, it would be like this:

    def "Simple test"() {
        BadFibonacci f = new BadFibonacci()

        f.fibonacci(1) == 1
        assert f.fibonacci(4) == 3

Basically, we can use def where we don’t care about the type. The name of the function can be defined between quotation marks, which allow us to use better naming for our tests. We have some special words as setup, when, expect, and, etc. to define our test in a more descriptive and structured way. And we have a power assert, which is part of the language itself, proving nice messages:

Condition not satisfied:

f.fibonacci(4) == 2
| |            |
| 3            false

Expected :2

Actual   :3

It provides all the information: the returned value (actual), the expected value, the function, the parameter, etc. assert is Groovy is really handy.

Now it’s the turn for the parametrized test. It would be something like this:

    def "parametrized test"() {
        BadFibonacci f = new BadFibonacci()

        f.fibonacci(index) == fibonacciNumber

        index | fibonacciNumber
        1     | 1
        2     | 1
        3     | 2

After show this I heard some 'oooh' in the audience. The magic of this code is: you don’t need to explain it! There is a table in the where: section and the values in expect: are automagically replace it in each iteration. If there is a fail, the message is crystal clear:

Condition not satisfied:

f.fibonacci(index) == fibonacciNumber
| |         |      |  |
| 2         3      |  4
|                  false

Expected :4

Actual   :2

Then I’ve introduced very shortly mocks and stubs. A mock is a object you create in your test to avoid to use a real object. For example, you don’t want to do real web requests or print a page in your tests, so you can use a mock from an interface or another object.

    Subscriber subscriber = Mock()

    def "mock example"() {

        1 * subscriber.receive("hello")

Basically, you create the subscriber interface as Mock and then you can invoke the methods. The 1 * is another nice feature of Spock, it specify how many messages you should receive. Cool, right?.

In some occasions, you need to define what return the methods of your mocks. For that, you can create a stub.

    def "stub example"() {
        subscriber.receive(_) >> "ok"


        subscriber.receive("message1") == 'ok'

In this case, with the >> notation we are defining the method receive should return ok independently of the parameter (_ means any value). The test pass without any problem.


I don’t like to recommend one library or another: all of the them have their use cases. It’s pretty clear we have great options in Java and I just give some examples. Now it’s your turn to decide which it’s better for you. The only thing I can say: write test and master your library of choice, it would make you a better developer!

If you want to take a deeper look to the examples, you will find them in this GitHub repository. Enjoy!

- Tags :

Third year in a row writing my proposals for the new year, yeah!. Again I’m not going to spend time reviewing the previous year, I let that for “internal use”.

There is something I’ve discovered in 2017 which wasn’t planned (or correctly estimated): my family require more time from me now than ever. “Family first” is a motto very respected by me (and my employer) so I had to restrict a lot of things of my life to focus on that. Now I’m more aware of it, so I will continue with the “family first” approach but I will try to do it in a more maintainable way: I have to focus more, be more realistic and have more help.

My first proposal for 2018 is recover my healthy style of life. I never in my life did so less sport as the past year. From 4-5 days per week in 2016 to 1 (or even less) in 2017. As a result, I have also to loose weight. I’m even surprised I was able to maintain my positive attitude with that “stress burning” low rate. This proposal isn’t very original but it’s important.

Second goal, my work in Optare Solutions. I will continue for a while (but not forever) as Technical Director. It’s a challenging role which I’ve accepted to make things happen even if, ironically, I see it as step back in my career. I’m very happy with some “small victories” in the past year (which are a team effort, not mine) as open new offices in Ourense, sponsor the VigoJUG and the GDGDevFest, provide the facilities to make a k8s workshop organised by VigoLabs, improvements in the workflow of some teams, the Abbilia initiative and, the most important, start to think as group (or even better, a big family) how to improve the company embracing continuous improvement instead of making individual efforts in silos or too big programs.

Yet, big cultural changes take time. Optare is a great place to work, the bar is really high to make a real difference because it’s a company with a long history of successes and well done projects so change the formula, even to improve it, is a complex task. I think we are very lucky to have a company like this in Galicia and I will continue to help it as much as possible.

I also will continue also contributing to NetApps, an Optare’s team where I have been working for years now and where I can share time and experiences with some of the best engineers I’ve ever met. My work as Technical Manager is quite lonely so this part-time role allows me to continue doing some “real world” work and recharge batteries when needed. I’m very thankful for that. This is going to be a great year for this team, I’m really excited and looking forward to the new challenges.

Last, but not least, I want to continue contributing to the local community:

Co-organize the VigoJUG has been a great experience. I will like to continue it with a clear goal: my vision (I’m not the only organizer) for the JUG is a group of friends who share time and knowledge. I personally would like to keep it in that way. Make something bigger is a temptation but I don’t believe it will work in the long term.

Also, I will like to launch the CoruñaJUG. I will be more present in that city in 2018 but don’t be afraid, I will continue cheering the Celta soccer team.

The previous year I’ve spent some time helping to launch VigoTech Alliance. It seems VigoTech as project is ended, most of the proposals to improve it have been rejected or ignored, which it’s fine: it means it’s good as it is. I will continue supporting the Alliance when needed but I don’t expect to spent too much time on it in 2018 (but I will be happy to be wrong it there are new initiatives).

I have also spoken in several local meetups as ForoDeEmprego, VigoLabs, GDGDevFest, Librecon, PythonVigo, and, of course, VigoJUG, and participated in hackathons (Refugal, GPUL). I’m very happy with this. It’s funny I did the opposite of the normal path: started in international conferences speaking in English, and then, move to local ones speaking in my mother tongue. I met great people and I did things I never through I would do it (as a motivational talk). I will try to continue that path as much as possible but I probably will need to slow down a bit.

Finally, last year I tried to contribute to some big open source projects (Kafka, Elastic, Lucene). It makes sense for several reasons but I failed. It requires some constancy and I just could dedicate time very occasionally. Out of the office, I spent most of the time playing with frameworks, testing new tools or reading books. I also did some MOOCs (Scala and Oracle Cloud). Learn for the simple pleasure of learn is my passion. I would like to do some serious PRs in 2018 but it’s going to be impossible :-(

Instead of that, I will try to update more frequently the blog with technical content. I’ve reading a lot about JVM performance in the last months, I have some experience and it’s a interesting subject for the blog. I will also try to attend a couple of international events related to the JVM. And, only if I have time, try to do some open source contributions in that field.

In summary, my proposal for 2018 is to be more focused in some things and let others pass. Happy 2018!

- Tags : event, coding

Some weeks ago I had the opportunity to speak in the Python Vigo meetup. It was just a lightning talk, 5 minutes. Why?. I really enjoy Python Vigo meetups, they are useful, fun and I always learn something new. So, when I read an email asking for speakers, I proposed the only Python related subject I know something about: Jython.

I have been using Jython for years to manage Weblogic servers. Being honest, I don’t like it, without any doubt I would prefer to use Groovy, in fact, that’s what we usually do. But I thought it may be interesting for someone in the Python community.

It was my first time doing a talk so short. Also I’m more used to do them in English. It was clear to me how hard it’s to do a 5 minutes talk, even for an easy subject like this.

I just wanted to say three things, if possible, without slides:

  • Don’t use Jython if you are looking for Python performance improvement.
  • Use Jython to explore Java classes or change them dinamically using Python syntax.
  • Use Jython to combine it with Java programs, to load configuration dinamically, something like a DSL (Domain Specific Language) but using Python as language.

Probably, the talk wasn’t clear for most people… Also I miss 15 seconds more to finish my example. Yet, some people asked me some things later related to it… so I’m not completely sad with the result and I learnt how hard it’s to do a good lightning talk.

In case you are insterested, in this GitHub repo you can find the things I did and also a Readme with the explanation. The talk is recorded so you can watch it here:

Notes for the future:

  • Be careful with short talks, they are harder than the long ones.
  • Use introductory slides, at least one.
  • It’s better to say less and clear than more and hard to understand.

- Tags : hackathon, event, coding

This weekend I’ve participated in the Refugal, a hackathon for refugees. It isn’t the fist time I participate in a hackathon but this one has been special in some way. Do you know those videos of strangers who doesn’t know each other but they start to play together in the middle of the station?. Something like this one:

Well, this is exactly what I felt some times during the weekend.

Let me start in the beginning. I arrived soon and started to speak with people. A nice introduction to the hackathon by David and Edo, a game to improve creativity and we started with the ideas.

I know I’m more a doer than a thinker. But I like to collaborate so I proposed two ideas: a bot to help communicated refugees and volunteers and a network of home webcams interconnected to provide real-time information to the refugees and also to people at home. My presentation of the ideas was quite bad, mainly I was speaking about a bot… but not all people were developers so later some people told me they didn’t know what a bot is. Big fail.

My “bot” idea was the third one with more votes. It was another one I liked a lot more (the first one) but I stayed with the bot. Very soon the team was formed: four developers (2 web, 1 mobile and myself) and one designer. It was the more technical team, probably because of my bad explanation of the idea.

We moved to a room. We discussed the idea for a while in front of a whiteboard and we separated the bot in different sections: Facebook Connector, Telegram Connector, Bot Logic, Design (logo, web, slides, etc.) and DevOps (my part: server, docker, Apache Kafka, etc.). In less than 30 minutes, everything was ready and we were working on the project.

Hard to explain what happened after that, four guys and a designer working like one person. Lines of code, commands, systems, drawings… everything happening so fast, 133 commits in less than 10 hours. I remember the designer stopping her drawing and asking: “Ey, guys, I know some development but I’m not understanding what are you are talking about. Is it complex what you are doing?”. The four developers in the room said Yes! at the same time. You may take a look to github repo: Docker, Docker Compose, AWS, Apache Kafka, four nodeJS and one SQLLite… Everything integrated and working!. And the designer, what a great work she did. You may have the best backend in the world, but without a good designer, people isn’t going to appreciate it. The bot web is done by her, nice work!

Working as a team

From time to time someone proposing something: always accepted or postponed to the end if there was time for that. And back to work, not even a discussion in the whole weekend. Sometimes we even forget go to eat (but a great organization and the impacthub Vigo is so cool!). Also we had some funny moments, smiles and lot of complicity.

I even had time to add a SMS connector using Nexmo, the best 15 euros spent in my life. First time I develop something using nodejs. I don’t like it yet but I have to say it was a good decision for the project: lot of libraries and SDKs, easy to deploy and fast to develop.

Of course, we run the demo during the presentation, it was great to see people sending messages from different networks and speaking with our bot. So fun!. I will pay good money for a video of that moment but everybody was too busy sending messages.

RefuBot presentation

This morning, when I terminated the EC2 instance hosting the bot, I felt some type of sadness. Yet it has been great to meet such great team this weekend. And who knows?. RefuBot works and it scales. I can assure that. Maybe someone will discover it in the future and RefuBot becomes real. If it’s really useful, that would be fair.

Photos are from this Picassa album.

- Tags : java, vim, neovim, groovy, gradle

I’ve tried so many times change Intellij or Eclipse by vim.. But when it’s related to Java is really hard to find a real alternative to those IDEs. And when we speak about Groovy, it’s even worse. Yet I use vim a lot: edit files, write blog posts, etc. Also my Chrome and Thunderbird configuration uses Vim shortcuts, so I keep myself more or less trained.

Some weeks ago I’ve discovered this blog post Use Vim as a Java IDE and I want to give it another opportunity. Let’s start.

Neovim in Fedora

This is straight-forward:

sudo dnf -y copr enable dperson/neovim
sudo dnf -y install neovim
sudo dnf -y install python3-neovim python3-neovim-gui

For Fedora 25 is even easier:

sudo dnf -y install neovim
sudo dnf -y install python2-neovim python3-neovim

For other systems, just check the official Neovim documentation.

We’ll need some other plugins:

sudo dnf -y install astyle

Install vim-plug

Again, this is straight-forward following the official instructions:

curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \

Install plugins

This is when things become messy. Start editing ~/.config/nvim/init.vim to add the plugins:

""""    vim-plug     """"
call plug#begin('~/.local/share/nvim/plugged')

" Others

Plug 'scrooloose/nerdtree', { 'on':  'NERDTreeToggle' }
Plug 'majutsushi/tagbar'

" Java development

Plug 'sbdchd/neoformat'
Plug 'artur-shaik/vim-javacomplete2'
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
Plug 'neomake/neomake'

" Initialize plugin system
call plug#end()

""""    deoplete     """"
let g:deoplete#enable_at_startup = 1
let g:deoplete#omni_patterns = {}
let g:deoplete#omni_patterns.java = '[^. *\t]\.\w*'
let g:deoplete#sources = {}
let g:deoplete#sources._ = []
let g:deoplete#file#enable_buffer_path = 1

""""  Java Complete  """"
autocmd FileType java setlocal omnifunc=javacomplete#Complete

""""     neomake     """"
autocmd! BufWritePost,BufEnter * Neomake

""""     neoformat   """"
augroup astyle
  autocmd BufWritePre * Neoformat
augroup END

Open nvim and type :PlugInstall.


Now, if you open a Java project, you should have auto completion, auto format and lint capabilities.

I will update this blog post with new things as soon as I have them.


  • [ ] Add Groovy support.
  • [ ] Add some screenshots or recording.

- Tags :

Last year I wrote a post with my goals for first time in my life. It was something new and I must say I’ve read it many times to see how things are going. It has been a good experience and I want to repeat it this year.

2016 year has been, in the professional aspect, the best year of my life. Probably also in the personal aspect with a new kid. I’m not going to speak too much about the last year, it was just great for me. I’ve achieved some of the goals, failed in others, and there was new ones… That’s the life.

Beginning of 2017 has been very stormy, even hard. Lots of problems, work and changes. That’s the reason I’ve waited so much to write this blog post. The good point when things are difficult is you are going to learn a lot. I have discovered things about my environment and myself which I’ve never thought about before.

The first thing I’ve discovered is the engineers “ego problem”. When you are fighting hard to make a project success, when you solve difficult problems, when you have to make your customers understand the consequences of their decisions or you are training younger engineers… Slowly but relentless the ego problem is growing on you. And let’s make this clear: I’m guilty. My motto always was: “No project where I work can’t fail”. Now I understand how wrong I was. Project can be a huge success for the customer, but a big fail for your company or the team because the lost of trustiness or the casualties.. And now I know I had some failed projects in my life.

Other good sign of this is my proposals for the last year. All of them are about me. Again, the ego problem. The reason was my fear to be out of the IT jobs market. Be without a project or a salary really scares me. I think this is irrational, I have a B plan just in case I loose my job and I wasn’t able to find a new one. Also I’m receiving job offers every week, some of them really good ones (that does not help with the ego problem either). Probably it’s more related to spend days without to do something useful and because I feel lucky to do what I do.

I deserve a punishment and 2017 will be the year to start with it.

First goal: repeat every morning in front of the mirror “Be humble” three times. If in some point of the day I fail on that, I will go to the involved person and I will ask for forgiveness. It doesn’t matter if it’s a small fail or the involved person is a jerk, I will do it anyway.

Second goal: make the company where I work a better place to work. This means stop claiming and start to actively work in the solution to solve the problems I’m claiming about. Let’s make this clear: companies must earn money. But at the same time, employees must be feel involved, happy and positive. Both things are related. I’m not sure how to do this or the real impact which I can have with my actions in a big company. I’m just one, I’m not a superhero and the company where I work is good but not perfect, there is always room for improvement. It doesn’t matter my role, I will just try to do as much as possible. Also I want to focus in the youngest engineers. In my opinion senior engineers should be more involved in professional evolution of the younger ones and I want to lead by example on that.

Third goal: return something to your local community. I can’t believe this is a goal I have when I’m 36 years old. Start so late on this is probably the biggest failure in my career. It was even more clear to me after an event last week for students. Two of the speakers made great points on the importance of contribute to your local community. I admire those two guys. I don’t pretend to be like them but at least I want to do something. Start the VigoJUG is a beginning. I hope to find other ways to contribute during the year. If you are reading this and you have any idea, please, let me know.

The last goal isn’t really a goal but a fear. Yesterday a fellow said I’m a Project Manager. It has been very painful for me because I realize he was partially right and I don’t want that role anymore.

Dilbert Has Management Potential

Last year I’ve been developing a lot but 2017 has been crazy and I’ve been more focused in solve problems and get things done… So my technical work has been reduced to Pull Request reviews and some time developing in the weekend. Rest of the time: mails, phone calls, write documents and meetings. I don’t want to spend the rest of my life doing only those things. I know they are useful and they are the way to achieve the goals I’ve described before. But I’ve spent 20 years of my life dealing with computers, I’ve trained myself to learn vim shortcuts, researching how to write clean code, use the command shell, etc. and I don’t want to loose that. It makes me feel happy and full. If I don’t do it, I will end being sad and frustrated, so I have to found a way to conciliate both things. Any advise will be welcomed.

I want to finish this post asking for forgiveness. Specially during my time in Accenture I did a lot of wrong things. Not even close but I pushed too hard some times in Optare. If I did something which has troubled you, I’m very sorry. I promise: I will be humble, I will be humble, I will be humble.

- Tags : development, devops, gke, terraform

In my previous post, Deploy on Kubernetes GKE with Terraform, we’ve seen how to start to use kubernetes but in a very simple way. The next thing we would like to do is persist the configuration, so we don’t need to reconfigure our bot each time we start the cluster. This post explain how to do it from the configuration created in the previous one.

Again we’ll use Leanmanager bot but everything applies to any other system which needs to store configuration or data in a database. In the case of Leanmanager we are using Boltdb, a pure Go key/value store. Boltdb is great for development but it doesn’t support to have more than one process opening the same database file, so it may be problematic if we want to have more than one Docker instance at the same time. Yet it’s enough for our purposes and the process is similar for Consul which it’s already in the Roadmap.

Create your persistent disks

If we want to persist data, we are going to need a disk, that’s common sense. In GCE we can do it very easily:

gcloud compute disks create --size 1GB leanmanager-disk

But again, we want to do it in an automated way with Terraform. Use the following file leanmanager-disk.tf:

variable "disk_name" {
  default = "leanmanager-disk"

resource "google_compute_disk" "default" {
name  = "${var.disk_name}"
  type  = "pd-ssd"
  zone = "${var.region}"
  size  = "1"

If you want to know more about it, visit the Terraform Google_Compute_Disk reference documentation.

Tell the container about the disk

In our previous post, we’ve launched the bot using kubectl run. This is OK for simple configuration but if we need to have something more complex, it doesn’t scale. We can create a pod, a group of one or more containers, using a YAML file like this:

apiVersion: v1
kind: Pod
  name: leanmanager
    name: leanmanager
    - image: antonmry/leanmanager:latest
      name: leanmanager
        - name: LEANMANAGER_TOKEN
        - name: LEANMANAGER_PATHDB
          value: /mnt
          # This name must match the volumes.name below.
        - name: leanmanager-persistent-storage
          mountPath: /mnt
    - name: leanmanager-persistent-storage
        # This disk must already exist.
        pdName: leanmanager-disk
        fsType: ext4

The file is auto-explanatory except the value LEANMANAGER_TOKEN_TEMPLATE. I don’t want to hardcode the Token here because the file will be uploaded to Github. Instead of that, I want to use my local environment variable LEANMANAGER_TOKEN but this isn’t supported yet in K8s, see Kubernetes equivalent of env-file in Docker.

So I’ve created a YAML template and in the Terraform file changed the last local-exec to:

  provisioner "local-exec" {
    command = "cp leanmanager-pod-template.yaml leanmanager.tmp.yaml && sed -i -- 's/LEANMANAGER_TOKEN_TEMPLATE/${var.LEANMANAGER_TOKEN}/g' leanmanager.tmp.yaml"

  provisioner "local-exec" {
    command = "kubectl create -f leanmanager.tmp.yaml"

  provisioner "local-exec" {
    command = "rm -f leanmanager.tmp.yaml"

Basically, I’m replacing strings with sed. Other more sophisticate approaches are possible as K8s secrets or Ansible, but this is simple and enough for the task we want to do.

Create the pod and test

Time to create the cluster and the pod:

terraform plan

The bot should connect. Now we can do some changes in the configuration, delete the pod:

kubectl delete pod leanmanager

Create it again:

kubectl create -f leanmanager.yaml

And check the status with the following commands and, once it’s in Running state, see if everything has been persisted:

kubectl get pod leanmanager
kubectl logs leanmanager


Persist data in Kubernetes is quite easy, even if you are going to do it automatically.

If you want to check all the files, the full project and the associated PR are in Github.

Not already linked but useful resources

Older posts are available in the archive.