Grace Teng

Blog

Advice for Le Wagon Web Dev Bootcamp grads

Congrats, you’re done with Le Wagon’s Web Development bootcamp. You have 360 hours of classroom time, three Rails applications, and zero professional software development experience. What’s next?

Le Wagon maintains its own career playbook, but depending on where you are and what your goals are, you may find yourself struggling to adapt its advice to your circumstances. I’m just one data point, but what I hope to do here is offer another perspective and perhaps a little direction as someone who was in your shoes not too long ago.

Can I really get a job?

Yes.

I had my own doubts after completing Le Wagon (batch #454), even though code came to me easily during the bootcamp. The doubt came from feeling that everyone who got a job must simply be “better” than me in some way, and that in order to get a developer job, I had to first learn more, practice more, etc. My way of fending off that uncertainty was to be a TA for the next full-time batch in Singapore, #512, and postpone the job search while I consolidated my skills. I don’t regret TA-ing at all, but my decision to TA was definitely rooted in fear of the unknown.

Miguel and Prima, my lead instructors, told me I had the chops to land a junior developer role, and I should go for it. In my mind, I discounted their enthusiasm because I thought, what else would they have said? However, the reality is, if you don’t apply for jobs, you truly have no idea where you stand in the job market. Once you start applying, you’ll get a better sense of what types of jobs are a good fit for bootcamp graduates, and how to best position yourself for them.

Part of the challenge in this job-hunting phase is that you will have to cast a wide net, and apply to jobs that use technologies outside of what you’ve used in Le Wagon. Some companies will be open to hiring juniors who are willing to learn the stack, while others will want you to have a base competency in the tech stack before hiring you. Some of the requirements are learnable within a relatively short time, while other competencies take longer. How can you determine which jobs you are suited for?

In my opinion, you can’t. My approach to job applications was essentially spray-and-pray. I’m ambivalent about this approach: it will be clear to many companies that you have no idea what you are signing up for (which is true), and it can be demoralising for you, the applicant. On the other hand, there will be places willing to hire a junior developer who is trainable. Unfortunately there is no way I know of to distinguish such organisations from the rest, short of applying to all of them and seeing what comes back.

The majority of places I applied to never replied, which is par for the course. I’ve had interviews that went really well, interviews that went nowhere, and one interview that went poorly. All of these interviews help you to learn about the tech job market, regardless of the interview outcome. The interview that didn’t work out sticks in my mind because even though it was a junior role, it required specialised technical knowledge that I simply did not have. The interviewer highlighted the total mismatch between my skillset and the job description, and estimated that it would take me six months of grunt work, reading and writing documentation, before I would be ready to make any code contribution.

On the one hand, I came out of that interview totally deflated. On the other, I learned a little bit more about an area of software development, and what would be expected of a software engineer in that domain, that I might not have otherwise.

With this information, you can start to develop an eye for what jobs you are more likely to be a candidate for, and to create a roadmap for professional development down the line.

How do I make myself competitive for developer positions?

As a career changer, you are not a blank slate.

You are likely to be applying to junior developer jobs, where your main competition is fresh university and polytechnic graduates. You have disadvantages, for sure: many of these graduates will have had a classical computer science education. Their CS knowledge is a known quantity to employers. It’s easy to feel that a nine-week bootcamp, or 24 weeks for part-timers, cannot possibly compare with a four-year degree.

On the other hand, you also have advantages that you can and should lean into:

  • You have some general professional skills that come only after years of working experience, such as the ability to independently define and achieve goals, managing competing demands on your time and energy, and the unspoken knowledge of how to conduct yourself in the workplace.
  • You have some skills specific to your previous jobs: for me, I highlighted my ability to communicate and to teach, honed from my previous lives as a documentary filmmaker, as a teacher and in customer support.
  • Bonus points if you have specialised industry or functional knowledge: understanding how marketers work is great if the company is building a product for marketing professionals. Understanding the logistics industry is great if the company is building technology to solve a supply chain management problem.
  • Bonus bonus points if your industry knowledge is in a heavily-regulated field: law, finance, insurance, healthcare, cybersecurity, real estate, etc.

Another thing to understand is that any employer that seriously considers hiring you is not hiring you on the basis of what you learnt in nine or 24 weeks, but on the basis of what you demonstrated.

This is an important distinction: you did not learn enough during the bootcamp to do your job. This is simply a fact: if everything a developer needed to know could be taught in nine weeks, the job market would look drastically different. Instead, what completing the bootcamp demonstrates is that you are trainable as a developer: you learned a tremendous amount in a compressed timeframe, you have the grit to work through things that are technically challenging, and now you have some foundation for learning everything else you need to learn on the job.

Performance expectations for junior developers typically revolve around attitude and ability to learn, not around the depth of your technical skills. This helps to reframe the problem: for jobs where you are a viable applicant, you are not competing against other applicants who know more than you do. You are being considered on the basis of your ability to learn, and that is something you definitely have.

Are my Le Wagon projects really good enough for a portfolio?

The time pressure of Demo Day forces many Le Wagon teams to focus only on features that will be demonstrated during Demo Day, and ignore any features or bugs outside of the demo’s path. It can end up being a very delicate operation: my team found a bug just before Demo Day that forced us to perform the checkout in a very specific order, for example. You may feel that your LW projects are not “ready” to be put into a portfolio that you can show to potential employers. That’s understandable.

In that case, what do you do with your projects? I can only really offer suggestions based on what I did, but hopefully you will find it useful as well.

The goal here is to show a baseline level of technical skill and an abundance of trainability. Instead of focusing on achieving an unrealistic level of polish in your LW projects, identify talking points around your projects and frame the project in terms of learning: what problems did you face while building this project? How did you work through these problems? What did you learn along the way? Put these talking points in your portfolio. This emphasises your ability to learn, and helps to contextualise the projects for potential employers.

Another thing I did is what I call the Redux option : identify 2-3 things that you didn’t get to finish for Demo Day, and complete them. These should be small tasks – fixing a known bug, making the app responsive, making it a simple progressive web app, adding error handling for a feature that only has its happy path completed. Then, if this is not a project you want to work on for the long term, stop here, accept that your early work is not going to be perfect, and move on.

You can see examples of how I wrote up my talking points and presented the Redux versions of my projects here:

What additional skills should I pick up?

I’m hesitant to answer this because it might fuel someone else’s imposter syndrome, but at the same time there are real gaps between what Le Wagon teaches and skills you really do need to know.

The first thing on the list is tests. The Le Wagon experience depends extensively on tests, at least until week 6 (for full-time) / week 15 or so (for part-time). By now you’re intimately familiar with how to interpret a failing rspec test. But can you write one?

If the answer is no, this is the first thing you should learn. There are two reasons for this:

  1. Everybody should be writing tests!
  2. Knowing how to write tests makes you more attractive to companies that care about testing, which are also companies that care about building good software, i.e. the companies you want to work for.

The second thing on the list of things to learn is JavaScript. Yes, you learnt five days’ worth of ES6, and then avoided it as much as possible during the projects because you didn’t understand any of it. I know. I understand the reason Le Wagon’s curriculum is built the way it is, and I don’t think I would take out something else to put in more JavaScript.

However, the JS covered in the bootcamp is grossly insufficient, especially if you want to pursue front-end or even generalist full-stack development. At a minimum, I would urge you to watch Codesmith’s videos on Promises, Async and the Event Loop. You must understand how frontend API calls really work, instead of copying the fetch(url).then(response => response.json).then(data => { // do stuff }) magic incantation from Kitt every time you need it. Better still, go through Codesmith’s CSX course, which mimics the style and content of what you learn in Le Wagon for Ruby, except this time in ES6.

From here, there are not as many hard-and-fast rules. I think many Wagoners would say to learn React , and I definitely think React is a good choice – but I actually don’t know if I consider it strictly necessary unless you definitely want to do frontend work. In my opinion, React is useful to learn because it gives you a mental model that is very different from Rails, and that you can then use as the starting point for learning other things. The course I typically recommend is Brian Holt’s Complete Intro to React v6 (if v7 is out by the time you read this, search for that instead).

Two more things I would add to this list are a high-level understanding of infrastructure and networking. You don’t need to understand the details at this stage, but you should understand how exactly a web application lives on a computer somewhere and how users somehow get that application delivered to their web browser.

Infrastructure and networking aren’t necessarily something that will come up at the job interview stage for a junior developer, but it makes your life as a developer vastly easier if you have a mental model and a vocabulary for talking about infrastructure and networking to your colleagues once you’re on the job.

How do I keep learning when I can’t raise any more tickets?

This is not an original visualisation:

Image: Three concentric circles. The innermost circle is labelled "Comfort", the middle circle is labelled "Learning", and the outermost circle is labelled "Panic". Three concentric circles. The innermost circle is labelled “Comfort”, the middle circle is labelled “Learning”, and the outermost circle is labelled “Panic”.

You’ve developed some familiarity with Rails, and a mental model of what software development looks like. That’s your comfort zone.

Outside of your comfort zone, there’s a zone of proximal development, or a learning zone. This is where you can learn something new if you can relate it to something you already know: you can read a tutorial and learn by analogy based on what you already know, or you can watch a video and follow along reasonably well. You want to stay in this zone as much as you can.

The great beyond is the panic zone : when you’re confronted with something so new, you don’t have a frame of reference at all. You want to stay out of this zone: spending too much time here makes you feel dumb and demoralised.

Over time, things that were formerly in your learning zone become your comfort zone, and the comfort zone circle expands. Things that were formerly in your panic zone start to come into your learning zone.

If you find yourself panicking when faced with something you don’t understand, take a step back, and see if there’s a way you can connect it with something you know, and build up to it that way. If there isn’t, there’s no shame in accepting that you’re not ready for it yet, and finding something else that does fall within your learning zone. Eventually, you’ll learn something that allows you to bridge that gap.

What should I consider when deciding on my first software development job?

My answer is going to be heavily biased by my experiences at Thoughtworks, which has been an excellent place for me to start my software development career. You should definitely talk to other alumni and other software developers, who will probably give you a variety of responses that you can consider in your decision-making.

In my opinion, the first thing you should look for is availability of mentorship : specifically, what support do junior developers get to develop their technical skills?

This can take many forms: a company may highlight its L&D budget, or give you access to many learning resources on Udemy, O’Reilly, or whatever its learning platform of choice is. The thing I would ask about, though, is what kind of technical feedback you can expect : do senior developers actively mentor juniors and give feedback on their code? If you’re unsure about something, can you get an extra set of eyes on it to verify you’re on the right track, or will you be left to your own devices? Feedback is critical to learning, and without feedback, it will be challenging to gain confidence in your skills as a developer.

The next thing I would look for is the quality of software engineering practices in the team or in the organisation:

  • Does the team write tests? A team that is committed to maintaining high test coverage of their codebase is generally a team that cares about software quality, and is likely to be a team you can learn a lot from.
  • How integrated is QA into the development process? Ideally, QA should be fully involved in the development process from start to finish: from gathering requirements, to defining a testing strategy, to performing QA after each user story is “dev done”. The larger the chunk of work that QA is expected to work on at a time (e.g. if QA is performed on an entire sprint’s worth of work at a time), the slower the feedback loop will be for you as a developer, and the harder it will be for you to learn from your mistakes.
  • How is code review conducted? Different organisations have different opinions about this, but the critical component is, once again, fast feedback: the sooner you get feedback on how to improve your code, the faster you learn and improve as a developer.
  • How frequently does/can the team deploy code? In my opinion, this is less of a direct factor that affects your job experience, and more of a proxy indicator. A team that is capable of deploying frequently is a team that maintains a high level of software quality. Just like with tests, that suggests a team that you can learn a lot from.

On the last point: note that the frequency of code deployment is often a business decision, not an engineering decision. An engineering team may be capable of deploying often, but the decision to deploy a new release involves a lot of other teams as well (product, marketing, CX, etc.), so the actual deployment frequency doesn’t matter as much. What matters is whether the team can deploy at short notice if they have to.

Other things to consider that will impact your job satisfaction, but that don’t necessarily reflect a company’s engineering quality, are:

  • How much pair programming is typical?
  • How large is the organisation, and how large is each team?
  • Is communication typically synchronous or asynchronous?
  • Is work conducted in-person or remotely?

I think these axes are mostly about preferences, and the right choice is whichever makes you happier. At the same time, it’s worth thinking about what is negotiable and what is not, and staying open to new experiences: I thought I would dislike how much pair programming happens at Thoughtworks, but over time I’ve come to really appreciate how much I learn through pairing.

What about the tech stack?

I personally don’t think the tech stack matters that much in your first software development role. You can definitely lean towards companies that use Rails or Ruby as a core part of their stack, and you should probably stay away from overly esoteric tech stacks for a first job. That’s about it.

Restricting your job search to only Rails is unnecessary: most of your Rails backend knowledge is readily transferable to Django, Express, Spring Boot, etc as long as you put in the work. Most employers who will consider a bootcamp candidate will be aware of this and give you some time to learn the stack. The cost of learning a specific stack is just not that high compared to the cost of hiring a weaker employee, so if you are an otherwise strong candidate for the job, the tech stack is unlikely to be the differentiating factor.

Erring in the other direction and being too open to esoteric stacks is a bigger risk, in my opinion. For a first job, you don’t want to become pigeonholed into a niche. Early in your software development career, you should aim to stay in the broad middle of the tech landscape, using more common frameworks and technologies.

To get a sense of how popular a given technology is, you can look at the Technology section of the annual Stack Overflow survey. If the main components of a company’s tech stack are not on the “Most Popular” list, or are very low on the “Most Popular” list and are at the bottom of the “Most Dreaded” list, I would probably not choose that company unless there were other very strong reasons to do so.

When will I learn enough?

I hope it doesn’t surprise you that the answer is “never”.

The world of web development is vast, and the wider world of software engineering even vaster still. Le Wagon teaches you two things:

  • a very basic skillset that everybody in web development has some version of: HTML, CSS, vanilla JS, and building an MVC backend
  • how to learn by doing, day in and day out

The first skill is your launchpad, but that second skill is the real fuel that will maintain the trajectory of your software development career. Every time you are asked to do something you have not done before, it will be bootcamp all over again: looking up resources, experimenting, debugging, and expanding your comfort zone to be a little bit bigger than it was when you woke up in the morning.

Applying to MCIT

I enrolled in the University of Pennsylvania’s online Master of Computer and Information Technology program in Spring 2021 and since then, a number of people have reached out to ask me about applying to MCIT. Since most potential applicants have the same questions and I find myself repeating the same answers, I figured I might as well put my thoughts all in one place.

If you are at all serious about applying to MCIT Online, you need to read the Penn Engineering Online FAQs. Please.

I understand if you have questions or doubts because you come from a different school system and need advice on recommenders, writing your personal statement, taking the GRE, or anything of the sort. I understand if you want to hear a student’s perspective on some of the official recommendations that the program makes. I do my best to be helpful. But if your question can be answered by a quick Google and a little bit of research, I’m going to doubt whether you’ve considered the commitment you are about to make.

All opinions are my own, and do not reflect those of the MCIT program. I’m just a student who applied, got admitted, and enrolled. I don’t have a privileged view inside the admissions committee.

What qualities does MCIT look for?

Here is the official answer from MCIT program director Dr. Tom Farmer: 5 Tips to Strengthen Your MCIT Online Application from the Program Director.

  1. Demonstrate quantitative ability.
  2. Make the most of your personal statement.
  3. Showcase your experience with online learning.
  4. Choose three solid recommenders if possible.
  5. Consider taking a CS course or two.

Let’s go over this one point at a time.

Evidence of quantitative ability

Your application should have either a previous degree in maths, physics, engineering, or with a similarly heavy quantitative component, or a high quant score on the GRE. MCIT Online does not publish GRE statistics, but the on-campus MCIT program does.

For the on-campus Fall 2021 admissions cycle, the average (median? I guess) GRE of admitted students was 162 Verbal, 168 Quantitative, 4.3 Analytical Writing. The number that really matters is the Quant score: 168Q out of a maximum score of 170 is around 92nd percentile. That’s higher than previous years, but honestly not by much: the average Quant score of admitted hovered between 165 and 167 between 2013 and 2020.

See: Do I need to take the GRE?

Compelling personal statement

Tell a story of how a program like MCIT Online fits into your personal and professional goals. Perhaps you’re a career changer moving into software development or data science. Perhaps you work in a tech-adjacent role (product manager, business analyst, legal or public policy in a tech company) and want to understand the tech domain better. Perhaps you are already working in software development or data science and want to fill in knowledge gaps. Heck, maybe you just want to learn.

If you consider other professional graduate programs such as the MBA, MD, JD, etc. there’s often an implicit requirement that you need to have a certain amount of a certain type of work and/or internship experience. That’s not the case here: MCIT students really do come from all sorts of academic and professional backgrounds. The important thing is that you can weave a story of how studying computer science will fit into your professional life.

Proven ability to learn academically in an online setting

If you’ve done any university-level courses on Coursera, edX, or any online learning platform, you should list them.

If you have not taken any such classes online, I would suggest doing a course like Harvard’s CS50x and getting the verified certificate. It’s useful both for admissions, and for assessing how you will do in an online learning environment.

Obviously the world has changed since MCIT Online was launched, and many students have had to learn online out of necessity. Even so, I think it’s useful to do online asynchronous courses on top of any online classes you may have had as a result of Covid. Instructional design and expectations for a class that’s designed to be online, as opposed to one that had to be moved online at short notice, are going to be different. Online learning favours learners who are independent, self-motivated and who know when and how to proactively ask for help. I think it’s instructive to figure out for yourself if you’re the kind of learner who suffers from the online environment or who benefits from it.

There’s also a question of whether online learning platforms such as Codecademy, Educative or Udemy count. My honest answer is that I don’t know how the admissions committee views them in comparison to Coursera and edX. However, my own experience has been that the MCIT Online learning experience is closest to Coursera and edX courses, in the sense that MCIT Online classes are academically-oriented and tend to be lecture- and assignment-driven. You should make sure that this is a style of online learning that you enjoy.

Strong recommendations

Choose your recommendations carefully. One of the worst things that can happen to your application is a lukewarm recommendation.

I suggest reading the Letters of Recommendation FAQ thoroughly to get a feel for what the program is looking for in the letters of recommendation.

Who should I ask for letters of recommendation?

You must choose at least two recommenders. Three is ideal but not always possible (I had two). In my opinion, the most important reason for nominating three recommenders is to make sure you don’t get any last-minute surprises from a letter-writer who doesn’t submit their recommendation before the deadline. Another good reason to find three letter-writers if you can is that they can address different dimensions of you as an individual: perhaps one person can talk about you in an academic setting, another in a professional setting, and the third in the context of a community or volunteer project.

What I typically suggest is that at least one recommender should be academic, ideally a professor who knows your work well. This could be a thesis advisor, a professor you’ve TA’ed or done research for, a professor whose class included a substantial assignment, or a professor with whom you’ve taken multiple classes. The academic recommender should be someone who can talk about your qualities as a student: do you work hard to understand the material, do you ask good questions in class, do you produce good insight in your work, etc. Ultimately, MCIT is an academic program, and an academic reference will likely be most reflective of how well you will do in an academic setting.

The second and third recommenders can also be academic, or they can be individuals who know you from work or other professional context (e.g. your manager, your web development bootcamp instructor, a leader at a program you volunteer with). Ideally this person would talk about your ability to succeed professionally: do you deliver when needed, can you manage your time and your responsibilities, how do they see you growing as a technology professional?

There are a few resources that I think are useful for navigating the letters of recommendation:

  • Computer science professor Kyle Burke has an excellent FAQ on asking him for recommendations. Of course, your mileage will vary if your professor is not Kyle Burke, but it’s still a good overview of what kind of information to provide to your recommenders.
  • If one of your recommenders is happy to help you out but is not sure how to go about writing your letter, take a look at UC Berkeley’s advice to GSIs on writing letters of recommendation. It’s targeted at graduate students who may be writing letters of recommendation for the first time, but most of the advice (particularly “Paragraph by Paragraph” and “Dos and Don’ts”) is easily adapted to other contexts such as work or community service.
  • UC Berkeley also has Guidelines for Writing Letters of Recommendation broken down by the type of graduate program (MCIT would fall under “Academic Graduate School”), but I find the advice given here to be a bit more generic and less explicit about what makes an effective recommendation letter.

Consider taking a CS course or two

Penn Engineering has a number of online courses. Some of these professors also teach MCIT courses: in particular, the Introduction to Programming with Python and Java Specialization is a lighter version of CIT 591 Introduction to Software Development.

My own preference is for Harvard’s CS50x, which I think is an excellent introductory course that will both give you technical skills and a broad understanding of computer science. It’s designed to be a first undergraduate course in computer science, so it needs to cater both to students who may never take another CS course in their life, and at the same time adequately prepare students who intend to major in CS. Somehow, it succeeds.

The other set of courses I took was the Introduction to Discrete Mathematics for Computer Science specialisation on Coursera. I felt it was important to have some maths in my application, and I found the course enjoyable, if lacking the dynamism of David Malan’s stagecraft.

The wonder of MOOCs is that there are so many amazing courses available for free or at very reasonable cost. Other MOOCs that I’ve heard great things about are:

These are a little more advanced, though, so treat them as the suggestions that they are, and not as a checklist of courses you have to do. The important thing here is simply to demonstrate that you’ve sought out some CS learning on your own.

How long should I spend on my application?

According to my Notion page history, I created my MCIT “Essay” page on April 6, 2020. The admission deadline for the Spring 2021 semester was July 31, 2020, so the whole process took me about four months.

The parts of the application that have the longest lead times are:

  • GRE preparation (4-12 weeks, depending on your preparation and test-taking ability)
  • Letters of recommendation (1-2 months advance notice for your recommenders)

If you’re only getting started on the application with just two months before the deadline, I would consider that to be too tight. It’s doable, but it increases the likelihood that you’ll submit something less than representative of your strength as a candidate.

Another wrinkle in your planning is whether you foresee taking the GRE more than once. I discuss this further under How should I prepare for the GRE?

Do I need to take the GRE?

Yes.

If you need to ask, the answer is yes.

The official answer is this:

No, the GRE is optional. But there are a few scenarios in which we strongly recommend taking the GRE:

  • You have not taken any quantitative courses (such as math or physics).
  • You feel the grades that you received in your bachelor’s program do not represent your current abilities and are lower than you would like them to be.
  • You received your undergraduate degree 15 or more years ago.

The way I see it, there is virtually no situation in which it is beneficial to skip the GRE. The best that can be said is that for some people, skipping it won’t actively hurt your application. That’s the class of applicants who already have strong evidence of quantitative ability on their transcript or résumé.

If you graduated from a mathematics / engineering / physics program with a 3.8 GPA less than 15 years ago, sure, you don’t have to do the GRE. If you build quantitative financial models for a living, you don’t have to do the GRE. If you majored in film but have an A+ in Real Analysis on your transcript, you don’t have to do the GRE. (I was a film major, I can make jokes about film majors and maths.) You already know if you fall into this category.

The truth is, if you do not have a quantitative background, the ability to use the GRE to prove your quantatitive bona fides is a godsend. Most schools want to see a college-level maths class on your transcript (Bath and OSU both do, for example), if not college-level CS classes. I have neither, and if you don’t either, the GRE is not optional for you.

Yes, standardised testing sucks. No, the GRE does not predict graduate school success. But let’s also be real here: in the MCIT program, there is at least one proctored exam per course. Each of these exams is about as long as the GRE, and requires the same kind of exam skills as the GRE. If preparing for a proctored, standardised exam is a deal-breaker for you, you may not enjoy graduate school very much.

How should I prepare for the GRE?

I spent about a month’s worth of evenings and weekends preparing for the GRE and took it once, coming out with a score I was more than happy with.

What I did was to buy Manhattan Prep’s 5lb. Book of GRE Practice Problems, take a diagnostic test, identify my weakest areas, and spend most of my time practising them. I probably spent 80% of my GRE preparation on statistics questions. My quantitative scores on practice tests at the end of my preparation were the same as my actual exam quant score.

You may need more time to prepare: most GRE preparation sites suggest 4 weeks as a minimum, and an upper bound of 12-20 weeks. Because the only sub-score that really matters is the Quantitative score, you don’t need 20 weeks cramming GRE vocabulary, but you want to give yourself enough time to be confident under exam conditions.

To retake or not to retake

Sometimes things go wrong. Your score isn’t as good as you want it to be, you fell ill on the day of the exam, you blanked out under exam conditions, whatever.

Retaking the GRE is pretty common, and there’s no harm in planning for it. You don’t have to take the GRE multiple times, but it’s nice to have that option if you bomb the first time. After each GRE test, you must wait 21 days before you can take the test again. That means that you should aim to take the GRE at least three weeks before the application deadline, in order to give yourself enough time for a re-take.

What other schools did you apply to?

I had four programs in mind: Penn’s MCIT program, University of Bath, Oregon State’s post-baccalaureate, National University of Singapore, and I ended up ranking them in that order. My criteria for choosing programs was simple:

  1. Must accept students without any prior CS background (this rules out Georgia Tech’s OMSCS, among others)
  2. Must be doable part-time
  3. Must not be more expensive than NUS’s MComp program at S$60,000+
  4. Must be doable completely remotely (NUS is the exception, as I can commute to it)
  5. Reasonable reputation as a research university (lots of schools offer online and/or part-time CS programs now, but I wanted to prioritise schools with a stronger reputation first)

The complication is that my undergraduate transcript does not have a single maths class. No Calc I, no “Math for Non-Majors”, no “Great Ideas in Mathematics”. This makes meeting the pre-requisites for many programs a bit of a challenge.

I could apply to MCIT without doing any prerequisite classes, as long as I had a good GRE score. To apply to Bath or OSU, I would have needed to take a college-level Calculus class first, and to apply to NUS, I would have needed to take three certificate CS classes first. The decision came down to this:

  • Ease of application (from best to worst): Penn, Bath/OSU, NUS
  • Cost (from cheapest to most expensive): Bath, Penn, OSU, NUS
  • Study mode (from best to worst): Penn/Bath/OSU, then NUS (in-person is actually a negative here, because of the commute time and the inflexible schedule)
  • Strength of CS program (from best to worst): NUS, Penn, Bath/OSU

That’s not to say that Bath or OSU have bad CS programs. Rather, I culled several programs that also met my criteria, but that had weaker reputations than these four. NUS and Penn are simply on a different level from Bath and OSU when it comes to computer science.

My plan was to apply one at a time, so if I had been rejected from Penn, I would have taken a Calc class and applied to Bath, then OSU, and then finally signed up for the certificate classes at NUS and pursued the CS program there. Since I got into Penn, I didn’t end up applying anywhere else.

Other questions

I hope this is useful to anybody planning to apply to MCIT Online. If you have any other questions, though, feel free to contact me. If I think your question is potentially relevant to other applicants, I may add it here.

Thinking About Test Frameworks

Yesterday, I wrote a post about why and how to write tests, inspired by my most recent project. The gist of it is that the key purpose of automated tests is to provide fast feedback. I ended the previous post with a remark that the need for fast feedback is the reason test frameworks exist.

You’ve used a test framework, but how much do you understand what it’s doing? I know that before this social impact project, I had no clue. On this project, we were working in a game engine that, to the best of our knowledge, does not have a testing framework in its ecosystem. One of the things that our team explored over the course of the project, and which we eventually did, was to build a test framework.

Evolving towards a test framework

(The idea behind this progression is shamelessly adapted from the first part of Kent C. Dodd’s Assert.js workshop. If not for this video, I don’t think I would have dared to contemplate writing a test framework at all, but he demystified the process and made it so accessible. Thanks Kent!)

Beginning with my definition of a test (not Kent’s):

A test is a bit of code that, when run, tells you something about whether some other part of your code (the subject under test) is working as intended.

In my previous post, I laid out the key pieces of information that the developer needs to be able to identify quickly, in order to understand what their tests are telling them:

  • How many tests ran?
  • How many tests passed?
  • How many tests failed?
  • Which tests failed?
    • What was the expected result?
    • What is the actual result?
    • Which line of code is the immediate cause of test failure?
      • If in doubt: What is the stack trace?

Let’s now write a single test, the simplest test that fulfills these conditions.

Writing one test

Let’s say I need to write a function that converts inches to millimeters. For the rest of this post, I’ll conveniently ignore the question of “how many tests ran?”, and concern ourselves with the other pieces of information. If we want to determine if the subject under test is working as intended, we first need to define what “working as intended” means. In this case, let’s say we want to convert 10 inches to millimeters:

const testConvertInchesToMillimeters = () => {
  const expected = 25.4;
  const actual = convertInchesToMillimeters(10);

  if (actual === expected) {
    console.log("Passed");
  } else {
    console.log("Expected: ", expected);
    console.log("Actual: ", actual);
  }
};

This test prints Passed if it passes. If it fails, it prints out the expected result and the actual result.

“Wait a second,” you might be thinking, “you said there should be a third piece of information given for test failures. Which line of code caused the test failure?”

I’ll get there. Now, let’s write the code to pass the test:

const convertInchesToMillimeters = (inches) => inches * 2.54;

You’ll notice that the subject under test is just one line. If the test fails, there’s only one line to look at!

In all seriousness, there’s an important observation to be made here. If an exception is thrown while running the code under test and that exception is not caught, Jest will point it out to you, as will most other testing frameworks. In that case, you know exactly where the immediate failure is inside the code under test.

However, uncaught exceptions are the exception (ba-dum-tss). They’re the one time the testing framework can tell you, from within the subject under test, where the test failed. That’s not what happens the rest of the time, because otherwise the test and the subject under test are insulated from each other. The test does not know about its subject’s implementation details (or at least, it shouldn’t).

So what is that “which line of code is the immediate cause of test failure” stuff? I’ll admit, I may have tried to be too pithy with that one-liner. If an exception is thrown and not caught, your test output should identify where that line of code is. On the other hand, if a test result does not meet its expectation, your test output should tell you where in the test that failed expectation is.

As a case in point, I wrote a test in Jest and made it fail. Look at the information Jest gives me:

Image: Output from a failing test in Jest Output from a failing test in Jest

Jest shows the expected result, the received (actual) result, and the line in the test defining the expected behaviour that didn’t happen. In other words, Jest points you to the line of code in the test that defines this as a failed test.

That is something that our test still lacks. Of course, there’s only one test and that test has only one “assertion”, so let’s add more.

Adding a second test

Now, I’ll add a function that converts kilograms to pounds. Again, I write the test first:

const testConvertKilogramsToPounds = () => {
  const expected = 22.2;
  const actual = convertKilogramsToPounds(10);

  if (actual === expected) {
    console.log("Passed");
  } else {
    console.log("Expected: ", expected);
    console.log("Actual: ", actual);
  }
};

const convertKilogramsToPounds = (kilograms) => kilograms * 2;

(If you spot the error… shh, keep it quiet!)

Great. I make a test file, and in it, I call:

testConvertInchesToMillimeters();
testConvertKilogramsToPounds();

Rather irresponsibly, I commit this without running my tests (“it’s just a toy codebase”), push it, and go off to lunch.

Now my teammate pulls the repo, runs the test file, and sees:

Passed
Expected: 22.2
Actual: 20

It’s only two tests, but we’ve started to run into problems. From the test output alone, my teammate doesn’t know which test passed, which test failed, and where to find the expectation that failed. Of course, with two tests, you might be tempted to dismiss this as a contrived and trivial problem, but it doesn’t take much imagination to see that at just 10, 15, 20 tests, the feedback loop will already start to slow down. There’s not enough information to quickly identify and fix failing tests.

This form of automated testing doesn’t scale well.

Minimum Viable Test Framework: Which Test Failed?

There are two problems with the test output to be solved here. At a glance, we are unable to identify:

  1. Which test failed?
  2. Where did it fail?

Let’s look at our two tests:

const testConvertInchesToMillimeters = () => {
  const expected = 25.4;
  const actual = convertInchesToMillimeters(10);

  if (actual === expected) {
    console.log("Passed");
  } else {
    console.log("Expected: ", expected);
    console.log("Actual: ", actual);
  }
};

const testConvertKilogramsToPounds = () => {
  const expected = 22.2;
  const actual = convertKilogramsToPounds(10);

  if (actual === expected) {
    console.log("Passed");
  } else {
    console.log("Expected: ", expected);
    console.log("Actual: ", actual);
  }
};

We might be tempted to fix this problem by writing a console.log("testNameHere") at the top of each test function. Don’t do it – that way lies lots of copy and paste and madness.

Instead, let’s think of the test from the object-oriented perspective. What properties does a Test object need to have? It needs to have a name or a description, so that we can quickly identify which tests passed or failed. It needs to have test code that we can run. There are other things we can add to our Test object, but this is the absolute minumum.

Having conceptualised the test as an object, I’m now going to do a 180 and write it as a function instead:

const test = (name, testCode) => {
  console.log(name);
  testCode();
};

Now we use our new test() function to invoke our tests:

test("convert inches to millimeters", () => {
  const expected = 25.4;
  const actual = convertInchesToMillimeters(10);

  if (actual === expected) {
    console.log("Passed");
  } else {
    console.log("Expected: ", expected);
    console.log("Actual: ", actual);
  }
});

If this is starting to look familiar, it should. The function signature for Jest’s test() function is test(name, fn, timeout), with timeout being optional. We’re slowly evolving our way towards a test framework. We’re not there yet, though: all our test() function does is print the name of the test before running it. The test() function doesn’t know anything about whether testCode() passes or fails – it doesn’t know anything about testCode(). How can the testCode() callback tell the test() function whether there was a test failure?

By throwing an exception!

Here we can take the opportunity to write an assertEquals() function and also keep our code DRY. We’ll start by simply moving the comparison between the expected and actual values into its own function:

const assertEquals = (expected, actual) => {
  if (actual === expected) {
    console.log("Passed");
  } else {
    console.log("Expected: ", expected);
    console.log("Actual: ", actual);
  }
};

test("convert inches to millimeters", () => {
  const expected = 25.4;
  const actual = convertInchesToMillimeters(10);

  assertEquals(expected, actual);
});

Next, we need to make assertEquals() throw an error if the expected and actual values are not equal:

const assertEquals = (expected, actual) => {
  if (actual !== expected) {
    throw new Error(`Expected: ${expected}, actual: ${actual}\n`);
  }
};

Then, the test() function needs to catch any error thrown from inside testCode():

const test = (name, testCode) => {
  try {
    testCode();
    console.log(`PASSED: ${name}`);
  } catch (error) {
    console.log(`FAILED: ${name}`);
    console.log(error.message);
  }
};

I’ve taken the opportunity to move the “Passed” message from the assertion block to the test() function. The assertEquals() function has no idea which test it’s running inside of, and it shouldn’t be assertEquals()’s job to decide if a test has passed or not. Besides, there may be multiple assertEquals() inside one test().

If the testCode() callback runs without throwing any exceptions, it passes. Otherwise, it fails. At this point, running our tests will produce this test output:

PASSED: convert inches to millimeters
FAILED: convert kilograms to pounds
Expected: 22.2, actual: 25.4

Now we know exactly which test failed. The next step is to identify where it failed.

Minimum Viable Test Framework: Where Did The Test Fail?

Our little test framework looks like this now:

const assertEquals = (expected, actual) => {
  if (actual !== expected) {
    throw new Error(`Expected: ${expected}, actual: ${actual}\n`);
  }
};

const test = (name, testCode) => {
  try {
    testCode();
    console.log(`PASSED: ${name}`);
  } catch (error) {
    console.log(`FAILED: ${name}`);
    console.log(error.message);
  }
};

It turns out that the task of identifying which assertion caused the test to fail is much easier than putting a name on the failed test. We can simply print error.stack instead of error.message, and get the call stack at the point the error was thrown:

const test = (name, testCode) => {
  try {
    testCode();
    console.log(`PASSED: ${name}`);
  } catch (error) {
    console.log(`FAILED: ${name}`);
    console.log(error.stack);
  }
};

Now our test output looks like this:

PASSED: convert inches to millimeters
FAILED: convert kilograms to pounds
Error: Expected: 22.2, actual: 20

    at assertEquals (~/foo.js:3:11)
    at ~/foo.js:31:3

This is all the information we need. It is arguably too much information. I’ve already manually truncated the stack trace, but we still have two line numbers – 3 and 31 – and we only care about line 31 (line 3 is the throw new Error() line from inside the assertEquals() function).

Still, the refinement of the minimum viable test framework is an exercise that I will leave to the reader.

Minimum Viable Test Framework: Next Steps

This is a good time to revisit the information that we care about when getting fast feedback from tests:

  • How many tests ran?
  • How many tests passed?
  • How many tests failed?
  • Which tests failed?
    • What was the expected result?
    • What is the actual result?
    • Which line of code is the immediate cause of test failure?
      • If in doubt: What is the stack trace?

We’ve made a lot of progress here in identifying which tests failed and how. This test framework still doesn’t tell us how many tests ran, passed or failed, but I think for most developers, that’s a challenge that’s approachable enough that I won’t cover it here.

If you wanted to extend this test framework, there are a lot of things you could do next. You might want to distinguish AssertionErrors from other types of runtime errors. You might want to time how long the tests take to run. You might want to have the ability to group tests into test suites, and to run only specified test suites. You might want to create lifecycle methods that run at the start and end of each test or test suite. You might want implement mocking or stubbing.

Regardless, I think you’ll agree that two of the biggest workhorses of any test framework are the test() and assertEquals() functions, or their equivalents in the framework of your choice. Hopefully, this post has given you some insight into the inner workings of test frameworks, and a deeper appreciation for how the best test frameworks seamlessly tighten the feedback loop in software development.