開源日報 每天推薦一個 GitHub 優質開源項目和一篇精選英文科技或編程文章原文,堅持閱讀《開源日報》,保持每日學習的好習慣。
今日推薦開源項目:《YTB DevYouTubeList》
今日推薦英文原文:《Stop Testing Code You』ve Already Written》

今日推薦開源項目:《YTB DevYouTubeList》傳送門:GitHub鏈接
推薦理由:YTB 上你能看到幾乎所有想看的玩意,總會有人去做它們的。這個項目收集了 YouTube 上與軟體開發有關的頻道,包括開發教程或者是編程直播之類的,下次可以考慮拿這個來打發時間下飯之類的(我可沒試過拿這些下飯),當然了大部分的資源都是英語,至於你要問有沒有非英語……只有俄語可以嗎?

今日推薦英文原文:《Stop Testing Code You』ve Already Written》作者:Jamie Morris
原文鏈接:https://medium.com/@maloric/stop-testing-code-youve-already-written-750773b9bb5b
推薦理由:測試也是一步一個腳印來的,這篇文章介紹了測試驅動開發的優點

Stop Testing Code You』ve Already Written

Test One Step at a Time, Not One Mountain at a Time

It』s that time of year where we start to make promises that we』ll probably just forget by February. New Years Resolutions are usually well meaning but we are usually trying to solve the same old problems in ways we』ve tried before. Eat healthy. Go to the gym more. Learn a new language. Write more unit tests.

Every time I talk to a team that』s struggling and ask them what they』ll do different next time, I always get the same answer: we』ll write more tests. Sometimes there are other answers thrown in there, but testing is the thing we all think we should be doing better.

The same teams have made the same resolution time and time again, so I wonder why it is that teams like this aren』t writing enough tests. 「Enough tests」 may be a poor choice of words — the quality of your tests is just as important as the quantity. But either way: based on my own anecdotal findings, most of us think we should be writing more / better tests.

So why don』t we? When I think about it logically, there is an obvious conclusion (obvious to someone who is fanatical about TDD). It』s hard to write tests after the fact.

You see, test driven development forces you to write code in small steps that are easily testable. I might write one test, then write another few lines, followed by another test, followed by another few lines.

But most teams who write their tests after they write their features don』t work with this incremental approach. You might write 200 lines of code before you start testing it. That leaves you with a working feature, little incentive to test it, and a lot of refactoring to do before you finish.

Why refactoring? Well, those 200 lines may not be easy to test. In order to make them testable, you』ll need to refactor a little. Refactoring can easily break your code, but at least you have a safety net of tests to… oh wait. See the problem?

So write your tests first. Not only will it give you a safety net from the start, but it will also mean one less refactor. Or more accurately, it will mean that refactor will be broken down into smaller chunks that are easier to manage.

What is TDD and Why Should You Care About It?

Test Driven Development is the act of writing your tests before you write the code you are testing. Want to write a feature that allows you to add a product to your basket? Start by writing a tests that checks if the feature works. The test will fail, because the feature hasn』t been written yet. Then you can write the feature to make the test pass. Simple, right?

How Do I Test Something I Haven』t Written Yet?

If you want to test 200 lines of code you haven』t written yet, you』ll have a tough time doing it. It』s hard enough to think about the next 200 lines when you are writing them, never mind when you are trying to figure out how to test them. The idea isn』t to write a whole feature』s worth of tests before you get started on the implementation. That would require incredible foresight to know exactly how you will implement the feature, without the benefit of any kind of feedback.

Instead, you test just enough to cause a test failure. This keeps you focused on the problem directly in front of you rather than the final destination. As always, I find examples help in this situation:

Let』s say you are writing a basket on an eCommerce website. At present adding a product doesn』t check the stock levels before adding an item to the basket. You』ve been asked to make sure products can only be added to the basket when they are in stock.

There are a whole bunch of tests you might think of at the outset. Here are some of the ones I thought of:
  1. Given that Product A is in stock (quantity > 0)
    When I add Product A to my basket
    Then there should be 1 x Product A in my basket
  2. Given that Product A is in stock (quantity = 0)
    When I add Product A to my basket
    Then there should be an error telling me the item is not in stock
  3. Given that Product A is in stock (quantity = 1)
    When I add 2 x Product A to my basket
    Then there should be an error telling me there is not sufficient stock
Assuming that there are already tests for adding an item to your basket (without checking stock), then test 1 should be easy to write, so we should start there. Write the other test cases down and just focus on the problem in front of you, which is 「how do we check stock levels?」

Your first test might only look like this:
it('should add an item to the basket when it is in stock', () => {
    let mockStockService = {};
    let basket = new Basket(mockStockService);
});
This isn』t much of a test — there aren』t even any assertions yet! But when considering how to check stock levels, I decided I would need to inject a service to do so. As soon as I try to instantiate the Basket class with this service, I get an error because the constructor does not expect any arguments. This is a failing test, even if it is only caused by a compilation failure rather than an assertion.

I don』t know exactly what methods will be on the service yet, but I can now update the Basket class to accept a StockService in its constructor. With that done, the test should 「pass」 (i.e. compile), at which point I can move onto my first assertion:
it('should add an item to the basket when it is in stock', () => {
    let mockStockService = {
        getStockLevel: jasmine.createSpy()
    };
    let basket = new Basket(mockStockService);
    basket.add(new Product(123));

    expect(mockStockService.getStockLevel).toHaveBeenCalledWith(123);
});
Again, I have wrote the minimum amount to make a test fail. I decided that I need a method called getStockLevel so I implemented a spy, but not a return value. I just want to get to the next assertion.

This doesn』t mean you』re only thinking about one problem at a time, but simply that the ones in sharpest focus are the ones closest to you right now. Don』t focus on a problem you don』t have to solve yet, because you will take brain power away from the ones you do. And by the time you have to solve that faraway problem, it may look a whole lot different.

This is one thing that I think people forget to explain about Test Driven Development. It』s not just about writing better tests, but it naturally breaks big problems down into bitesize pieces. As a result, testing can often feel much less daunting than it would if you had 200 lines of code to test. You』re generally just trying to find the minimum amount of test that will cause the test run to fail, followed by the minimum amount of code to make it pass.

Then you refactor, if needed. This time you have a safety net in the form of the test you just wrote. This process is often called Red, Green, Refactor.
  • Red is when you have a failing test.
  • Green is when the test is passing.
  • Refactor is when you look at how you made the test pass and decide there is a better way. Rather than saying 「we』ll fix that later」 you make an effort to fix it now, while it』s fresh in your mind.
Just remember that it』s all about keeping this loop as short as possible. You ought to be switching between test / implementation every few minutes at most if you can manage it. Doing so will break down the problem you』re solving into digestible chunks that won』t give your brain heartburn.

So stop writing tests for code you』ve written and start writing tests for code you haven』t written yet.
下載開源日報APP:https://openingsource.org/2579/
加入我們:https://openingsource.org/about/join/
關注我們:https://openingsource.org/about/love/