It is the first time that I try to write an article in English.
In the past, I didn’t write test code. Just thinking QA is responsible for testing.
As a developer, I don’t need to care about tests.
Although I know tests are essential, I can’t be aware of their importance.
After I joined my current team, it is required for developers to write tests.
So I have to write tests code.
After 8 months, I realize that tests are critical. I need to enforce my ability of testing.
therefore, I recently read a book
Vladimir Khorikov - Unit Testing Principles Practices and Patterns.
Learned a lot of unit test and integration best practice and code design.
So I want to share some knowledge with you.
There are two kinds of tests.
Do we need a 100% test coverage?
No. We don’t.
For trivial code, we can ignore that. Because they aren’t worth it.
We should focus on our business logic.
If a metric shows that there’s too little coverage in your code base—say, only 10%—
that’s a good indication that you are not testing enough.
But the reverse isn’t true:
even 100% coverage isn’t a guarantee that you have a good-quality test suite. A test
suite that provides high coverage can still be of poor quality.
The goal of unit test is to enable sustainable project growth.
Just as all tests are not created equal, not all parts of your code base are worth the
same attention in terms of unit testing.
Unit test should test a unit of behavior rather than code.
The four pillars of a good unit test
The more features you develop, the more chances there are that you’ll break one of those features with a new release.
Sometimes We don’t awake that the new release will break one existing feature. Even with QA regression test, it also happens many times.
If we have a good automation test, can reduce these situations.
So good tests should protect against regressions.
To maximize the metric of protection against regressions, the test needs to aim at exercising as much code as possible.
Resistance to refactoring — the degree to which a test can sustain a refactoring of the underlying application code without turning red (failing).
This attribute can give us confidence to refactor.
How can we do?
We shouldn’t test the details of code. We should test the observable behavior.
Aim at the end result instead of implementation details
There is a example:
# we shouldn't care what's detail that we get from the data test "get user by id" do assert "select * from users where id = 1" == User.get(1).to_sql end # we just need to verify the data test "get user by id" do assert User.get(1).name == "Steven" assert User.get(1).id == 1 end
Fast feedback brings a excellent experience.
Picture the situation, we run a test, it takes 1 minute to show you result.
I can’t stand it.
Our project has hundreds of thousands of code. Run slowly and waste my time to wait for result.
How to run faster?
With less communications of out of process.
Async and parallel execution.
Using plain English as test titles.
Simple phrases in plain English do a much better job: they are more expressive
and don’t box you in a rigid naming structure. With simple phrases, you can describe
the system behavior in a way that’s meaningful to a customer or a domain expert.
how to write code to easy test
Split business logic and out of process communications (side effects).
def check do if User.admin? do :error else :ok end end def check_with_side_effect do if User.admin? do # side effect AuditLog.record() :error else :ok end end
With side effects, it is hard to compose code and hard to test.
Pure function is easiest to test.
Using integration tests to verify the behavior of the system as a whole.
Integration test verifies database, third-party api, mq and so on.
All out-of-process dependencies fall into two categories:
dependencies are only accessible through your application; interactions with
them aren’t visible to the external world. A typical example is a database. External systems normally don’t access your database directly; they do that through
the API your application provides.
Interactions with such dependencies are observable externally. Examples include
an SMTP server and a message bus: both produce side effects visible to other
For those out-of-process dependencies, we should mock unmanaged dependencies.
It simply summary Unit Testing.
Talk is cheap, we should write more code.