ITÊýÂë ¹ºÎï ÍøÖ· Í·Ìõ Èí¼þ ÈÕÀú ÔĶÁ ͼÊé¹Ý
TxTС˵ÔĶÁÆ÷
¡ýÓïÒôÔĶÁ,С˵ÏÂÔØ,¹ÅµäÎÄѧ¡ý
ͼƬÅúÁ¿ÏÂÔØÆ÷
¡ýÅúÁ¿ÏÂÔØͼƬ,ÃÀŮͼ¿â¡ý
ͼƬ×Ô¶¯²¥·ÅÆ÷
¡ýͼƬ×Ô¶¯²¥·ÅÆ÷¡ý
Ò»¼üÇå³ýÀ¬»ø
¡ýÇáÇáÒ»µã,Çå³ýϵͳÀ¬»ø¡ý
¿ª·¢: C++֪ʶ¿â Java֪ʶ¿â JavaScript Python PHP֪ʶ¿â È˹¤ÖÇÄÜ Çø¿éÁ´ ´óÊý¾Ý Òƶ¯¿ª·¢ ǶÈëʽ ¿ª·¢¹¤¾ß Êý¾Ý½á¹¹ÓëËã·¨ ¿ª·¢²âÊÔ ÓÎÏ·¿ª·¢ ÍøÂçЭÒé ϵͳÔËά
½Ì³Ì: HTML½Ì³Ì CSS½Ì³Ì JavaScript½Ì³Ì GoÓïÑÔ½Ì³Ì JQuery½Ì³Ì VUE½Ì³Ì VUE3½Ì³Ì Bootstrap½Ì³Ì SQLÊý¾Ý¿â½Ì³Ì CÓïÑÔ½Ì³Ì C++½Ì³Ì Java½Ì³Ì Python½Ì³Ì Python3½Ì³Ì C#½Ì³Ì
ÊýÂë: µçÄÔ ±Ê¼Ç±¾ ÏÔ¿¨ ÏÔʾÆ÷ ¹Ì̬ӲÅÌ Ó²ÅÌ ¶ú»ú ÊÖ»ú iphone vivo oppo СÃ× »ªÎª µ¥·´ ×°»ú ͼÀ­¶¡
 
   -> ¿ª·¢²âÊÔ -> Software Engineering at Google-III-parter-12-Unit Testing(µ¥Ôª²âÊÔ ) -> ÕýÎÄÔĶÁ

[¿ª·¢²âÊÔ]Software Engineering at Google-III-parter-12-Unit Testing(µ¥Ôª²âÊÔ )

²Î¿¼ https://github.com/daizhenhong/swe-at-google/blob/main/Part_III_Processes/total/Chapter-12-total.md

µÚ12ÕÂ


µ¥Ôª²âÊÔ Unit Testing

×÷Õß: Erik Kue€er


±à¼­: Tom Manshreck

origin

The previous chapter introduced two of the main axes along which Google classifies tests: size and scope. To recap, size refers to the resources consumed by a test and what it is allowed to do, and scope refers to how much code a test is intended to validate. Though Google has clear definitions for test size, scope tends to be a little fuzzier. We use the term unit test to refer to tests of relatively narrow scope, such as of a single class or method. Unit tests are usually small in size, but this isn¡¯t always the case.

After preventing bugs, the most important purpose of a test is to improve engineers¡¯ productivity. Compared to broader-scoped tests, unit tests have many properties that make them an excellent way to optimize productivity:

Ç°Ò»Õ½éÉÜÁ˹ȸè¶Ô²âÊÔ½øÐзÖÀàµÄÁ½¸öÖ÷ÒªÖá:´óСºÍ·¶Î§¡£¸ÅÀ¨Ò»ÏÂ,´óСָµÄÊDzâÊÔËùÏûºÄµÄ×ÊÔ´ÒÔ¼°ËüËùÔÊÐí×öµÄÊÂÇé,¶ø·¶Î§Ö¸µÄÊDzâÊÔÒªÑéÖ¤µÄ´úÂëÊýÁ¿¡£ËäÈ»¹È¸è¶Ô²âÊÔ´óСÓÐÃ÷È·µÄ¶¨Òå,µ«·¶Î§ÍùÍùÓеãÄ£ºý¡£ÎÒÃÇʹÓÃÊõÓïµ¥Ôª²âÊÔÀ´Ö¸´úÏà¶ÔÏÁÕ­·¶Î§µÄ²âÊÔ,±ÈÈçµ¥¸öÀà»ò·½·¨µÄ²âÊÔ¡£µ¥Ôª²âÊÔͨ³£¹æÄ£½ÏС,µ«Çé¿ö²¢·Ç×ÜÊÇÈç´Ë¡£

ÔÚ·ÀÖ¹bugÖ®ºó,²âÊÔ×îÖØÒªµÄÄ¿µÄÊÇÌá¸ß¹¤³ÌʦµÄÉú²úÁ¦¡£Ó뷶Χ¸ü¹ãµÄ²âÊÔÏà±È,µ¥Ôª²âÊÔÓÐÐí¶àÌØÐÔ,ÕâЩÌØÐÔʹÆä³ÉΪÓÅ»¯Éú²úÁ¦µÄ¾ø¼Ñ·½Ê½:

origin
  • They tend to be small according to Google¡¯s definitions of test size. Small tests are fast and deterministic, allowing developers to run them frequently as part of their workflow and get immediate feedback.
  • They tend to be easy to write at the same time as the code they¡¯re testing, allow©\ ing engineers to focus their tests on the code they¡¯re working on without having to set up and understand a larger system.
  • They promote high levels of test coverage because they are quick and easy to write. High test coverage allows engineers to make changes with confidence that they aren¡¯t breaking anything.
  • They tend to make it easy to understand what¡¯s wrong when they fail because each test is conceptually simple and focused on a particular part of the system.
  • They can serve as documentation and examples, showing engineers how to use the part of the system being tested and how that system is intended to work.
  • ¸ù¾Ý¹È¸è¶Ô²âÊÔ´óСµÄ¶¨Òå,ËüÃÇÇãÏòÓÚ½ÏС¡£Ð¡²âÊÔÊÇ¿ìËÙºÍÈ·¶¨µÄ,ÔÊÐí¿ª·¢ÈËÔ±¾­³£ÔËÐÐËüÃÇ×÷Ϊ¹¤×÷Á÷µÄÒ»²¿·Ö,²¢»ñµÃ¼´Ê±·´À¡¡£
  • ËüÃÇÒ×ÓÚÔÚ²âÊÔ´úÂëµÄͬʱ±àд,ÔÊÐí¹¤³Ìʦ½«²âÊÔµÄÖصã·ÅÔÚËûÃÇÕýÔÚ¹¤×÷µÄ´úÂëÉÏ,¶ø²»±Ø½¨Á¢ºÍÀí½âÒ»¸ö¸ü´óµÄϵͳ¡£
  • ËüÃÇ´Ù½øÁ˸ßˮƽµÄ²âÊÔ¸²¸Ç,ÒòΪËüÃÇ¿ìËÙÇÒÈÝÒ×±àд¡£¸ß²âÊÔ¸²¸ÇÂÊʹ¹¤³ÌʦÄܹ»×ÔÐŵØ×ö³ö¸ü¸Ä,È·ÐÅËûÃÇûÓÐÆÆ»µÈκζ«Î÷¡£
  • ËûÃÇÇãÏòÓÚÔÚʧ°ÜʱÈÃÍæ¼Ò¸üÈÝÒ×Àí½âÄÄÀï³öÁËÎÊÌâ,ÒòΪÿ¸ö²âÊÔ¸ÅÄîÉ϶¼ºÜ¼òµ¥,²¢ÇÒÖ»¹ØעϵͳµÄÌض¨²¿·Ö¡£
  • ËüÃÇ¿ÉÒÔ×÷ΪÎĵµºÍʾÀý,Ïò¹¤³ÌʦչʾÈçºÎʹÓñ»²âÊÔϵͳµÄ²¿·Ö,ÒÔ¼°¸ÃϵͳÈçºÎ¹¤×÷¡£
origin
Due to their many advantages, most tests written at Google are unit tests, and as a rule of thumb, we encourage engineers to aim for a mix of about 80% unit tests and 20% broader-scoped tests. This advice, coupled with the ease of writing unit tests and the speed with which they run, means that engineers run a lot of unit tests¡ªit¡¯s not at all unusual for an engineer to execute thousands of unit tests (directly or indirectly) during the average workday.

ÓÉÓڹȸèµÄÐí¶àÓÅÊÆ,´ó¶àÊýÔڹȸèÉϱàдµÄ²âÊÔ¶¼Êǵ¥Ôª²âÊÔ,¸ù¾Ý¾­Ñé,ÎÒÃǹÄÀø¹¤³Ìʦ½«Ä¿±êÉ趨Ϊ80%µÄµ¥Ôª²âÊÔºÍ20%µÄ¿í·¶Î§²âÊÔµÄ×éºÏ¡£Õâ¸ö½¨Òé,¼ÓÉϱàдµ¥Ôª²âÊԵıã±ãÐÔºÍÔËÐеÄËÙ¶È,Òâζ׏¤³ÌʦҪÔËÐдóÁ¿µÄµ¥Ôª²âÊÔ¡ª¡ª¶ÔÓÚÒ»¸ö¹¤³ÌʦÀ´Ëµ,ÔÚƽ¾ù¹¤×÷ÈÕÖÐÖ´ÐÐÊýǧ¸öµ¥Ôª²âÊÔ(Ö±½Ó»ò¼ä½Ó)ÊǺܳ£¼ûµÄ¡£

origin
Because they make up such a big part of engineers¡¯ lives, Google puts a lot of focus on test maintainability. Maintainable tests are ones that ¡°just work¡±: after writing them, engineers don¡¯t need to think about them again until they fail, and those failures indicate real bugs with clear causes. The bulk of this chapter focuses on exploring the idea of maintainability and techniques for achieving it.
ÓÉÓÚËüÃÇÔÚ¹¤³ÌʦµÄÉú»îÖÐÕ¼¾ÝÁËÈç´Ë´óµÄÒ»²¿·Ö,¹È¸è½«´óÁ¿¾«Á¦·ÅÔÚ²âÊԵĿÉά»¤ÐÔÉÏ¡£¿Éά»¤µÄ²âÊÔÊÇÄÇЩ¡°Ö»Êǹ¤×÷¡±µÄ²âÊÔ:ÔÚ±àдËüÃÇÖ®ºó,¹¤³Ìʦ²»ÐèÒªÔÙ¿¼ÂÇËüÃÇ,Ö±µ½ËüÃÇʧ°Ü,¶øÕâЩʧ°Ü±íÃ÷ÁËÕæÕýµÄbug,²¢ÇÒÓÐÃ÷È·µÄÔ­Òò¡£±¾ÕµÄÖ÷ÒªÄÚÈÝÊÇ̽ÌÖ¿Éά»¤ÐԵĸÅÄîºÍʵÏÖ¿Éά»¤ÐԵļ¼Êõ¡£

¿Éά»¤µÄÖØÒªÐÔ The Importance of Maintainability

origin

Imagine this scenario: Mary wants to add a simple new feature to the product and is able to implement it quickly, perhaps requiring only a couple dozen lines of code. But when she goes to check in her change, she gets a screen full of errors back from the automated testing system. She spends the rest of the day going through those failures one by one. In each case, the change introduced no actual bug, but broke some of the assumptions that the test made about the internal structure of the code, requiring those tests to be updated. Often, she has difficulty figuring out what the tests were trying to do in the first place, and the hacks she adds to fix them make those tests even more difficult to understand in the future. Ultimately, what should have been a quick job ends up taking hours or even days of busywork, killing Mary¡¯s productivity and sapping her morale.

ÏëÏóÒ»ÏÂÕâÑùµÄ³¡¾°:MaryÏ£ÍûÏò²úÆ·Ìí¼ÓÒ»¸ö¼òµ¥µÄÐÂÌØÐÔ,²¢ÇÒÄܹ»¿ìËÙʵÏÖËü,Ò²ÐíÖ»ÐèÒª¼¸Ê®ÐдúÂë¡£µ«Êǵ±Ëý¼ì²é¸ü¸Äʱ,×Ô¶¯²âÊÔϵͳ»á·µ»ØÒ»¸ö³äÂú´íÎóµÄÆÁÄ»¡£Ê£ÏµÄʱ¼äÀï,ËýÒ»¸ö½ÓÒ»¸öµØ¾­ÀúÕâЩʧ°Ü¡£ÔÚÿÖÖÇé¿öÏÂ,¸ü¸Ä¶¼Ã»ÓÐÒýÈëʵ¼ÊµÄbug,µ«´òÆÆÁ˲âÊÔ¶Ô´úÂëÄÚ²¿½á¹¹Ëù×öµÄһЩ¼ÙÉè,ÒªÇó¸üÐÂÕâЩ²âÊÔ¡£Í¨³£,ËýÔÚÒ»¿ªÊ¼¾ÍºÜÄÑŪÇå³þÕâЩ²âÊÔÊÔͼ×öʲô,¶øËýΪÐÞ¸´ÕâЩ²âÊÔ¶øÌí¼ÓµÄhackʹµÃÕâЩ²âÊÔÔÚδÀ´¸ü¼ÓÄÑÒÔÀí½â¡£×îÖÕ,±¾Ó¦ºÜ¿ìÍê³ÉµÄ¹¤×÷È´»¨·ÑÁËÂêÀöÊýСʱÉõÖÁÊýÌìµÄæµ¹¤×÷,¶óɱÁËËýµÄ¹¤×÷ЧÂÊ,´ìÉËÁËËýµÄÊ¿Æø¡£

origin
Here, testing had the opposite of its intended effect by draining productivity rather than improving it while not meaningfully increasing the quality of the code under test. This scenario is far too common, and Google engineers struggle with it every day. There¡¯s no magic bullet, but many engineers at Google have been working to develop sets of patterns and practices to alleviate these problems, which we encourage the rest of the company to follow.
ÔÚÕâÀï,²âÊÔ²úÉúÁËÏà·´µÄЧ¹û,½µµÍÁËÉú²úÂÊ,¶ø²»ÊÇÌá¸ßÁËÉú²úÂÊ,ͬʱҲûÓÐÏÔÖøµØÌá¸ß±»²âÊÔ´úÂëµÄÖÊÁ¿¡£ÕâÖÖ³¡¾°Ì«³£¼ûÁË,¹È¸è¹¤³ÌʦÿÌ춼ÔÚÓëÖ®¶·Õù¡£Ã»ÓÐʲôÁ鵤ÃîÒ©,µ«¹È¸èµÄÐí¶à¹¤³ÌʦһֱÔÚŬÁ¦¿ª·¢Ò»Ì×ģʽºÍʵ¼ùÀ´»º½âÕâЩÎÊÌâ,ÎÒÃǹÄÀø¹«Ë¾µÄÆäËûÈËԱЧ·Â¡£ origin
The problems Mary ran into weren¡¯t her fault, and there was nothing she could have done to avoid them: bad tests must be fixed before they are checked in, lest they impose a drag on future engineers. Broadly speaking, the issues she encountered fall into two categories. First, the tests she was working with were brittle: they broke in response to a harmless and unrelated change that introduced no real bugs. Second, the tests were unclear: after they were failing, it was difficult to determine what was wrong, how to fix it, and what those tests were supposed to be doing in the first place.
MaryÓöµ½µÄÎÊÌâ²¢²»ÊÇËýµÄ´í,ËýҲûÓа취±ÜÃâÕâЩÎÊÌâ:Ôã¸âµÄ²âÊÔ±ØÐëÔÚ¼ì²é֮ǰ½øÐÐÐÞ¸´,ÒÔÃâ¸øδÀ´µÄ¹¤³Ìʦ´øÀ´Âé·³¡£Ò»°ãÀ´Ëµ,ËýÓöµ½µÄÎÊÌâ¿ÉÒÔ·ÖΪÁ½Àà¡£Ê×ÏÈ,ËýÕýÔÚ½øÐеIJâÊÔÊÇ´àÈõµÄ:ËüÃÇÔÚ¶ÔÒ»¸öÎÞº¦ÇÒ²»Ïà¹ØµÄ¸ü¸Ä×ö³ö·´Ó¦Ê±ÖжÏÁË,¶øÕâ¸ö¸ü¸Ä²¢Ã»ÓÐÒýÈëÕæÕýµÄbug¡£Æä´Î,ÕâЩ²âÊÔ²¢²»Ã÷È·:ÔÚËüÃÇʧ°Üºó,ºÜÄÑÈ·¶¨ÄÄÀï³öÁËÎÊÌâ,ÈçºÎÐÞ¸´,ÒÔ¼°ÕâЩ²âÊÔ×î³õÓ¦¸Ã×öʲô¡£

·ÀÖ¹´àÐÔ²âÊÔ Preventing Brittle Tests

origin

As just defined, a brittle test is one that fails in the face of an unrelated change to production code that does not introduce any real bugs1. Such tests must be diagnosed and fixed by engineers as part of their work. In small codebases with only a few engineers, having to tweak a few tests for every change might not be a big problem. But if a team regularly writes brittle tests, test maintenance will inevitably consume a larger and larger proportion of the team¡¯s time as they are forced to comb through an increasing number of failures in an ever-growing test suite. If a set of tests needs to be manually tweaked by engineers for each change, calling it an ¡°automated test suite¡± is a bit of a stretch!

ÕýÈç¸Õ¸Õ¶¨ÒåµÄÄÇÑù,´àÈõ²âÊÔÊÇÔÚ¶ÔÉú²ú´úÂë½øÐв»Ïà¹ØÇÒ²»»áÒýÈëÈκÎÕæÕý´íÎóµÄ¸ü¸Äʱʧ°ÜµÄ²âÊÔ1¡£¹¤³Ìʦ±ØÐëÔÚ¹¤×÷ÖÐÕï¶ÏºÍÐÞ¸´´ËÀà²âÊÔ¡£ÔÚÖ»ÓÐÉÙÊý¹¤³ÌʦµÄСÐÍ´úÂë¿âÖÐ,ÿ´Î¸ü¸Ä¶¼±ØÐëµ÷ÕûһЩ²âÊÔ¿ÉÄܲ»ÊÇʲô´óÎÊÌâ¡£µ«ÊÇÈç¹ûÒ»¸öÍŶӾ­³£±àд´àÈõµÄ²âÊÔ,²âÊÔά»¤½«²»¿É±ÜÃâµØÏûºÄÍŶÓÔ½À´Ô½¶àµÄʱ¼ä,ÒòΪËûÃDZ»ÆÈÔÚ²»¶ÏÔö³¤µÄ²âÊÔÌ×¼þÖÐÊáÀíÔ½À´Ô½¶àµÄ¹ÊÕÏ¡£Èç¹û¹¤³Ìʦÿ´Î¸ü¸Ä¶¼ÐèÒªÊÖ¶¯µ÷ÕûÒ»×é²âÊÔ,ÄÇô½«Æä³ÆΪ¡°×Ô¶¯»¯²âÊÔÌ×¼þ¡±Óеãǣǿ!

origin
Brittle tests cause pain in codebases of any size, but they become particularly acute at Google¡¯s scale. An individual engineer might easily run thousands of tests in a single day during the course of their work, and a single large-scale change (see Chapter 22) can trigger hundreds of thousands of tests. At this scale, spurious breakages that affect even a small percentage of tests can waste huge amounts of engineering time. Teams at Google vary quite a bit in terms of how brittle their test suites are, but we¡¯ve identified a few practices and patterns that tend to make tests more robust to change.
´àÈõµÄ²âÊÔ»á¸øÈκιæÄ£µÄ´úÂë¿â´øÀ´Í´¿à,µ«ÔÚ Google µÄ¹æÄ£ÉÏËüÃDZäµÃÌرðÑÏÖØ¡£µ¥¸ö¹¤³ÌʦÔÚÆ乤×÷¹ý³ÌÖпÉÄÜÔÚÒ»ÌìÖ®ÄÚÇáËÉÔËÐÐÊýǧ´Î²âÊÔ,¶øÒ»´Î´ó¹æÄ£¸ü¸Ä(²Î¼ûµÚ 22 ÕÂ)¿ÉÄܻᴥ·¢ÊýÊ®Íò´Î²âÊÔ¡£ÔÚÕâÖÖ¹æÄ£ÏÂ,¼´Ê¹ÊÇÓ°ÏìһС²¿·Ö²âÊÔµÄÐé¼ÙÆÆËðÒ²»áÀË·Ñ´óÁ¿µÄ¹¤³Ìʱ¼ä¡£ Google µÄÍŶÓÔÚ²âÊÔÌ×¼þµÄ´àÈõ³Ì¶È·½Ãæ´æÔںܴó²îÒì,µ«ÎÒÃÇÒѾ­È·¶¨ÁËһЩʵ¼ùºÍģʽ,ÕâЩʵ¼ùºÍģʽÍùÍù»áʹ²âÊÔ¸ü¼Ó½¡×³,Ò×ÓÚ¸ü¸Ä¡£

Ϊ²»±äµÄ²âÊÔ¶øŬÁ¦ Strive for Unchanging Tests

origin

Before talking about patterns for avoiding brittle tests, we need to answer a question: just how often should we expect to need to change a test after writing it? Any time spent updating old tests is time that can¡¯t be spent on more valuable work. Therefore, the ideal test is unchanging: after it¡¯s written, it never needs to change unless the requirements of the system under test change.

What does this look like in practice? We need to think about the kinds of changes that engineers make to production code and how we should expect tests to respond to those changes. Fundamentally, there are four kinds of changes:

ÔÚÌÖÂÛ±ÜÃâ´àÐÔ²âÊÔµÄģʽ֮ǰ,ÎÒÃÇÐèÒª»Ø´ðÒ»¸öÎÊÌâ:ÔÚ±àд²âÊÔÖ®ºó,ÎÒÃÇÓ¦¸ÃÆÚÍû¶à³¤Ê±¼ä¸ü¸ÄÒ»´Î²âÊÔ?Èκλ¨ÔÚ¸üоɲâÊÔÉϵÄʱ¼ä¶¼²»ÄÜÓÃÓÚ¸üÓмÛÖµµÄ¹¤×÷¡£Òò´Ë,ÀíÏëµÄ²âÊÔÊDz»±äµÄ:ÔÚ±àд֮ºó,ËüÓÀÔ¶²»ÐèÒª¸ü¸Ä,³ý·Ç±»²âϵͳµÄÐèÇó·¢Éú±ä»¯¡£

ÕâÔÚʵ¼ùÖÐÊÇʲôÑùµÄÄØ?ÎÒÃÇÐèÒª¿¼Âǹ¤³Ìʦ¶Ô²úÆ·´úÂëËù×öµÄ¸÷ÖÖ¸ü¸Ä,ÒÔ¼°ÎÒÃÇÓ¦¸ÃÈçºÎÆÚÍû²âÊÔ¶ÔÕâЩ¸ü¸Ä×÷³öÏìÓ¦¡£´Ó¸ù±¾ÉÏ˵,ÓÐËÄÖֱ仯:

origin

Pure refactorings

When an engineer refactors the internals of a system without modifying its interface, whether for performance, clarity, or any other reason, the system¡¯s tests shouldn¡¯t need to change. The role of tests in this case is to ensure that the refactoring didn¡¯t change the system¡¯s behavior. Tests that need to be changed during a refactoring indicate that either the change is affecting the system¡¯s behavior and isn¡¯t a pure refactoring, or that the tests were not written at an appropriate level of abstraction. Google¡¯s reliance on large-scale changes (described in Chapter 22) to do such refactorings makes this case particularly important for us.

´¿´âµÄÖع¹

µ±¹¤³ÌʦÖع¹ÏµÍ³µÄÄÚ²¿¶ø²»ÐÞ¸ÄÆä½Ó¿Úʱ,ÎÞÂÛÊdzöÓÚÐÔÄÜ¡¢ÇåÎú¶È»¹ÊÇÈκÎÆäËûÔ­Òò,ϵͳµÄ²âÊÔÓ¦¸Ã²»ÐèÒª¸ü¸Ä¡£ÔÚÕâÖÖÇé¿öÏÂ,²âÊÔµÄ×÷ÓÃÊÇÈ·±£Öع¹²»»á¸Ä±äϵͳµÄÐÐΪ¡£ÔÚÖع¹ÆÚ¼äÐèÒª¸ü¸ÄµÄ²âÊÔ±íÃ÷,Ҫô¸ü¸ÄÕýÔÚÓ°ÏìϵͳµÄÐÐΪ,¶ø²»ÊÇ´¿´âµÄÖع¹,Ҫô²âÊÔûÓÐÔÚÊʵ±µÄ³éÏ󼶱ðÉϱàд¡£¹È¸èÒÀÀµÓÚ´ó¹æÄ£µÄ¸ü¸Ä(ÔÚµÚ22ÕÂÖÐÃèÊö)À´½øÐÐÖع¹,ÕâʹµÃÕâÖÖÇé¿ö¶ÔÎÒÃÇÀ´ËµÌرðÖØÒª¡£
origin

New features

When an engineer adds new features or behaviors to an existing system, the system¡¯s existing behaviors should remain unaffected. The engineer must write new tests to cover the new behaviors, but they shouldn¡¯t need to change any existing tests. As with refactorings, a change to existing tests when adding new features suggest unintended consequences of that feature or inappropriate tests.

й¦ÄÜ

µ±¹¤³ÌʦÏòÏÖÓÐϵͳÌí¼ÓÐÂÌØÐÔ»òÐÐΪʱ,ϵͳµÄÏÖÓÐÐÐΪӦ¸Ã²»ÊÜÓ°Ïì¡£¹¤³Ìʦ±ØÐë±àдеIJâÊÔÀ´¸²¸ÇеÄÐÐΪ,µ«ÊÇËûÃDz»Ó¦¸ÃÐèÒª¸ü¸ÄÈκÎÏÖÓеIJâÊÔ¡£ÓëÖع¹Ò»Ñù,Ìí¼ÓÐÂÌØÐÔʱ¶ÔÏÖÓвâÊԵĸü¸Ä»áµ¼Ö¸ÃÌØÐÔ»ò²»Êʵ±µÄ²âÊÔ²úÉúÒâÏë²»µ½µÄºó¹û¡£
origin

Bug fixes

Fixing a bug is much like adding a new feature: the presence of the bug suggests that a case was missing from the initial test suite, and the bug fix should include that missing test case. Again, bug fixes typically shouldn¡¯t require updates to existing tests.

bugÐÞ¸´

ÐÞ¸´Ò»¸öbug¾ÍÏñÌí¼ÓÒ»¸öÐÂÌØÐÔ:bugµÄ³öÏÖ±íÃ÷ÔÚ×î³õµÄ²âÊÔÌ×¼þÖжªÊ§ÁËÒ»¸öÓÃÀý,¶øÐÞ¸´µÄbugÓ¦¸Ã°üÀ¨Õâ¸ö¶ªÊ§µÄ²âÊÔÓÃÀý¡£Í¬Ñù,bugÐÞ¸´Í¨³£²»ÐèÒª¶ÔÏÖÓвâÊÔ½øÐиüС£
origin

Behavior changes

Changing a system¡¯s existing behavior is the one case when we expect to have to make updates to the system¡¯s existing tests. Note that such changes tend to be significantly more expensive than the other three types. A system¡¯s users are likely to rely on its current behavior, and changes to that behavior require coordination with those users to avoid confusion or breakages. Changing a test in this case indicates that we¡¯re breaking an explicit contract of the system, whereas changes in the previous cases indicate that we¡¯re breaking an unintended contract. Lowlevel libraries will often invest significant effort in avoiding the need to ever make a behavior change so as not to break their users.

ÐÐΪ±ä»¯

µ±ÎÒÃÇÏ£Íû¸üÐÂϵͳµÄÏÖÓвâÊÔʱ,¸ü¸ÄϵͳµÄÏÖÓÐÐÐΪÊÇÒ»ÖÖÇé¿ö¡£Çë×¢Òâ,ÕâÖÖ¸ü¸ÄÍùÍù±ÈÆäËûÈýÖÖÀàÐ͵ĸü¸ÄÒª°º¹óµÃ¶à¡£ÏµÍ³µÄÓû§ºÜ¿ÉÄÜÒÀÀµÓÚÆ䵱ǰµÄÐÐΪ,¶ø¶Ô¸ÃÐÐΪµÄ¸ü¸ÄÐèÒªÓëÕâЩÓû§½øÐÐЭµ÷,ÒÔ±ÜÃâ»ìÂÒ»òÆÆ»µ¡£ÔÚÕâÖÖÇé¿öÏÂ,¸ü¸Ä²âÊÔ±íÃ÷ÎÒÃÇÕýÔÚÆÆ»µÒ»¸öÃ÷È·µÄϵͳÆõÔ¼,¶øÔÚÒÔÇ°µÄÇé¿öϸü¸Ä±íÃ÷ÎÒÃÇÕýÔÚÆÆ»µÒ»¸ö·ÇÔ¤ÆÚµÄÆõÔ¼¡£µÍ¼¶¿âͨ³£»áͶÈë´óÁ¿¾«Á¦À´±ÜÃâ½øÐÐÐÐΪ¸ü¸Ä,ÒÔÃâÆÆ»µÓû§¡£
origin
The takeaway is that after you write a test, you shouldn¡¯t need to touch that test again as you refactor the system, fix bugs, or add new features. This understanding is what makes it possible to work with a system at scale: expanding it requires writing only a small number of new tests related to the change you¡¯re making rather than potentially having to touch every test that has ever been written against the system. Only breaking changes in a system¡¯s behavior should require going back to change its tests, and in such situations, the cost of updating those tests tends to be small relative to the cost of updating all of the system¡¯s users.

½áÂÛÊÇ,ÔÚ±àд²âÊÔÖ®ºó,ÔÚÖع¹ÏµÍ³¡¢ÐÞ¸´bug»òÌí¼ÓÐÂÌØÐÔʱ,²»Ó¦¸ÃÔÙÅöÄǸö²âÊÔ¡£ÕâÖÖÀí½âʹ´ó¹æÄ£µÄϵͳ¹¤×÷³ÉΪ¿ÉÄÜ:À©Õ¹ËüÖ»ÐèÒª±àдÉÙÁ¿ÓëÄúËù×öµÄ¸ü¸ÄÏà¹ØµÄвâÊÔ,¶ø²»ÊÇDZÔڵرØÐë´¥¼°Ã¿Ò»¸öÕë¶Ôϵͳ±àдµÄ²âÊÔ¡£Ö»ÓÐÆÆ»µÏµÍ³ÐÐΪµÄ¸ü¸Ä²ÅÐèÒª·µ»Ø¸ü¸ÄËüµÄ²âÊÔ,ÔÚÕâÖÖÇé¿öÏÂ,¸üÐÂÕâЩ²âÊԵijɱ¾Ïà¶ÔÓÚ¸üÐÂËùÓÐϵͳÓû§µÄ³É±¾À´ËµÊǺÜСµÄ¡£

ͨ¹ý¹«¹²API²âÊÔ Test via Public APIs

origin
Now that we understand our goal, let¡¯s look at some practices for making sure that tests don¡¯t need to change unless the requirements of the system being tested change. By far the most important way to ensure this is to write tests that invoke the system being tested in the same way its users would; that is, make calls against its public API rather than its implementation details. If tests work the same way as the system¡¯s users, by definition, change that breaks a test might also break a user. As an additional bonus, such tests can serve as useful examples and documentation for users.
ÏÖÔÚÎÒÃÇÒѾ­Àí½âÁËÎÒÃǵÄÄ¿±ê,ÈÃÎÒÃÇ¿´¿´Ò»Ð©Êµ¼ù,ÒÔÈ·±£²âÊÔ²»ÐèÒª¸ü¸Ä,³ý·Ç±»²âÊÔϵͳµÄÐèÇó·¢Éú¸ü¸Ä¡£µ½Ä¿Ç°ÎªÖ¹,È·±£ÕâÒ»µãµÄ×îÖØÒªµÄ·½·¨ÊDZàд²âÊÔ,ÒÔÓëÓû§ÏàͬµÄ·½Ê½µ÷Óñ»²âÊÔµÄϵͳ;Ò²¾ÍÊÇ˵,¶ÔÆ乫¹²API¶ø²»ÊÇÆäʵÏÖϸ½Ú½øÐе÷Óá£Èç¹û²âÊԵŤ×÷·½Ê½ÓëϵͳÓû§Ïàͬ,ÄÇô¸ù¾Ý¶¨Òå,ÆÆ»µ²âÊԵĸü¸ÄÒ²¿ÉÄÜÆÆ»µÓû§¡£ÁíÍâ,ÕâÑùµÄ²âÊÔ¿ÉÒÔ×÷ΪÓÐÓõÄʾÀýºÍÎĵµÌṩ¸øÓû§¡£ origin
Consider Example 12-1, which validates a transaction and saves it to a database
¿¼ÂÇʾÀý12-1,ËüÑéÖ¤ÊÂÎñ²¢½«Æä±£´æµ½Êý¾Ý¿â ```Java public void processTransaction(Transaction transaction) { if (isValid(transaction)) { saveToDatabase(transaction); } } private boolean isValid(Transaction t) { return t.getAmount() < t.getSender().getBalance(); } private void saveToDatabase(Transaction t) { String s = t.getSender() + "," + t.getRecipient() + "," + t.getAmount(); database.put(t.getId(), s); } public void setAccountBalance(String accountName, int balance) { // Write the balance to the database directly } public void getAccountBalance(String accountName) { // Read transactions from the database to determine the account balance } A tempting way to test this code would be to remove the ¡°private¡± visibility modifiers and test the implementation logic directly, as demonstrated in Example 12-2. Example 12-2. A naive test of a transaction API¡¯s implementation @Test public void emptyAccountShouldNotBeValid() { assertThat(processor.isValid(newTransaction().setSender(EMPTY_ACCOUNT))) .isFalse(); } @Test public void shouldSaveSerializedData() { processor.saveToDatabase(newTransaction() .setId(123) .setSender("me") .setRecipient("you") .setAmount(100)); assertThat(database.get(123)).isEqualTo("me,you,100"); } ``` origin
A tempting way to test this code would be to remove the ¡°private¡± visibility modifiers and test the implementation logic directly, as demonstrated in Example 12-2.

Example 12-2. A naive test of a transaction API¡¯s implementation

²âÊÔÕâ¶Î´úÂëµÄÒ»¸öÓÕÈ˵ķ½·¨ÊÇɾ³ýprivate¿É¼ûÐÔÐÞÊηû,²¢Ö±½Ó²âÊÔʵÏÖÂß¼­,ÈçÀý12-2Ëùʾ¡£

ʾÀý12-2 ¶ÔÊÂÎñAPIʵÏֵļòµ¥²âÊÔ

@Test
public void emptyAccountShouldNotBeValid() {
 assertThat(processor.isValid(newTransaction().setSender(EMPTY_ACCOUNT)))
 .isFalse();
}
@Test
public void shouldSaveSerializedData() {
 processor.saveToDatabase(newTransaction()
 .setId(123)
 .setSender("me")
 .setRecipient("you")
 .setAmount(100));
 assertThat(database.get(123)).isEqualTo("me,you,100");
}
origin

This test interacts with the transaction processor in a much different way than its real users would: it peers into the system¡¯s internal state and calls methods that aren¡¯t publicly exposed as part of the system¡¯s API. As a result, the test is brittle, and almost any refactoring of the system under test (such as renaming its methods, factoring them out into a helper class, or changing the serialization format) would cause the test to break, even if such a change would be invisible to the class¡¯s real users.

Instead, the same test coverage can be achieved by testing only against the class¡¯s public API, as shown in Example 12-3.2

Example 12-3. Testing the public API

´Ë²âÊÔÓëÊÂÎñ´¦ÀíÆ÷µÄ½»»¥·½Ê½Óëʵ¼ÊÓû§µÄ½»»¥·½Ê½·Ç³£²»Í¬:Ëü²é¿´ÏµÍ³µÄÄÚ²¿×´Ì¬,²¢µ÷ÓÃδ×÷ΪϵͳAPIµÄÒ»²¿·Ö¹«¿ªµÄ·½·¨¡£²âÊÔ½á¹û,ÊÇ´àÈõµÄ,¼¸ºõËùÓеÄÖع¹±»²âϵͳ(ÈçÖØÃüÃûËüµÄ·½·¨,·Ö½â³ÉÒ»¸öÖúÊÖÀà,»ò¸Ä±äµÄÐòÁл¯¸ñʽ)»áµ¼Ö²âÊÔ´òÆÆ,¼´Ê¹ÕâÑùµÄ±ä»¯½«ÎÞÐεÄÀàµÄÕæʵÓû§¡£

Ïà·´,ͬÑùµÄ²âÊÔ¸²¸Ç¿ÉÒÔͨ¹ýÖ»Õë¶ÔÀàµÄ¹«¹²API½øÐвâÊÔÀ´ÊµÏÖ,ÈçÀý12-3Ëùʾ¡£2
Àý12-3.²âÊÔ¹«¹²API

@Test
public void shouldTransferFunds() {
 processor.setAccountBalance("me", 150);
 processor.setAccountBalance("you", 20);
 processor.processTransaction(newTransaction()
 .setSender("me")
 .setRecipient("you")
 .setAmount(100));
 assertThat(processor.getAccountBalance("me")).isEqualTo(50);
 assertThat(processor.getAccountBalance("you")).isEqualTo(120);
}
@Test
public void shouldNotPerformInvalidTransactions() {
 processor.setAccountBalance("me", 50);
 processor.setAccountBalance("you", 20);
 processor.processTransaction(newTransaction()
 .setSender("me")
 .setRecipient("you")
 .setAmount(100));
 assertThat(processor.getAccountBalance("me")).isEqualTo(50);
origin
Tests using only public APIs are, by definition, accessing the system under test in the same manner that its users would. Such tests are more realistic and less brittle because they form explicit contracts: if such a test breaks, it implies that an existing user of the system will also be broken. Testing only these contracts means that you¡¯re free to do whatever internal refactoring of the system you want without having to worry about making tedious changes to tests.

¸ù¾Ý¶¨Òå,½öʹÓù«¹²apiµÄ²âÊÔÒÔÓëÓû§ÏàͬµÄ·½Ê½·ÃÎʱ»²âϵͳ¡£ÕâÑùµÄ²âÊÔ¸üÏÖʵ,Ò²¸ü²»´àÈõ,ÒòΪËüÃÇÐγÉÁËÃ÷È·µÄÆõÔ¼:Èç¹ûÕâÑùµÄ²âÊÔʧ°Ü,¾ÍÒâζ×ÅϵͳµÄÏÖÓÐÓû§Ò²»áʧ°Ü¡£Ö»²âÊÔÕâЩÆõÔ¼Òâζ×ÅÄú¿ÉÒÔ×ÔÓɵضÔϵͳ½øÐÐÈκÎÄúÏëÒªµÄÄÚ²¿Öع¹,¶ø²»±Øµ£ÐĶԲâÊÔ½øÐз¦Î¶µÄ¸ü¸Ä¡£

origin
It¡¯s not always clear what constitutes a ¡°public API,¡± and the question really gets to the heart of what a ¡°unit¡± is in unit testing. Units can be as small as an individual function or as broad as a set of several related packages/modules. When we say ¡°public API¡± in this context, we¡¯re really talking about the API exposed by that unit to third parties outside of the team that owns the code. This doesn¡¯t always align with the notion of visibility provided by some programming languages; for example, classes in Java might define themselves as ¡°public¡± to be accessible by other packages in the same unit but are not intended for use by other parties outside of the unit. Some languages like Python have no built-in notion of visibility (often relying on conventions like prefixing private method names with underscores), and build systems like Bazel can further restrict who is allowed to depend on APIs declared public by the programming language.

²¢²»×ÜÊÇÇå³þʲô¹¹³ÉÁË¡°¹«¹²API¡±,Õâ¸öÎÊÌâÕæÕý´¥¼°µ½µ¥Ôª²âÊÔÖеġ°µ¥Ôª¡±µÄºËÐÄ¡£µ¥Ôª¿ÉÒÔСµ½Ò»¸öµ¥¶ÀµÄº¯Êý,Ò²¿ÉÒÔ´óµ½¼¸¸öÏà¹Ø°ü/Ä£¿éµÄ¼¯ºÏ¡£µ±ÎÒÃÇÔÚÕâ¸öÉÏÏÂÎÄÖÐÌáµ½¡°¹«¹²API¡±Ê±,ÎÒÃÇʵ¼ÊÉÏÊÇÔÚ̸ÂÛÓɸõ¥ÔªÏòÓµÓдúÂëµÄÍŶÓÖ®ÍâµÄµÚÈý·½¹«¿ªµÄAPI¡£Õâ²¢²»×ÜÊÇÓëһЩ±à³ÌÓïÑÔÌṩµÄ¿É¼ûÐÔ¸ÅÄîÏàÒ»ÖÂ;ÀýÈç,JavaÖеÄÀà¿ÉÒÔ½«×Ô¼º¶¨ÒåΪ¡°public¡±,ÒÔ¹©Í¬Ò»µ¥ÔªÖеÄÆäËû°ü·ÃÎÊ,µ«²»´òË㹩µ¥ÔªÍâµÄÆäËû·½Ê¹Óá£ÏñPythonÕâÑùµÄһЩÓïÑÔûÓÐÄÚÖõĿɼûÐÔ¸ÅÄî(ͨ³£ÒÀÀµÓÚһЩԼ¶¨,±ÈÈçÔÚ˽Óз½·¨ÃûÇ°¼ÓÉÏÏ»®Ïß),²¢ÇÒ¹¹½¨ÏµÍ³,±ÈÈçBazel,¿ÉÒÔ½øÒ»²½ÏÞÖÆË­¿ÉÒÔÒÀÀµ±à³ÌÓïÑÔÉùÃ÷µÄ¹«¹²api¡£

origin
Defining an appropriate scope for a unit and hence what should be considered the public API is more art than science, but here are some rules of thumb:
Ϊһ¸öµ¥Ôª¶¨ÒåÒ»¸öºÏÊʵķ¶Î§,Òò´ËʲôӦ¸Ã±»ÈÏΪÊǹ«¹²API¸üÏñÊÇÒÕÊõ¶ø²»ÊÇ¿Æѧ,µ«ÕâÀïÓÐһЩ¾­Ñé·¨Ôò: origin
  • If a method or class exists only to support one or two other classes (i.e., it is a ¡°helper class¡±), it probably shouldn¡¯t be considered its own unit, and its functionality should be tested through those classes instead of directly.

?Èç¹ûÒ»¸ö·½·¨»òÀàÖ»Ö§³ÖÒ»¸ö»òÁ½¸öÆäËûÀà(ÀýÈç,ËüÊÇÒ»¸ö¡°helperÀࡱ),Ëü¿ÉÄܲ»Ó¦¸Ã±»ÈÏΪÊÇËü×Ô¼ºµÄµ¥Ôª,ËüµÄ¹¦ÄÜÓ¦¸Ãͨ¹ýÕâЩÀà¶ø²»ÊÇÖ±½Ó²âÊÔ¡£

origin
  • If a package or class is designed to be accessible by anyone without having to consult with its owners, it almost certainly constitutes a unit that should be tested directly, where its tests access the unit in the same way that the users would.
  • Èç¹ûÒ»¸ö°ü»òÀà±»Éè¼Æ³ÉÈκÎÈ˶¼¿ÉÒÔ·ÃÎÊ,¶ø²»ÐèÒªÓëËüµÄËùÓÐÕßЭÉÌ,Ëü¼¸ºõ¿Ï¶¨»á¹¹³ÉÒ»¸öµ¥Ôª,Ó¦¸ÃÖ±½Ó²âÊÔ,ÔÚÕâÀï,ËüµÄ²âÊÔ·ÃÎʵ¥ÔªµÄ·½Ê½ÓëÓû§Ò»Ñù¡£
origin
  • If a package or class can be accessed only by the people who own it, but it is designed to provide a general piece of functionality useful in a range of contexts (i.e., it is a ¡°support library¡±), it should also be considered a unit and tested directly. This will usually create some redundancy in testing given that the support library¡¯s code will be covered both by its own tests and the tests of its users. However, such redundancy can be valuable: without it, a gap in test coverage could be introduced if one of the library¡¯s users (and its tests) were ever removed.
  • Èç¹ûÒ»¸ö°ü»òÀàÖ»Äܱ»ÓµÓÐËüµÄÈË·ÃÎÊ,µ«Ëü±»Éè¼ÆΪÔÚһϵÁÐÉÏÏÂÎÄÖÐÌṩһ¸öͨÓõŦÄÜ¿é(ÀýÈç,ËüÊÇÒ»¸ö¡°Ö§³Ö¿â¡±),ÄÇôËüÒ²Ó¦¸Ã±»ÊÓΪһ¸öµ¥Ôª²¢Ö±½Ó²âÊÔ¡£Õâͨ³£»áÔÚ²âÊÔÖвúÉúһЩÈßÓà,ÒòΪ֧³Ö¿âµÄ´úÂ뽫±»Ëü×Ô¼ºµÄ²âÊÔºÍËüµÄÓû§µÄ²âÊÔ¸²¸Ç¡£È»¶ø,ÕâÑùµÄÈßÓàÊÇÓмÛÖµµÄ:Èç¹ûûÓÐÈßÓà,µ±¿âµÄÒ»¸öÓû§(¼°Æä²âÊÔ)±»É¾³ýʱ,¿ÉÄÜ»áÒýÈë²âÊÔ¸²¸ÇµÄȱ¿Ú¡£
origin
At Google, we¡¯ve found that engineers sometimes need to be persuaded that testing via public APIs is better than testing against implementation details. The reluctance is understandable because it¡¯s often much easier to write tests focused on the piece of code you just wrote rather than figuring out how that code affects the system as a whole. Nevertheless, we have found it valuable to encourage such practices, as the extra upfront effort pays for itself many times over in reduced maintenance burden. Testing against public APIs won¡¯t completely prevent brittleness, but it¡¯s the most important thing you can do to ensure that your tests fail only in the event of meaningful changes to your system.
Ôڹȸè,ÎÒÃÇ·¢ÏÖ¹¤³ÌʦÓÐʱÐèÒª±»Ëµ·þ,ͨ¹ý¹«¹²api½øÐвâÊԱȸù¾ÝʵÏÖϸ½Ú½øÐвâÊÔ¸üºÃ¡£ÕâÖÖ²»ÇéÔ¸ÊÇ¿ÉÒÔÀí½âµÄ,ÒòΪͨ³£¸üÈÝÒ×±àд²âÊÔ¼¯ÖÐÔÚÄú¸Õ¸Õ±àдµÄ´úÂëƬ¶ÎÉÏ,¶ø²»ÊÇŪÇå³þ´úÂëÈçºÎÓ°ÏìÕû¸öϵͳ¡£È»¶ø,ÎÒÃÇ·¢ÏÖ¹ÄÀøÕâÑùµÄʵ¼ùÊǺÜÓмÛÖµµÄ,ÒòΪ¶îÍâµÄÇ°ÆÚ¹¤×÷ÔÚ¼õÉÙά»¤¸ºµ£·½ÃæÊÇÖµµÃµÄ¡£Õë¶Ô¹«¹²api½øÐвâÊÔ²¢²»ÄÜÍêÈ«±ÜÃâ´àÈõÐÔ,µ«ÕâÊÇÈ·±£²âÊÔ½öÔÚϵͳ·¢ÉúÓÐÒâÒåµÄ¸ü¸Äʱ²Å»áʧ°ÜµÄ×îÖØÒªµÄÊÂÇé¡£

²âÊÔ״̬,¶ø²»Êǽ»»¥ Test State, Not Interactions

origin
Another way that tests commonly depend on implementation details involves not which methods of the system the test calls, but how the results of those calls are verified. In general, there are two ways to verify that a system under test behaves as expected. With state testing, you observe the system itself to see what it looks like after invoking with it. With interaction testing, you instead check that the system took an expected sequence of actions on its collaborators in response to invoking it. Many tests will perform a combination of state and interaction validation.
²âÊÔͨ³£ÒÀÀµÓÚʵÏÖϸ½ÚµÄÁíÒ»ÖÖ·½Ê½²¢²»Éæ¼°²âÊÔµ÷ÓÃϵͳµÄÄÄЩ·½·¨,¶øÊÇÈçºÎÑéÖ¤ÕâЩµ÷ÓõĽá¹û¡£Í¨³£,ÓÐÁ½ÖÖ·½·¨À´ÑéÖ¤±»²âÊÔµÄϵͳÊÇ·ñ°´Ô¤ÆÚÔËÐС£Ê¹ÓÃ״̬²âÊÔ,Äú¿ÉÒÔ¹Û²ìϵͳ±¾Éí,¿´¿´µ÷ÓÃËüÖ®ºóËüÊÇʲôÑù×Ó¡£Ê¹Óý»»¥²âÊÔ,Äú¿ÉÒÔ¼ì²éϵͳ¶ÔÆäЭ×÷Õß²ÉÈ¡Ô¤ÆڵĶ¯×÷ÐòÁÐÒÔÏìÓ¦µ÷ÓÃËü¡£Ðí¶à²âÊÔ½«Ö´ÐÐ״̬ºÍ½»»¥ÑéÖ¤µÄ×éºÏ¡£ origin
Interaction tests tend to be more brittle than state tests for the same reason that it¡¯s more brittle to test a private method than to test a public method: interaction tests check how a system arrived at its result, whereas usually you should care only what the result is. Example 12-4 illustrates a test that uses a test double (explained further in Chapter 13) to verify how a system interacts with a database.
½»»¥²âÊÔÍùÍù±È״̬²âÊÔ¸ü´àÈõ,ÕâÓë²âÊÔ˽Óз½·¨±È²âÊÔ¹«¹²·½·¨¸ü´àÈõµÄÔ­ÒòÊÇÒ»ÑùµÄ:½»»¥²âÊÔ¼ì²éϵͳÊÇÈçºÎµÃµ½ËüµÄ½á¹ûµÄ,¶øͨ³£ÄúÓ¦¸ÃÖ»¹ØÐĽá¹ûÊÇʲô¡£Àý12-4ÑÝʾÁËÒ»¸öʹÓÃtest double(ÔÚµÚ13ÕÂÖнøÒ»²½½âÊÍ)À´Ñé֤ϵͳÈçºÎÓëÊý¾Ý¿â½»»¥µÄ²âÊÔ¡£ origin
Example 12-4. A brittle interaction test
Àý12-4 ´àÐÔÏ໥×÷ÓÃÊÔÑé ```java @Test public void shouldWriteToDatabase() { accounts.createUser("foobar"); verify(database).put("foobar"); } ``` origin

The test verifies that a specific call was made against a database API, but there are a couple different ways it could go wrong:

  • If a bug in the system under test causes the record to be deleted from the database shortly after it was written, the test will pass even though we would have wanted it to fail.

  • If the system under test is refactored to call a slightly different API to write an equivalent record, the test will fail even though we would have wanted it to pass.

Example 12-5. Testing against state

Õâ¸ö²âÊÔÑéÖ¤Ò»¸öÌض¨µÄµ÷ÓÃÊÇ·ñÕë¶ÔÒ»¸öÊý¾Ý¿âAPI,µ«ÊÇÓм¸ÖÖ²»Í¬µÄ·½·¨¿ÉÄܳö´í:
  • Èç¹û²âÊÔϵͳÖеÄÒ»¸öbugµ¼Ö¼Ç¼ÔÚдÈëºó²»¾Ã¾Í´ÓÊý¾Ý¿âÖÐɾ³ý,ÄÇô¼´Ê¹ÎÒÃÇÏ£ÍûËüʧ°Ü,²âÊÔÒ²»áͨ¹ý¡£

  • Èç¹û±»²âÊÔµÄϵͳ±»Öع¹,µ÷ÓÃÒ»¸öÉÔ΢²»Í¬µÄAPIÀ´Ð´Ò»¸öµÈ¼ÛµÄ¼Ç¼,²âÊÔ½«»áʧ°Ü,¼´Ê¹ÎÒÃÇÏ£ÍûËüͨ¹ý¡£

Àý12-5. ²âÊÔÏà·´µÄ״̬

@Test
public void shouldCreateUsers() {
 accounts.createUser("foobar");
 assertThat(accounts.getUser("foobar")).isNotNull();
}
origin

This test more accurately expresses what we care about: the state of the system under test after interacting with it.

The most common reason for problematic interaction tests is an over reliance on mocking frameworks. These frameworks make it easy to create test doubles that record and verify every call made against them, and to use those doubles in place of real objects in tests. This strategy leads directly to brittle interaction tests, and so we tend to prefer the use of real objects in favor of mocked objects, as long as the real objects are fast and deterministic.

For a more extensive discussion of test doubles and mocking frameworks, when they should be used, and safer alternatives, see Chapter 13.

Õâ¸ö²âÊÔ¸ü׼ȷµØ±í´ïÁËÎÒÃÇËù¹ØÐĵÄ:Óë±»²âϵͳ½»»¥ºóµÄ״̬¡£

ÓÐÎÊÌâµÄ½»»¥²âÊÔ×î³£¼ûµÄÔ­ÒòÊǹý¶ÈÒÀÀµmock¿ò¼Ü¡£ÕâЩ¿ò¼ÜʹµÃ´´½¨²âÊÔË«¾«¶È¶ÔÏó±äµÃºÜÈÝÒ×,¸Ã²âÊÔË«¾«¶È¶ÔÏó¼Ç¼ºÍÑéÖ¤¶ÔËüÃǽøÐеÄÿ¸öµ÷ÓÃ,²¢ÔÚ²âÊÔÖÐʹÓÃÕâЩ˫¾«¶È¶ÔÏó´úÌæʵ¼Ê¶ÔÏó¡£ÕâÖÖ²ßÂÔÖ±½Óµ¼Ö´àÐÔ½»»¥²âÊÔ,Òò´ËÎÒÃÇÇãÏòÓÚʹÓÃÕæʵ¶ÔÏó¶ø²»ÊÇÄ£Äâ¶ÔÏó,Ö»ÒªÕæʵ¶ÔÏóËٶȿìÇÒ¾ßÓÐÈ·¶¨ÐÔ¡£

¹ØÓÚ²âÊÔË«¹¦ÄܺÍmock¿ò¼ÜµÄ¸ü¹ã·ºÌÖÂÛ,ÒÔ¼°ËüÃÇÓ¦¸ÃÔÚʲôʱºòʹÓÃ,ÒÔ¼°¸ü°²È«µÄÌæ´ú·½°¸,Çë²Î¼û µÚ13ÕÂ.

±àдÇåÎúµÄ²âÊÔ Writing Clear Tests

origin
Sooner or later, even if we¡¯ve completely avoided brittleness, our tests will fail. Failure is a good thing¡ªtest failures provide useful signals to engineers, and are one of the main ways that a unit test provides value.
ÎÞÂÛÈçºÎ,¼´Ê¹ÎÒÃÇÒѾ­ÍêÈ«±ÜÃâÁË´àÐÔ,ÎÒÃǵIJâÊÔÒ²»áʧ°Ü¡£Ê§°ÜÊÇÒ»¼þºÃÊ¡ª¡ª²âÊÔʧ°ÜΪ¹¤³ÌʦÌṩÓÐÓõÄÐźÅ,²¢ÇÒÊǵ¥Ôª²âÊÔÌṩ¼ÛÖµµÄÖ÷Òª·½Ê½Ö®Ò»¡£ origin

Test failures happen for one of two reasons:3

  • The system under test has a problem or is incomplete. This result is exactly what tests are designed for: alerting you to bugs so that you can fix them.

  • The test itself is flawed. In this case, nothing is wrong with the system under test, but the test was specified incorrectly. If this was an existing test rather than one that you just wrote, this means that the test is brittle. The previous section discussed how to avoid brittle tests, but it¡¯s rarely possible to eliminate them entirely.

²âÊÔʧ°ÜµÄ·¢ÉúÓÐÁ½¸öÔ­Òò:3

  • ±»²âϵͳ´æÔÚÎÊÌâ»ò²»ÍêÕû¡£Õâ¸ö½á¹ûÕýÊÇÉè¼Æ²âÊÔµÄÄ¿µÄ:ÌáÐÑÄú´íÎó,ÒÔ±ãÄú¿ÉÒÔÐÞ¸´ËüÃÇ¡£

  • ²âÊÔ±¾Éí´æÔÚȱÏÝ¡£ÔÚÕâÖÖÇé¿öÏÂ,±»²âÊÔµÄϵͳûÓÐÈκÎÎÊÌâ,µ«ÊÇÖ¸¶¨µÄ²âÊÔ²»ÕýÈ·¡£Èç¹ûÕâÊÇÒ»¸öÏÖÓеIJâÊÔ,¶ø²»ÊÇÄú¸Õ¸Õ±àдµÄ²âÊÔ,ÕâÒâζןòâÊÔÊÇ´àÈõµÄ¡£ÉÏÒ»½ÚÌÖÂÛÁËÈçºÎ±ÜÃâ´àÐÔ²âÊÔ,µ«¼¸ºõ²»¿ÉÄÜÍêÈ«Ïû³ýËüÃÇ¡£

origin

When a test fails, an engineer¡¯s first job is to identify which of these cases the failure falls into and then to diagnose the actual problem. The speed at which the engineer can do so depends on the test¡¯s clarity. A clear test is one whose purpose for existing and reason for failing is immediately clear to the engineer diagnosing a failure. Tests fail to achieve clarity when their reasons for failure aren¡¯t obvious or when it¡¯s difficult to figure out why they were originally written. Clear tests also bring other benefits, such as documenting the system under test and more easily serving as a basis for new tests.

µ±²âÊÔʧ°Üʱ,¹¤³ÌʦµÄµÚÒ»Ï×÷ÊÇÈ·¶¨¹ÊÕÏÂäÔÚÄÄЩÇé¿öÏÂ,È»ºóÕï¶Ïʵ¼ÊÎÊÌâ¡£¹¤³ÌʦÄܹ»×öµ½ÕâÒ»µãµÄËÙ¶ÈÈ¡¾öÓÚ²âÊÔµÄÇåÎú¶È¡£ÇåÎúµÄ²âÊÔÊÇÖ¸´æÔÚµÄÄ¿µÄºÍʧ°ÜµÄÔ­ÒòÄܹ»Á¢¼´ÈÃÕï¶Ïʧ°ÜµÄ¹¤³ÌʦÇå³þµÄ²âÊÔ¡£µ±²âÊÔʧ°ÜµÄÔ­Òò²»Ã÷ÏÔ»òÕߺÜÄÑÕÒ³ö×î³õ±àдËüÃǵÄÔ­Òòʱ,²âÊԾͲ»ÄÜ´ïµ½ÇåÎúµÄÄ¿µÄ¡£ÇåÎúµÄ²âÊÔ»¹»á´øÀ´ÆäËûµÄºÃ´¦,±ÈÈç¼Ç¼²âÊÔϵÄϵͳ,¸üÈÝÒ××÷ΪвâÊԵĻù´¡¡£ origin

Test clarity becomes significant over time. Tests will often outlast the engineers who wrote them, and the requirements and understanding of a system will shift subtly as it ages. It¡¯s entirely possible that a failing test might have been written years ago by an engineer no longer on the team, leaving no way to figure out its purpose or how to fix it. This stands in contrast with unclear production code, whose purpose you can usually determine with enough effort by looking at what calls it and what breaks when it¡¯s removed. With an unclear test, you might never understand its purpose, since removing the test will have no effect other than (potentially) introducing a subtle hole in test coverage.

Ëæ×Åʱ¼äµÄÍÆÒÆ,²âÊÔÇåÎú¶È±äµÃ·Ç³£ÖØÒª¡£²âÊÔͨ³£»á±È±àд²âÊԵŤ³Ìʦ¸ü³Ö¾Ã,¶øÇÒËæ×ÅϵͳµÄÀÏ»¯,¶ÔϵͳµÄÐèÇóºÍÀí½âÒ²»á·¢Éú΢ÃîµÄ±ä»¯¡£Ê§°ÜµÄ²âÊÔÍêÈ«ÓпÉÄÜÊǼ¸ÄêÇ°Óɲ»ÔÙÔÚÍŶÓÖеŤ³Ìʦ±àдµÄ,ÕâÑù¾ÍûÓа취ŪÇå³þ²âÊÔµÄÄ¿µÄ»òÈçºÎÐÞ¸´Ëü¡£ÕâÓë²»Ã÷È·µÄ²úÆ·´úÂëÐγÉÁËÏÊÃ÷¶Ô±È,²úÆ·´úÂëµÄÄ¿µÄͨ³£¿ÉÒÔͨ¹ý²é¿´ÊÇʲôµ÷ÓÃËüÒÔ¼°µ±Ëü±»É¾³ýʱÊÇʲôÖжÏÀ´È·¶¨¡£¶ÔÓÚÒ»¸ö²»Ã÷È·µÄ²âÊÔ,Äú¿ÉÄÜÓÀÔ¶²»»áÀí½âËüµÄÄ¿µÄ,ÒòΪ³ýÈ¥²âÊÔ½«Ã»ÓÐÈκÎЧ¹û,³ýÁË(DZÔÚµØ)ÔÚ²âÊÔ¸²¸ÇÖÐÒýÈëÒ»¸ö΢ÃîµÄ©¶´¡£ origin
In the worst case, these obscure tests just end up getting deleted when engineers can¡¯t figure out how to fix them. Not only does removing such tests introduce a hole in test coverage, but it also indicates that the test has been providing zero value for perhaps the entire period it has existed (which could have been years).
ÔÚ×µÄÇé¿öÏÂ,ÕâЩģºýµÄ²âÊÔ×îÖջᱻɾ³ý,ÒòΪ¹¤³ÌʦÎÞ·¨ÕÒ³öÈçºÎÐÞ¸´ËüÃÇ¡£ÒƳýÕâÑùµÄ²âÊÔ²»½ö»áÔÚ²âÊÔ¸²¸ÇÉÏÒýÈëÒ»¸ö©¶´,¶øÇÒËü»¹±íÃ÷,¸Ã²âÊÔ¿ÉÄÜÔÚÆä´æÔÚµÄÕû¸öʱÆÚ(¿ÉÄÜÊǼ¸Äê)ÌṩÁËÁãÖµ¡£ origin
For a test suite to scale and be useful over time, it¡¯s important that each individual test in that suite be as clear as possible. This section explores techniques and ways of thinking about tests to achieve clarity.
ΪÁËÈòâÊÔÌ×¼þËæʱ¼äÀ©Õ¹²¢·¢»Ó×÷ÓÃ,Ì×¼þÖеÄÿ¸öµ¥¶À²âÊÔ¶¼¾¡¿ÉÄÜÇåÎúÊǷdz£ÖØÒªµÄ¡£±¾½Ú½«Ì½ÌÖ²âÊԵļ¼ÊõºÍ·½·¨,ÒÔʵÏÖ²âÊÔµÄÇåÎúÐÔ¡£

ÈÃÄãµÄ²âÊÔÍêÕû¶ø¼ò½à Make Your Tests Complete and Concise

origin
Two high-level properties that help tests achieve clarity are completeness and con©\ ciseness. A test is complete when its body contains all of the information a reader needs in order to understand how it arrives at its result. A test is concise when it con©\ tains no other distracting or irrelevant information. Example 12-6 shows a test that is neither complete nor concise: Example 12-6. An incomplete and cluttered test

Á½¸öÓÐÖúÓÚ²âÊÔʵÏÖÇåÎúµÄ¸ß¼¶ÊôÐÔÊÇÍêÕûÐԺͼò½àÐÔ¡£µ±Ò»¸ö²âÊÔµÄÖ÷Ìå°üº¬Á˶ÁÕßΪÁËÀí½âËüÊÇÈçºÎµÃµ½ËüµÄ½á¹ûËùÐèÒªµÄËùÓÐÐÅϢʱ,Õâ¸ö²âÊÔ¾ÍÍê³ÉÁË¡£µ±Ò»¸ö²âÊÔ²»°üº¬ÆäËû¸ÉÈÅ»òÎ޹صÄÐÅϢʱ,ËüÊǼò½àµÄ¡£Àý12-6ÏÔʾÁËÒ»¸ö¼È²»ÍêÕûÒ²²»¼ò½àµÄ²âÊÔ:
Àý12-6 Ò»¸ö²»ÍêÕûÇÒ»ìÂҵIJâÊÔ

@Test
public void shouldPerformAddition() {
 Calculator calculator = new Calculator(new RoundingStrategy(),
 "unused", ENABLE_COSINE_FEATURE, 0.01, calculusEngine, false);
 int result = calculator.calculate(newTestCalculation());
 assertThat(result).isEqualTo(5); // Where did this number come from?
}
origin
The test is passing a lot of irrelevant information into the constructor, and the actual important parts of the test are hidden inside of a helper method. The test can be made more complete by clarifying the inputs of the helper method, and more concise by using another helper to hide the irrelevant details of constructing the calculator, as illustrated in Example 12-7.

Example 12-7. A complete, concise test

²âÊÔ½«´óÁ¿²»Ïà¹ØµÄÐÅÏ¢´«µÝµ½¹¹Ô캯ÊýÖÐ,¶ø²âÊÔµÄʵ¼ÊÖØÒª²¿·ÖÒþ²ØÔÚhelper·½·¨ÖС£Ã÷È·helper·½·¨µÄÊäÈë¿ÉÒÔʹ²âÊÔ¸üÍêÕû,ʹÓÃÁíÒ»¸öhelperÒþ²Ø¹¹Ôì¼ÆËãÆ÷µÄÎÞ¹Øϸ½Ú¿ÉÒÔʹ²âÊÔ¸ü¼ò½à,ÈçÀý12-7Ëùʾ¡£

Àý12-7¡£Ò»¸öÍêÕû¶ø¼ò½àµÄ²âÊÔ

@Test
public void shouldPerformAddition() {
 Calculator calculator = newCalculator();
 int result = calculator.calculate(newCalculation(2, Operation.PLUS, 3));
  assertThat(result).isEqualTo(5);
}
origin
Ideas we discuss later, especially around code sharing, will tie back to completeness and conciseness. In particular, it can often be worth violating the DRY (Don¡¯t Repeat Yourself) principle if it leads to clearer tests. Remember: a test¡¯s body should contain all of the information needed to understand it without containing any irrelevant or distracting information.

ÎÒÃÇÉÔºóÌÖÂÛµÄÏë·¨,ÌرðÊǹØÓÚ´úÂë¹²ÏíµÄÏë·¨,½«ÓëÍêÕûÐԺͼò½àÐÔÁªÏµÆðÀ´¡£ÌرðÊÇ,Èç¹ûÎ¥±³DRY (Don 't Repeat Yourself)Ô­Ôò¿ÉÒÔ´øÀ´¸üÇåÎúµÄ²âÊÔ,ÄÇôΥ±³DRYÔ­Ôòͨ³£ÊÇÖµµÃµÄ¡£¼Çס:²âÊÔµÄÖ÷ÌåÓ¦¸Ã°üº¬Àí½âËüËùÐèµÄËùÓÐÐÅÏ¢,¶ø²»°üº¬ÈκÎÎ޹ػò·ÖÉ¢×¢ÒâÁ¦µÄÐÅÏ¢¡£

¶ÔÐÐΪ½øÐвâÊÔ,¶ø²»ÊÇ·½·¨ Test Behaviors, Not Methods

origin
The first instinct of many engineers is to try to match the structure of their tests to the structure of their code such that every production method has a corresponding test method. This pattern can be convenient at first, but over time it leads to problems: as the method being tested grows more complex, its test also grows in complexity and becomes more difficult to reason about. For example, consider the snippet of code in Example 12-8, which displays the results of a transaction.

Example 12-8. A transaction snippet

Ðí¶à¹¤³ÌʦµÄµÚÒ»·´Ó¦Êdz¢ÊÔ½«²âÊԵĽṹÓë´úÂëµÄ½á¹¹ÏàÆ¥Åä,ÕâÑùÿ¸öÉú²ú·½·¨¶¼ÓÐÏàÓ¦µÄ²âÊÔ·½·¨¡£ÕâÖÖģʽһ¿ªÊ¼¿ÉÄܷܺ½±ã,µ«Ëæ×Åʱ¼äµÄÍÆÒÆ,Ëü»áµ¼ÖÂÎÊÌâ:Ëæ×ű»²âÊԵķ½·¨±äµÃÔ½À´Ô½¸´ÔÓ,ËüµÄ²âÊÔÒ²±äµÃÔ½À´Ô½¸´ÔÓ,Ô½À´Ô½ÄÑÒÔÍÆÀí¡£ÀýÈç,¿¼ÂÇʾÀý12-8ÖеĴúÂëƬ¶Î,ËüÏÔʾÁËÊÂÎñµÄ½á¹û¡£

Àý12-8¡£Ò»¸öÊÂÎñ´úÂëƬ¶Î

public void displayTransactionResults(User user, Transaction transaction) {
 ui.showMessage("You bought a " + transaction.getItemName());
 if (user.getBalance() < LOW_BALANCE_THRESHOLD) {
 ui.showMessage("Warning: your balance is low!");
 }
}
origin
It wouldn¡¯t be uncommon to find a test covering both of the messages that might be shown by the method, as presented in Example 12-9.

Example 12-9. A method-driven test

ÔÚʾÀý12-9ÖÐ,¿ÉÒÔ·¢ÏÖÒ»¸ö°üº¬·½·¨¿ÉÄÜÏÔʾµÄÁ½¸öÏûÏ¢µÄ²âÊÔ,Õâ²¢²»º±¼û¡£

Àý12-9¡£²âÊÔÇý¶¯·½·¨

@Test
public void testDisplayTransactionResults() {
 transactionProcessor.displayTransactionResults(
 newUserWithBalance(
 LOW_BALANCE_THRESHOLD.plus(dollars(2))),
 new Transaction("Some Item", dollars(3)));
 assertThat(ui.getText()).contains("You bought a Some Item");
 assertThat(ui.getText()).contains("your balance is low");
}
origin

With such tests, it¡¯s likely that the test started out covering only the first method. Later, an engineer expanded the test when the second message was added (violating the idea of unchanging tests that we discussed earlier). This modification sets a bad precedent: as the method under test becomes more complex and implements more functionality, its unit test will become increasingly convoluted and grow more and more difficult to work with.

The problem is that framing tests around methods can naturally encourage unclear tests because a single method often does a few different things under the hood and might have several tricky edge and corner cases. There¡¯s a better way: rather than writing a test for each method, write a test for each behavior.4 A behavior is any guarantee that a system makes about how it will respond to a series of inputs while in a particular state.5 Behaviors can often be expressed using the words ¡°given,¡± ¡°when,¡± and ¡°then¡±: ¡°Given that a bank account is empty, when attempting to withdraw money from it, then the transaction is rejected.¡± The mapping between methods and behaviors is many-to-many: most nontrivial methods implement multiple behaviors, and some behaviors rely on the interaction of multiple methods. The previous example can be rewritten using behavior-driven tests, as presented in Example 12-10.

Example 12-10. A behavior-driven test

ÔÚÕâÑùµÄ²âÊÔÖÐ,²âÊÔ¿ÉÄÜÒ»¿ªÊ¼Ö»¸²¸ÇµÚÒ»ÖÖ·½·¨¡£ÉÔºó,µ±Ìí¼ÓµÚ¶þÌõÏûϢʱ,¹¤³ÌʦÀ©Õ¹Á˲âÊÔ(Î¥·´ÁËÎÒÃÇÇ°ÃæÌÖÂ۵IJ»±ä²âÊÔµÄÏë·¨)¡£Õâ¸öÐÞ¸ÄÉèÖÃÁËÒ»¸ö²»ºÃµÄÏÈÀý:Ëæ×ŲâÊÔÖеķ½·¨±äµÃ¸ü¼Ó¸´ÔÓ,ʵÏÖÁ˸ü¶àµÄ¹¦ÄÜ,ËüµÄµ¥Ôª²âÊÔ½«±äµÃÔ½À´Ô½¸´ÔÓ,Ô½À´Ô½ÄÑÒÔʹÓá£

ÎÊÌâÔÚÓÚ,ΧÈÆ·½·¨¹¹½¨²âÊÔ»á×ÔÈ»¶øÈ»µØ¹ÄÀø²»Ã÷È·µÄ²âÊÔ,ÒòΪµ¥Ò»µÄ·½·¨Í¨³£»áÔÚÄ»ºó×öһЩ²»Í¬µÄÊÂÇé,²¢ÇÒ¿ÉÄÜ»áÓÐһЩ¼¬ÊֵıßÔµºÍ½ÇÂäÇé¿ö4¡£ÓÐÒ»ÖÖ¸üºÃµÄ·½·¨:Ϊÿ¸öÐÐΪ±àдһ¸ö²âÊÔ,¶ø²»ÊÇΪÿ¸ö·½·¨±àдһ¸ö²âÊÔ5¡£ÐÐΪÊÇϵͳÔÚÌض¨×´Ì¬ÏÂÈçºÎÏìӦһϵÁÐÊäÈëµÄÈκα£Ö¤¡£ÐÐΪͨ³£¿ÉÒÔÓá°¸ø¶¨¡±¡¢¡°µ±¡±ºÍ¡°È»ºó¡±À´±í´ï:¡°¼ÙÉèÒ»¸öÒøÐÐÕË»§ÊÇ¿ÕµÄ,µ±ÊÔͼ´ÓËüÀïÃæȡǮʱ,ÄÇô½»Ò׾ͻᱻ¾Ü¾ø¡£¡±·½·¨ºÍÐÐΪ֮¼äµÄÓ³ÉäÊǶà¶Ô¶àµÄ:´ó¶àÊýÖØÒªµÄ·½·¨ÊµÏÖ¶à¸öÐÐΪ,һЩÐÐΪÒÀÀµÓÚ¶à¸ö·½·¨µÄ½»»¥¡£¿ÉÒÔʹÓÃÐÐΪÇý¶¯²âÊÔÖØдǰÃæµÄÀý×Ó,ÈçÀý12-10Ëùʾ¡£

Àý12-10¡£ÐÐΪÇý¶¯²âÊÔ

@Test
public void displayTransactionResults_showsItemName() {
 transactionProcessor.displayTransactionResults(
 new User(), new Transaction("Some Item"));
 assertThat(ui.getText()).contains("You bought a Some Item");
}
@Test
public void displayTransactionResults_showsLowBalanceWarning() {
 transactionProcessor.displayTransactionResults(
 newUserWithBalance(
 LOW_BALANCE_THRESHOLD.plus(dollars(2))),
 new Transaction("Some Item", dollars(3)));
 assertThat(ui.getText()).contains("your balance is low");
}
origin
The extra boilerplate required to split apart the single test is more than worth it, and the resulting tests are much clearer than the original test. Behavior-driven tests tend to be clearer than method-oriented tests for several reasons. First, they read more like natural language, allowing them to be naturally understood rather than requiring laborious mental parsing. Second, they more clearly express cause and effect because each test is more limited in scope. Finally, the fact that each test is short and descriptive makes it easier to see what functionality is already tested and encourages engineers to add new streamlined test methods instead of piling onto existing methods.

·Ö¸îµ¥¸ö²âÊÔËùÐèµÄ¶îÍâÑù°åÊǷdz£ÖµµÃµÄ,½á¹û±È֮ǰµÄ²âÊÔ¸üÇåÎú¡£³öÓÚ¼¸¸öÔ­Òò,ÐÐΪÇý¶¯µÄ²âÊÔÍùÍù±ÈÃæÏò·½·¨µÄ²âÊÔ¸üÇåÎú¡£Ê×ÏÈ,ËüÃǶÁÆðÀ´¸üÏñ×ÔÈ»ÓïÑÔ,ʹËüÃÇÄܹ»×ÔÈ»µØ±»Àí½â,¶ø²»ÐèÒª·ÑÁ¦µÄÐÄÀí½âÎö¡£Æä´Î,ËüÃǸüÇå³þµØ±í´ïÒò¹û¹Øϵ,ÒòΪÿ¸ö²âÊԵķ¶Î§¸üÓÐÏÞ¡£×îºó,ÿ¸ö²âÊÔ¶¼ºÜ¶ÌÇÒÃèÊöÐÔÇ¿,ÕâÒ»ÊÂʵʹÎÒÃǸüÈÝÒ׿´µ½ÄÄЩ¹¦ÄÜÒѾ­±»²âÊÔ,²¢¹ÄÀø¹¤³ÌʦÌí¼Óеļò»¯µÄ²âÊÔ·½·¨,¶ø²»ÊǶѻýÔÚÏÖÓеķ½·¨ÉÏ¡£

½á¹¹²âÊÔÇ¿µ÷ÐÐΪ Structure tests to emphasize behaviors

origin

Thinking about tests as being coupled to behaviors instead of methods significantly affects how they should be structured. Remember that every behavior has three parts: a ¡°given¡± component that defines how the system is set up, a ¡°when¡± component that defines the action to be taken on the system, and a ¡°then¡± component that validates the result6. Tests are clearest when this structure is explicit. Some frameworks like Cucumber and Spock directly bake in given/when/then. Other languages can use whitespace and optional comments to make the structure stand out, such as that shown in Example 12-11.

Example 12-11. A well-structured test

½«²âÊÔ¿´×÷ÊÇÓëÐÐΪ¶ø²»ÊÇÓë·½·¨ÏàñîºÏµÄ,Õ⽫¼«´óµØÓ°Ïì²âÊԵĽṹ¡£¼Çס,ÿ¸öÐÐΪ¶¼ÓÐÈý¸ö²¿·Ö:Ò»¸ö¡°give¡±×é¼þ,Ëü¶¨ÒåÁËϵͳÊÇÈçºÎÉèÖõÄ;Ò»¸ö¡°when¡±×é¼þ,Ëü¶¨ÒåÁËÒªÔÚϵͳÉÏÖ´ÐеIJÙ×÷;Ò»¸ö¡°then¡±×é¼þ,ËüÑéÖ¤½á¹û6¡£µ±Õâ¸ö½á¹¹ÊÇÃ÷È·µÄʱ,²âÊÔÊÇ×îÇå³þµÄ¡£ÓÐЩ¿ò¼Ü,ÈçCucumberºÍSpock,Ö±½ÓÔÚ¸ø¶¨/µ±/È»ºóÖнøÐк決¡£ÆäËûÓïÑÔ¿ÉÒÔʹÓÿոñºÍ¿ÉѡעÊÍÀ´Í»³öÕâ¸ö½á¹¹,ÈçÀý12-11Ëùʾ¡£

ʾÀý12-11¡£Ò»¸ö½á¹¹Á¼ºÃµÄ²âÊÔ

@Test
public void transferFundsShouldMoveMoneyBetweenAccounts() {
 // Given two accounts with initial balances of $150 and $20
 Account account1 = newAccountWithBalance(usd(150));
 Account account2 = newAccountWithBalance(usd(20));
 // When transferring $100 from the first to the second account
 bank.transferFunds(account1, account2, usd(100));
 // Then the new account balances should reflect the transfer
 assertThat(account1.getBalance()).isEqualTo(usd(50));
 assertThat(account2.getBalance()).isEqualTo(usd(120));
}
origin
This level of description isn¡¯t always necessary in trivial tests, and it¡¯s usually sufficient to omit the comments and rely on whitespace to make the sections clear. However, explicit comments can make more sophisticated tests easier to understand. This pattern makes it possible to read tests at three levels of granularity:
  1. A reader can start by looking at the test method name (discussed below) to get a rough description of the behavior being tested.
  2. If that¡¯s not enough, the reader can look at the given/when/then comments for a formal description of the behavior.
  3. Finally, a reader can look at the actual code to see precisely how that behavior is expressed.

This pattern is most commonly violated by interspersing assertions among multiple calls to the system under test (i.e., combining the ¡°when¡± and ¡°then¡± blocks). Merging the ¡°then¡± and ¡°when¡± blocks in this way can make the test less clear because it makes it difficult to distinguish the action being performed from the expected result.

ÕâÖÖ¼¶±ðµÄÃèÊöÔÚ¼òµ¥µÄ²âÊÔÖв¢²»×ÜÊDZØÒªµÄ,ͨ³£Ê¡ÂÔ×¢ÊͲ¢ÒÀ¿¿¿Õ¸ñÀ´Ê¹²¿·ÖÇåÎú¾Í×ã¹»ÁË¡£È»¶ø,ÏÔʽעÊÍ¿ÉÒÔʹ¸ü¸´ÔӵIJâÊÔ¸üÈÝÒ×Àí½â¡£ÕâÖÖģʽ¿ÉÒÔÔÚÈý¸öÁ£¶È¼¶±ð¶ÁÈ¡²âÊÔ:

  1. ¶ÁÕß¿ÉÒԴӲ鿴²âÊÔ·½·¨Ãû³Æ(ÏÂÃæÌÖÂÛ)¿ªÊ¼,ÒÔ»ñµÃ±»²âÊÔÐÐΪµÄ´ÖÂÔÃèÊö¡£
  2. Èç¹ûÕ⻹²»¹»,¶ÁÕß¿ÉÒԲ鿴¸ø¶¨µÄ/when/then×¢ÊÍ,ÒÔ»ñµÃ¶Ô¸ÃÐÐΪµÄÕýʽÃèÊö¡£
  3. ×îºó,¶ÁÕß¿ÉÒԲ鿴ʵ¼ÊµÄ´úÂë,ÒÔ׼ȷµØÁ˽â¸ÃÐÐΪÊÇÈçºÎ±í´ïµÄ¡£

ÔÚ¶Ô±»²âÊÔϵͳµÄ¶à¸öµ÷ÓÃÖ®¼äÉ¢²¼¶ÏÑÔ(ÀýÈç,½áºÏ¡°when¡±ºÍ¡°then¡±¿é),ͨ³£»áÎ¥·´ÕâÖÖģʽ¡£ÒÔÕâÖÖ·½Ê½ºÏ²¢¡°then¡±ºÍ¡°when¡±¿é»áʹ²âÊÔ±äµÃ²»ÄÇôÇåÎú,ÒòΪËüºÜÄѽ«ÕýÔÚÖ´ÐеIJÙ×÷ÓëÔ¤ÆڵĽá¹ûÇø·Ö¿ªÀ´¡£

origin

When a test does want to validate each step in a multistep process, it¡¯s acceptable to define alternating sequences of when/then blocks. Long blocks can also be made more descriptive by splitting them up with the word ¡°and.¡± Example 12-12 shows what a relatively complex, behavior-driven test might look like.

Example 12-12. Alternating when/then blocks within a test

µ±²âÊÔȷʵÏëÒªÑéÖ¤¶à²½Öè¹ý³ÌÖеÄÿ¸ö²½Öèʱ,¿ÉÒÔ¶¨ÒåWhen /then¿éµÄ½»ÌæÐòÁС£³¤¿é»¹¿ÉÒÔͨ¹ýʹÓ᰺͡±À´·Ö¸ô,ʹÆä¸ü¾ßÃèÊöÐÔ¡£Àý12-12ÏÔʾÁËÒ»¸öÏà¶Ô¸´Ôӵġ¢ÐÐΪÇý¶¯µÄ²âÊÔ¿ÉÄÜÊÇʲôÑùµÄ¡£
12-12ʾÀý¡£when/thenÔÚ²âÊÔÖÐ×èÈû½»Ìæ

@Test
public void shouldTimeOutConnections() {
 // Given two users
 User user1 = newUser();
 User user2 = newUser();
 // And an empty connection pool with a 10-minute timeout
 Pool pool = newPool(Duration.minutes(10));
 // When connecting both users to the pool
 pool.connect(user1);
 pool.connect(user2);
 // Then the pool should have two connections
 assertThat(pool.getConnections()).hasSize(2);
 // When waiting for 20 minutes
 clock.advance(Duration.minutes(20));
 // Then the pool should have no connections
 assertThat(pool.getConnections()).isEmpty();
 // And each user should be disconnected
 assertThat(user1.isConnected()).isFalse();
 assertThat(user2.isConnected()).isFalse();
}
origin
When writing such tests, be careful to ensure that you¡¯re not inadvertently testing multiple behaviors at the same time. Each test should cover only a single behavior, and the vast majority of unit tests require only one ¡°when¡± and one ¡°then¡± block.
ÔÚ±àдÕâÑùµÄ²âÊÔʱ,ҪСÐÄÈ·±£ÄúûÓÐÔÚͬһʱ¼äÎÞÒâµØ²âÊÔ¶à¸öÐÐΪ¡£Ã¿¸ö²âÊÔÓ¦¸ÃÖ»¸²¸Çµ¥Ò»µÄÐÐΪ,²¢ÇÒ¾ø´ó¶àÊýµ¥Ôª²âÊÔÖ»ÐèÒªÒ»¸ö¡°when¡±ºÍÒ»¸ö¡°then¡±¿é¡£

ÔÚÕýÔÚ²âÊÔµÄÐÐΪ֮ºóÃüÃû²âÊÔ Name tests after the behavior being tested

origin
Method-oriented tests are usually named after the method being tested (e.g., a test for the updateBalance method is usually called testUpdateBalance). With more focused behavior-driven tests, we have a lot more flexibility and the chance to convey useful information in the test¡¯s name. The test name is very important: it will often be the first or only token visible in failure reports, so it¡¯s your best opportunity to communicate the problem when the test breaks. It¡¯s also the most straightforward way to express the intent of the test.

A test¡¯s name should summarize the behavior it is testing. A good name describes both the actions that are being taken on a system and the expected outcome. Test names will sometimes include additional information like the state of the system or its environment before taking action on it. Some languages and frameworks make this easier than others by allowing tests to be nested within one another and named using strings, such as in Example 12-13, which uses Jasmine.

Example 12-13. Some sample nested naming patterns

ÃæÏò·½·¨µÄ²âÊÔͨ³£ÒÔ±»²âÊԵķ½·¨ÃüÃû(ÀýÈç,updateBalance·½·¨µÄ²âÊÔͨ³£³ÆΪtestUpdateBalance)¡£ÓÐÁ˸ü¼¯ÖеÄÐÐΪÇý¶¯²âÊÔ,ÎÒÃÇÓиü¶àµÄÁé»îÐԺͻú»áÔÚ²âÊÔµÄÃû³ÆÖд«µÝÓÐÓõÄÐÅÏ¢¡£²âÊÔÃû³Æ·Ç³£ÖØÒª:Ëüͨ³£ÊÇʧ°Ü±¨¸æÖеÚÒ»¸ö»òΨһ¿É¼ûµÄ±ê¼Ç,Òò´Ëµ±²âÊÔÖжÏʱ,ËüÊÇÄú¹µÍ¨ÎÊÌâµÄ×î¼Ñ»ú»á¡£ÕâÒ²ÊDZí´ï²âÊÔÒâͼµÄ×îÖ±½ÓµÄ·½Ê½¡£

²âÊÔµÄÃû³ÆÓ¦¸Ã×ܽáËüËù²âÊÔµÄÐÐΪ¡£Ò»¸öºÃµÄÃû³Æ¼È¿ÉÒÔÃèÊöÔÚϵͳÉÏÖ´ÐеIJÙ×÷,Ò²¿ÉÒÔÃèÊöÔ¤ÆڵĽá¹û¡£²âÊÔÃû³ÆÓÐʱ»á°üº¬¶îÍâµÄÐÅÏ¢,±ÈÈçÔÚ¶ÔÆä²ÉÈ¡Ðж¯Ö®Ç°ÏµÍ³»òÆä»·¾³µÄ״̬¡£ÓÐЩÓïÑԺͿò¼ÜÔÊÐí²âÊԱ˴ËǶÌײ¢Ê¹ÓÃ×Ö·û´®ÃüÃû,´Ó¶øʹÕâÒ»µã±ÈÆäËûÓïÑԺͿò¼Ü¸üÈÝÒ×ʵÏÖ,ÀýÈçÔÚʾÀý12-13ÖÐ,ËüʹÓÃÁËJasmine¡£

Àý12 - 13¡£Ò»Ð©Ç¶Ì×ÃüÃûģʽʾÀý

describe("multiplication", function() {
 describe("with a positive number", function() {
    var positiveNumber = 10;
    it("is positive with another positive number", function() {
      expect(positiveNumber * 10).toBeGreaterThan(0);
    });
    it("is negative with a negative number", function() {
      expect(positiveNumber * -10).toBeLessThan(0);
    });
  });
  describe("with a negative number", function() {
    var negativeNumber = 10;
    it("is negative with a positive number", function() {
      expect(negativeNumber * 10).toBeLessThan(0);
    });
    it("is positive with another negative number", function() {
      expect(negativeNumber * -10).toBeGreaterThan(0);
    });
  });  
});
origin
Other languages require us to encode all of this information in a method name, leading to method naming patterns like that shown in Example 12-14.

Example 12-14. Some sample method naming patterns

ÆäËûÓïÑÔÒªÇóÎÒÃǽ«ËùÓÐÕâЩÐÅÏ¢±àÂëµ½Ò»¸ö·½·¨ÃûÖÐ,µ¼Ö·½·¨ÃüÃûģʽÈçÀý12-14Ëùʾ¡£

Àý12-14¡£Ò»Ð©Ê¾Àý·½·¨ÃüÃûģʽ

multiplyingTwoPositiveNumbersShouldReturnAPositiveNumber
multiply_postiveAndNegative_returnsNegative
divide_byZero_throwsException
origin
Names like this are much more verbose than we¡¯d normally want to write for methods in production code, but the use case is different: we never need to write code that calls these, and their names frequently need to be read by humans in reports. Hence, the extra verbosity is warranted.

Many different naming strategies are acceptable so long as they¡¯re used consistently within a single test class. A good trick if you¡¯re stuck is to try starting the test name with the word ¡°should.¡± When taken with the name of the class being tested, this naming scheme allows the test name to be read as a sentence. For example, a test of a BankAccount class named shouldNotAllowWithdrawalsWhenBalanceIsEmpty can be read as ¡°BankAccount should not allow withdrawals when balance is empty.¡± By reading the names of all the test methods in a suite, you should get a good sense of the behaviors implemented by the system under test. Such names also help ensure that the test stays focused on a single behavior: if you need to use the word ¡°and¡± in a test name, there¡¯s a good chance that you¡¯re actually testing multiple behaviors and should be writing multiple tests!

ÕâÑùµÄÃû³Æ±ÈÎÒÃÇͨ³£Ï£ÍûÔÚÉú²ú´úÂëÖÐΪ·½·¨±àдµÄÃû³ÆÒªÈß³¤µÃ¶à,µ«ÓÃÀý²»Í¬:ÎÒÃÇ´Ó²»ÐèÒª±àдµ÷ÓÃÕâЩ·½·¨µÄ´úÂë,¶øÇÒËüÃǵÄÃû³Æ¾­³£ÐèÒªÔÚ±¨¸æÖб»È˶ÁÈ¡¡£Òò´Ë,¶îÍâµÄÈß³¤ÊDZØÒªµÄ¡£
Ö»ÒªÔÚÒ»¸ö²âÊÔÀàÖÐÒ»ÖµØʹÓÃÐí¶à²»Í¬µÄÃüÃû²ßÂÔ,ËüÃǶ¼ÊÇ¿ÉÒÔ½ÓÊܵġ£Èç¹ûÄú¿¨×¡ÁË,Ò»¸öºÜºÃµÄ¼¼ÇÉÊdz¢ÊÔÒÔµ¥´Ê¡°should¡±¿ªÊ¼²âÊÔÃû³Æ¡£µ±Ê¹Óñ»²âÊÔÀàµÄÃû³Æʱ,Õâ¸öÃüÃû·½°¸ÔÊÐí½«²âÊÔÃû³Æ¶ÁΪһ¸ö¾ä×Ó¡£ÀýÈç,Ò»¸öÃûΪshouldnotallowswhenbalanceisemptyµÄBankAccountÀà²âÊÔ¿ÉÒÔ±»¶ÁΪ¡°µ±Óà¶îΪ¿Õʱ,BankAccount²»ÔÊÐíÈ¡¿î¡±¡£Í¨¹ýÔĶÁÌ×¼þÖÐËùÓвâÊÔ·½·¨µÄÃû³Æ,ÄúÓ¦¸ÃÄܹ»ºÜºÃµØÁ˽ⱻ²âÊÔϵͳËùʵÏÖµÄÐÐΪ¡£ÕâÑùµÄÃû³Æ»¹ÓÐÖúÓÚÈ·±£²âÊÔרעÓÚµ¥Ò»µÄÐÐΪ:Èç¹ûÄúÐèÒªÔÚ²âÊÔÃû³ÆÖÐʹÓ᰺͡±Õâ¸ö´Ê,ÄÇôºÜÓпÉÄÜÄúʵ¼ÊÉÏÕýÔÚ²âÊÔ¶à¸öÐÐΪ,²¢ÇÒÓ¦¸Ã±àд¶à¸ö²âÊÔ!

²»ÒªÔÚ²âÊÔÖÐʹÓÃÂß¼­ Don¡¯t Put Logic in Tests

origin
Clear tests are trivially correct upon inspection; that is, it is obvious that a test is doing the correct thing just from glancing at it. This is possible in test code because each test needs to handle only a particular set of inputs, whereas production code must be generalized to handle any input. For production code, we¡¯re able to write tests that ensure complex logic is correct. But test code doesn¡¯t have that luxury¡ªif you feel like you need to write a test to verify your test, something has gone wrong

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn¡¯t take much logic to make a test more difficult to reason about. For example, does the test in Example 12-15 look correct to you?
Example 12-15. Logic concealing a bug

ÇåÎúµÄ²âÊÔÔÚ¼ì²éʱͨ³£ÊÇÕýÈ·µÄ;Ò²¾ÍÊÇ˵,ºÜÃ÷ÏÔ,½ö½ö´ÓƳһÑ۾ͿÉÒÔ¿´³ö²âÊÔÊÇÕýÈ·µÄ¡£ÕâÔÚ²âÊÔ´úÂëÖÐÊÇ¿ÉÄܵÄ,ÒòΪÿ¸ö²âÊÔÖ»ÐèÒª´¦ÀíÒ»×éÌض¨µÄÊäÈë,¶øÉú²ú´úÂë±ØÐëͨÓÃÒÔ´¦ÀíÈκÎÊäÈë¡£¶ÔÓÚ²úÆ·´úÂë,ÎÒÃÇÄܹ»±àдȷ±£¸´ÔÓÂß¼­ÕýÈ·µÄ²âÊÔ¡£µ«ÊDzâÊÔ´úÂëûÓÐÕâÖÖÑϸñµÄÒªÇ󡪡ªÈç¹ûÄú¾õµÃÐèÒª±àдһ¸ö²âÊÔÀ´ÑéÖ¤ÄúµÄ²âÊÔ,ÄÇôһ¶¨ÊdzöÁËÎÊÌâ

¸´ÔÓÐÔͨ³£ÒÔÂß¼­µÄÐÎʽ³öÏÖ¡£Âß¼­ÊÇͨ¹ý±à³ÌÓïÑÔµÄÃüÁîʽ²¿·ÖÀ´¶¨ÒåµÄ,±ÈÈçÔËËã·û¡¢Ñ­»·ºÍÌõ¼þ¡£µ±Ò»¶Î´úÂë°üº¬Âß¼­Ê±,ÄãÐèÒª×öһЩÐÄÀí¼ÆËãÀ´È·¶¨ËüµÄ½á¹û,¶ø²»Êǽö½ö´ÓÆÁÄ»ÉÏÔĶÁËü¡£²»ÐèҪ̫¶àµÄÂß¼­¾Í¿ÉÒÔʹ²âÊÔ±äµÃ¸ü¼ÓÄÑÒÔÍÆÀí¡£ÀýÈç,Àý12-15ÖеIJâÊÔ¿´ÆðÀ´ÕýÈ·Âð?
Àý12-15. ͨ¹ýÂß¼­Òþ²Øbug

@Test
public void shouldNavigateToAlbumsPage() {
 String baseUrl = "http://photos.google.com/";
 Navigator nav = new Navigator(baseUrl);
 nav.goToAlbumPage();
 assertThat(nav.getCurrentUrl()).isEqualTo(baseUrl + "/albums");
}
origin
There¡¯s not much logic here: really just one string concatenation. But if we simplify the test by removing that one bit of logic, a bug immediately becomes clear, as demonstrated in Example 12-16.

Example 12-16. A test without logic reveals the bug

ÕâÀïûÓÐÌ«¶àµÄÂß¼­:ʵ¼ÊÉÏÖ»ÊÇÒ»¸ö×Ö·û´®Á¬½Ó¡£µ«ÊÇÈç¹ûÎÒÃÇͨ¹ýɾ³ýÕâÒ»µãÂß¼­À´¼ò»¯²âÊÔ,Ò»¸ö´íÎó¾Í»áÁ¢¿ÌÇåÎúÆðÀ´,ÈçÀý12-16Ëùʾ¡£

Àý12-16¡£Ã»ÓÐÂß¼­µÄ²âÊÔ»á½Òʾ´íÎó

@Test
public void shouldNavigateToPhotosPage() {
 Navigator nav = new Navigator("http://photos.google.com/");
 nav.goToPhotosPage();
 assertThat(nav.getCurrentUrl()))
 .isEqualTo("http://photos.google.com//albums"); // Oops!
}
origin
When the whole string is written out, we can see right away that we¡¯re expecting two slashes in the URL instead of just one. If the production code made a similar mistake, this test would fail to detect a bug. Duplicating the base URL was a small price to pay for making the test more descriptive and meaningful (see the discussion of DAMP versus DRY tests later in this chapter).

If humans are bad at spotting bugs from string concatenation, we¡¯re even worse at spotting bugs that come from more sophisticated programming constructs like loops and conditionals. The lesson is clear: in test code, stick to straight-line code over clever logic, and consider tolerating some duplication when it makes the test more descriptive and meaningful. We¡¯ll discuss ideas around duplication and code sharing later in this chapter.

µ±Ð´³öÕû¸ö×Ö·û´®Ê±,ÎÒÃÇ¿ÉÒÔÂíÉÏ¿´µ½URLÖÐÐèÒªÁ½¸öб¸Ü¶ø²»ÊÇÒ»¸ö¡£Èç¹û²úÆ·´úÂë·¸ÁËÀàËƵĴíÎó,Õâ¸ö²âÊÔ½«ÎÞ·¨¼ì²âµ½´íÎó¡£ÎªÁËʹ²âÊÔ¸ü¾ßÃèÊöÐԺ͸üÓÐÒâÒå(Çë²ÎÔı¾ÕºóÃæ¹ØÓÚDAMPºÍDRY²âÊÔµÄÌÖÂÛ),¸´ÖÆ»ù±¾URLÊÇÒ»¸öºÜСµÄ´ú¼Û¡£

Èç¹ûÈËÀ಻ÉÆÓÚ´Ó×Ö·û´®Á¬½ÓÖз¢ÏÖ´íÎó,ÄÇôÎÒÃǸü²»ÉÆÓÚ·¢ÏÖÀ´×Ô¸ü¸´Ôӵıà³Ì½á¹¹(ÈçÑ­»·ºÍÌõ¼þ)µÄ´íÎó¡£½ÌѵºÜÇå³þ:ÔÚ²âÊÔ´úÂëÖÐ,¼á³ÖÖ±Ïß´úÂë¶ø²»ÊÇ´ÏÃ÷µÄÂß¼­,²¢¿¼ÂÇÈÝÈÌһЩÖظ´,ÒÔʹ²âÊÔ¸ü¾ßÃèÊöÐԺ͸üÓÐÒâÒå¡£ÎÒÃǽ«ÔÚ±¾ÕºóÃæÌÖÂÛ¹ØÓÚ¸´ÖƺʹúÂë¹²ÏíµÄÏë·¨¡£

±àдÇåÎúµÄʧ°ÜÏûÏ¢ Write Clear Failure Messages

origin

One last aspect of clarity has to do not with how a test is written, but with what an engineer sees when it fails. In an ideal world, an engineer could diagnose a problem just from reading its failure message in a log or report without ever having to look at the test itself. A good failure message contains much the same information as the test¡¯s name: it should clearly express the desired outcome, the actual outcome, and any relevant parameters.

Here¡¯s an example of a bad failure message:
Test failed: account is closed
Did the test fail because the account was closed, or was the account expected to be closed and the test failed because it wasn¡¯t? A better failure message clearly distinguishes the expected from the actual state and gives more context about the result:

×îºóÒ»¸öÇåÎúµÄ·½ÃæÓëÈçºÎ±àд²âÊÔÎÞ¹Ø,¶øÊÇÓ빤³ÌʦÔÚ²âÊÔʧ°Üʱ¿´µ½µÄÇé¿öÓйء£ÔÚÀíÏëµÄÇé¿öÏÂ,¹¤³Ìʦ¿ÉÒÔͨ¹ýÔĶÁÈÕÖ¾»ò±¨¸æÖеÄʧ°ÜÏûÏ¢À´Õï¶ÏÎÊÌâ,¶ø²»±Ø²é¿´²âÊÔ±¾Éí¡£Ò»¸öºÃµÄʧ°ÜÏûÏ¢°üº¬Óë²âÊÔÃû³ÆÏàͬµÄÐÅÏ¢:ËüÓ¦¸ÃÇå³þµØ±í´ïÆÚÍûµÄ½á¹û¡¢Êµ¼ÊµÄ½á¹ûºÍÈκÎÏà¹ØµÄ²ÎÊý¡£

ÏÂÃæÊÇÒ»¸öʧ°ÜÏûÏ¢µÄÀý×Ó:
Test failed: account is closed

origin
Did the test fail because the account was closed, or was the account expected to be closed and the test failed because it wasn¡¯t? A better failure message clearly distinguishes the expected from the actual state and gives more context about the result:
²âÊÔʧ°ÜÊÇÒòΪ¹Ø±ÕÁËÕÊ»§,»¹ÊÇÆÚÍû¹Ø±ÕÕÊ»§¶ø²âÊÔʧ°ÜÊÇÒòΪûÓйرÕÕÊ»§?Ò»¸ö¸üºÃµÄʧ°ÜÏûÏ¢¿ÉÒÔÇå³þµØÇø·ÖÔ¤ÆÚ״̬ºÍʵ¼Ê״̬,²¢¸ø³ö¹ØÓÚ½á¹ûµÄ¸ü¶àÉÏÏÂÎÄ: ``` Expected an account in state CLOSED, but got account: <{name: "my-account", state: "OPEN"} ``` origin
Good libraries can help make it easier to write useful failure messages. Consider the assertions in Example 12-17 in a Java test, the first of which uses classical JUnit asserts, and the second of which uses Truth, an assertion library developed by Google:

Example 12-17. An assertion using the Truth library

ºÃµÄ¿â¿ÉÒÔ°ïÖú±àдÓÐÓõÄʧ°ÜÏûÏ¢¡£¿¼ÂÇJava²âÊÔÖеÄʾÀý12-17ÖеĶÏÑÔ,µÚÒ»¸öʹÓÃÁ˾­µäµÄJUnit¶ÏÑÔ,µÚ¶þ¸öʹÓÃÁËÓɹȸ迪·¢µÄ¶ÏÑÔ¿âTruth:

Àý12 - 17¡£Ê¹ÓÃTruth¿âµÄ¶ÏÑÔ

Set<String> colors = ImmutableSet.of("red", "green", "blue");
assertTrue(colors.contains("orange")); // JUnit
assertThat(colors).contains("orange"); // Truth
origin
Because the first assertion only receives a Boolean value, it is only able to give a generic error message like ¡°expected but was ,¡± which isn¡¯t very informative in a failing test output. Because the second assertion explicitly receives the subject of the assertion, it is able to give a much more useful error message: AssertionError: should have contained .¡±

Not all languages have such helpers available, but it should always be possible to manually specify the important information in the failure message. For example, test assertions in Go conventionally look like Example 12-18.

Example 12-18. A test assertion in Go

ÒòΪµÚÒ»¸ö¶ÏÑÔÖ»½ÓÊÕÒ»¸ö²¼¶ûÖµ,ËùÒÔËüÖ»Äܸø³öÒ»¸ö·ºÐ͵ĴíÎóÏûÏ¢,Èç¡°expected but was¡±,ÕâÔÚʧ°ÜµÄ²âÊÔÊä³öÖв»ÊǺÜÓÐÓá£ÒòΪµÚ¶þ¸ö¶ÏÑÔÏÔʽµØ½ÓÊÕ¶ÏÑÔµÄÖ÷Óï,ËùÒÔËüÄܹ»¸ø³öÒ»¸ö¸üÓÐÓõĴíÎóÏûÏ¢:AssertionError: should have contains¡£¡±

²¢²»ÊÇËùÓеÄÓïÑÔ¶¼ÓÐÕâÑùµÄÖúÊÖ,µ«ÊÇÓ¦¸Ã×ÜÊÇ¿ÉÒÔÔÚʧ°ÜÏûÏ¢ÖÐÊÖ¶¯Ö¸¶¨ÖØÒªÐÅÏ¢¡£ÀýÈç,GoÖеIJâÊÔ¶ÏÑÔͨ³£¿´ÆðÀ´ÏñÀý12-18¡£

Àý12-18¡£GoÖеIJâÊÔ¶ÏÑÔ

result := Add(2, 3)
if result != 5 {
 t.Errorf("Add(2, 3) = %v, want %v", result, 5)
}

²âÊԺʹúÂë¹²Ïí:DAMP,¶ø²»ÊÇDRY Tests and Code Sharing: DAMP, Not DRY

origin
One final aspect of writing clear tests and avoiding brittleness has to do with code sharing. Most software attempts to achieve a principle called DRY¡ª¡°Don¡¯t Repeat Yourself.¡± DRY states that software is easier to maintain if every concept is canonically represented in one place and code duplication is kept to a minimum. This approach is especially valuable in making changes easier because an engineer needs to update only one piece of code rather than tracking down multiple references. The downside to such consolidation is that it can make code unclear, requiring readers to follow chains of references to understand what the code is doing.

In normal production code, that downside is usually a small price to pay for making code easier to change and work with. But this cost/benefit analysis plays out a little differently in the context of test code. Good tests are designed to be stable, and in fact you usually want them to break when the system being tested changes. So DRY doesn¡¯t have quite as much benefit when it comes to test code. At the same time, the costs of complexity are greater for tests: production code has the benefit of a test suite to ensure that it keeps working as it becomes complex, whereas tests must stand by themselves, risking bugs if they aren¡¯t self-evidently correct. As mentioned earlier, something has gone wrong if tests start becoming complex enough that it feels like they need their own tests to ensure that they¡¯re working properly.

±àдÇåÎúµÄ²âÊԺͱÜÃâ´àÈõÐÔµÄ×îºóÒ»¸ö·½ÃæÓë´úÂë¹²ÏíÓйء£´ó¶àÊýÈí¼þ¶¼ÊÔͼʵÏÖÒ»¸ö½Ð×öDRYµÄÔ­Ôò¡ª¡ª¡°²»ÒªÖظ´×Ô¼º¡±¡£DRY±íʾ,Èç¹ûÿ¸ö¸ÅÄ±»¹æ·¶»¯µØ±íʾÔÚÒ»¸öµØ·½,²¢ÇÒ½«´úÂ븴ÖƱ£³ÖÔÚ×îµÍÏÞ¶È,ÄÇôÈí¼þ¾Í¸üÈÝÒ×ά»¤¡£ÕâÖÖ·½·¨ÔÚ¼ò»¯¸ü¸Ä·½ÃæÌرðÓмÛÖµ,ÒòΪ¹¤³ÌʦֻÐèÒª¸üÐÂÒ»¶Î´úÂë,¶ø²»ÐèÒª¸ú×Ù¶à¸öÒýÓá£ÕâÖֺϲ¢µÄȱµãÊÇ,Ëü»áʹ´úÂë²»ÇåÎú,ÒªÇó¶ÁÕß×ñÑ­ÒýÓÃÁ´À´Àí½â´úÂëÕýÔÚ×öʲô¡£

ÔÚÕý³£µÄ²úÆ·´úÂëÖÐ,ΪÁËʹ´úÂë¸üÈÝÒ׸ü¸ÄºÍʹÓÃ,ÕâÖÖȱµãͨ³£ÊÇÒ»¸öºÜСµÄ´ú¼Û¡£µ«ÊÇÕâÖֳɱ¾/ÊÕÒæ·ÖÎöÔÚ²âÊÔ´úÂëµÄÉÏÏÂÎÄÖбíÏÖµÃÓе㲻ͬ¡£ºÃµÄ²âÊÔ±»Éè¼Æ³ÉÊÇÎȶ¨µÄ,ÊÂʵÉÏ,µ±±»²âÊÔµÄϵͳ·¢Éú±ä»¯Ê±,Äúͨ³£Ï£ÍûËüÃDZ»ÆÆ»µ¡£ËùÒÔµ±Éæ¼°µ½²âÊÔ´úÂëʱ,DRY²¢Ã»ÓÐÄÇô¶àµÄºÃ´¦¡£Í¬Ê±,¶ÔÓÚ²âÊÔÀ´Ëµ,¸´ÔÓÐԵĴú¼Û¸ü´ó:²úÆ·´úÂëÓÐÒ»¸ö²âÊÔÌ×¼þµÄºÃ´¦,¿ÉÒÔÈ·±£ËüÔÚ±äµÃ¸´ÔÓʱ¼ÌÐø¹¤×÷,¶ø²âÊÔ±ØÐë¶ÀÁ¢ÔËÐÐ,Èç¹ûËüÃDz»ÊÇ×ÔÃ÷ÎÞÎóµÄ»°,¾ÍÓгö´íµÄ·çÏÕ¡£ÕýÈçÇ°ÃæÌáµ½µÄ,Èç¹û²âÊÔ¿ªÊ¼±äµÃ·Ç³£¸´ÔÓ,ÒÔÖÁÓڸоõÐèÒª×Ô¼ºµÄ²âÊÔÀ´È·±£ËüÃÇÕý³£¹¤×÷,ÄÇô¾Í»á³öÏÖÎÊÌâ¡£

origin

Instead of being completely DRY, test code should often strive to be DAMP¡ªthat is, to promote ¡°Descriptive And Meaningful Phrases.¡± A little bit of duplication is OK in tests so long as that duplication makes the test simpler and clearer. To illustrate, Example 12-19 presents some tests that are far too DRY.

Example 12-19. A test that is too DRY

²âÊÔ´úÂë²»Ó¦¸ÃÊÇÍêÈ«DRYµÄ,¶øÓ¦¸ÃŬÁ¦×öµ½damp¡ª¡ªÒ²¾ÍÊÇ˵,Ìᳫ¡°ÃèÊöÐÔºÍÓÐÒâÒåµÄ¶ÌÓ¡£ÔÚ²âÊÔÖÐÓÐÒ»µãÖظ´ÊÇ¿ÉÒÔµÄ,Ö»ÒªÖظ´¿ÉÒÔʹ²âÊÔ¸ü¼òµ¥¡¢¸üÇåÎú¡£¾ÙÀý˵Ã÷,Àý12-19¸ø³öÁËһЩ·Ç³£DRYµÄ²âÊÔ¡£

ʾÀý-12-19¡£Ì«DRYµÄ²âÊÔ

@Test
public void shouldAllowMultipleUsers() {
 List<User> users = createUsers(false, false);
 Forum forum = createForumAndRegisterUsers(users);
 validateForumAndUsers(forum, users);
}
@Test
public void shouldNotAllowBannedUsers() {
 List<User> users = createUsers(true);
 Forum forum = createForumAndRegisterUsers(users);
 validateForumAndUsers(forum, users);
}
// Lots more tests...
private static List<User> createUsers(boolean... banned) {
 List<User> users = new ArrayList<>();
 for (boolean isBanned : banned) {
 users.add(newUser()
 	.setState(isBanned ? State.BANNED : State.NORMAL)
 	.build());
 }
 return users;
}
private static Forum createForumAndRegisterUsers(List<User> users) {
 Forum forum = new Forum();
  for (User user : users) {
 	try {
 		forum.register(user);
 	} catch(BannedUserException ignored) {}
 }
 return forum;
}
private static void validateForumAndUsers(Forum forum, List<User> users) {
 assertThat(forum.isReachable()).isTrue();
 for (User user : users) {
 	assertThat(forum.hasRegisteredUser(user)).isEqualTo(user.getState() == State.BANNED);
 }
}
origin
The problems in this code should be apparent based on the previous discussion of clarity. For one, although the test bodies are very concise, they are not complete: important details are hidden away in helper methods that the reader can¡¯t see without having to scroll to a completely different part of the file. Those helpers are also full of logic that makes them more difficult to verify at a glance (did you spot the bug?). The test becomes much clearer when it¡¯s rewritten to use DAMP, as shown in Example 12-20.

Example 12-20. Tests should be DAMP

¸ù¾ÝÇ°Ãæ¹ØÓÚÇåÎú¶ÈµÄÌÖÂÛ,Õâ¶Î´úÂëÖеÄÎÊÌâÓ¦¸ÃÊǺÜÃ÷ÏԵġ£Ê×ÏÈ,¾¡¹Ü²âÊÔÌå·Ç³£¼ò½à,µ«ËüÃDz¢²»ÍêÕû:ÖØÒªµÄϸ½ÚÒþ²ØÔÚhelper·½·¨ÖÐ,Èç¹û²»¹ö¶¯µ½ÎļþµÄÍêÈ«²»Í¬µÄ²¿·Ö,¶ÁÕß¾ÍÎÞ·¨¿´µ½ÕâЩ·½·¨¡£ÕâЩÖúÊÖ»¹³äÂúÁËÂß¼­,ʹËüÃǸüÄÑÒÔÒ»ÑÛÑéÖ¤(Äú·¢ÏÖ´íÎóÁËÂð?)ÈçÀý12-20Ëùʾ,µ±Ëü±»ÖØдΪʹÓÃDAMPʱ,²âÊÔ±äµÃ¸ü¼ÓÇåÎú¡£

ʾÀý12-20¡£²âÊÔÓ¦²ÉÓÃDAMP

@Test
public void shouldAllowMultipleUsers() {
 User user1 = newUser().setState(State.NORMAL).build();
 User user2 = newUser().setState(State.NORMAL).build();
 Forum forum = new Forum();
 forum.register(user1);
 forum.register(user2);
 assertThat(forum.hasRegisteredUser(user1)).isTrue();
 assertThat(forum.hasRegisteredUser(user2)).isTrue();
}
@Test
public void shouldNotRegisterBannedUsers() {
 User user = newUser().setState(State.BANNED).build();
 Forum forum = new Forum();
 try {
 forum.register(user);
 } catch(BannedUserException ignored) {}
 assertThat(forum.hasRegisteredUser(user)).isFalse();
}
origin
These tests have more duplication, and the test bodies are a bit longer, but the extra verbosity is worth it. Each individual test is far more meaningful and can be understood entirely without leaving the test body. A reader of these tests can feel confident that the tests do what they claim to do and aren¡¯t hiding any bugs.

DAMP is not a replacement for DRY; it is complementary to it. Helper methods and test infrastructure can still help make tests clearer by making them more concise, factoring out repetitive steps whose details aren¡¯t relevant to the particular behavior being tested. The important point is that such refactoring should be done with an eye toward making tests more descriptive and meaningful, and not solely in the name of reducing repetition. The rest of this section will explore common patterns for sharing code across tests.

ÕâЩ²âÊÔÓиü¶àµÄÖظ´,²¢ÇÒ²âÊÔÖ÷ÌåÉÔ΢³¤Ò»Ð©,µ«ÊǶîÍâµÄÈß³¤ÊÇÖµµÃµÄ¡£Ã¿¸öµ¥¶ÀµÄ²âÊÔ¶¼¸üÓÐÒâÒå,²¢ÇÒÎÞÐèÀ뿪²âÊÔÌå¾Í¿ÉÒÔÍêÈ«Àí½â¡£ÔĶÁÕâЩ²âÊԵĶÁÕß¿ÉÒÔÈ·ÐÅÕâЩ²âÊÔÄܹ»Íê³ÉËüÃÇËùÉù³ÆµÄ¹¤×÷,²¢ÇÒûÓÐÒþ²ØÈκÎbug¡£

DAMP²»ÊÇDRYµÄÌæ´úÆ·;ËüÊǶÔËüµÄ²¹³ä¡£Helper·½·¨ºÍ²âÊÔ»ù´¡½á¹¹ÈÔÈ»¿ÉÒÔͨ¹ý¼ò½à»¯Ê¹²âÊÔ¸üÇåÎú,ÅųýÄÇЩϸ½ÚÓë±»²âÊÔµÄÌض¨ÐÐΪÎ޹صÄÖظ´²½Öè,´Ó¶øʹ²âÊÔ¸üÇåÎú¡£ÖØÒªµÄÒ»µãÊÇ,ÕâÑùµÄÖع¹Ó¦¸Ã×ÅÑÛÓÚʹ²âÊÔ¸ü¾ßÃèÊöÐԺ͸üÓÐÒâÒå,¶ø²»ÊÇһζµÄ¼õÉÙÖظ´¡£±¾½ÚµÄÆäÓಿ·Ö½«Ì½ÌÖ¿ç²âÊÔ¹²Ïí´úÂëµÄͨÓÃģʽ¡£

¹²ÏíÖµ Shared Values

origin
Many tests are structured by defining a set of shared values to be used by tests and then by defining the tests that cover various cases for how these values interact. Example 12-21 illustrates what such tests look like.

Example 12-21. Shared values with ambiguous names

Ðí¶à²âÊÔ¶¼ÊÇͨ¹ý¶¨ÒåÒ»×éÓÃÓÚ²âÊԵĹ²ÏíÖµ,È»ºó¶¨Ò帲¸ÇÕâЩֵÈçºÎ½»»¥µÄ¸÷ÖÖÇé¿öµÄ²âÊÔÀ´¹¹½¨µÄ¡£Àý12-21˵Ã÷ÁËÕâÑùµÄ²âÊÔÊÇʲôÑùµÄ¡£

ʾÀý12-21¡£ÃüÃû²»Ã÷È·µÄ¹²ÏíÖµ

private static final Account ACCOUNT_1 = Account.newBuilder()
 .setState(AccountState.OPEN).setBalance(50).build();
private static final Account ACCOUNT_2 = Account.newBuilder()
 .setState(AccountState.CLOSED).setBalance(0).build();
private static final Item ITEM = Item.newBuilder()
 .setName("Cheeseburger").setPrice(100).build();
// Hundreds of lines of other tests...
@Test
public void canBuyItem_returnsFalseForClosedAccounts() {
 assertThat(store.canBuyItem(ITEM, ACCOUNT_1)).isFalse();
}
@Test
public void canBuyItem_returnsFalseWhenBalanceInsufficient() {
 assertThat(store.canBuyItem(ITEM, ACCOUNT_2)).isFalse();
}
origin

This strategy can make tests very concise, but it causes problems as the test suite grows. For one, it can be difficult to understand why a particular value was chosen for a test. In Example 12-21, the test names fortunately clarify which scenarios are being tested, but you still need to scroll up to the definitions to confirm that ACCOUNT_1 and ACCOUNT_2 are appropriate for those scenarios. More descriptive constant names (e.g.,CLOSED_ACCOUNT and ACCOUNT_WITH_LOW_BALANCE) help a bit, but they still make it more difficult to see the exact details of the value being tested, and the ease of reusing these values can encourage engineers to do so even when the name doesn¡¯t exactly describe what the test needs.

Engineers are usually drawn to using shared constants because constructing individual values in each test can be verbose. A better way to accomplish this goal is to construct data using helper methods (see Example 12-22) that require the test author to specify only values they care about, and setting reasonable defaults7 for all other values. This construction is trivial to do in languages that support named parameters, but languages without named parameters can use constructs such as the Builder pattern to emulate them (often with the assistance of tools such as AutoValue):

Example 12-22. Shared values using helper methods

ÕâÖÖ²ßÂÔ¿ÉÒÔʹ²âÊԷdz£¼ò½à,µ«Ëæ×ŲâÊÔÌ×¼þµÄÔö³¤,Ëü»áµ¼ÖÂÎÊÌâ¡£Ê×ÏÈ,ºÜÄÑÀí½âΪʲôҪΪ²âÊÔÑ¡ÔñÒ»¸öÌض¨µÄÖµ¡£ÔÚʾÀý12-21ÖÐ,²âÊÔÃû³ÆÐÒÔ˵سÎÇåÁËÕýÔÚ²âÊԵij¡¾°,µ«ÊÇÄúÈÔÈ»ÐèÒªÏòÉϹö¶¯µ½¶¨Òå,ÒÔÈ·ÈÏACCOUNT_1ºÍACCOUNT_2ÊÊÓÃÓÚÕâЩ³¡¾°¡£¸ü¾ßÃèÊöÐԵij£ÊýÃû³Æ(ÀýÈç,CLOSED_ACCOUNTºÍACCOUNT_WITH_LOW_BALANCE)ÓÐËù°ïÖú,µ«ËûÃÇÈÔȻʹÆä¸üÄÑÒÔ¿´µ½µÄ¾ßÌåϸ½ÚÕýÔÚ²âÊԵļÛÖµ,ºÍÒ×ÓÚÖØÓÃÕâЩֵ¿ÉÒÔ¹ÄÀø¹¤³ÌʦÕâÑù×ö¼´Ê¹Ãû³Æ²»È·ÇÐÃèÊö²âÊÔÐèÇó¡£

¹¤³Ìʦͨ³£ÇãÏòÓÚʹÓù²Ïí³£Á¿,ÒòΪÔÚÿ¸ö²âÊÔÖй¹Ôìµ¥¶ÀµÄÖµ¿ÉÄܺÜÈß³¤¡£ÊµÏÖÕâһĿ±êµÄ¸üºÃ·½·¨ÊÇʹÓðïÖú·½·¨(²Î¼ûʾÀý12-22)À´¹¹ÔìÊý¾Ý,ÕâЩ·½·¨ÒªÇó²âÊÔ×÷ÕßÖ»Ö¸¶¨ËûÃǹØÐĵÄÖµ,²¢ÎªËùÓÐÆäËûÖµÉèÖúÏÀíµÄĬÈÏÖµ7¡£ÕâÖÖ¹¹ÔìÔÚÖ§³ÖÃüÃû²ÎÊýµÄÓïÑÔÖÐÊǺܼòµ¥µÄ,µ«ÊDz»Ö§³ÖÃüÃû²ÎÊýµÄÓïÑÔ¿ÉÒÔʹÓÃÖîÈçBuilderģʽ֮ÀàµÄ¹¹ÔìÀ´Ä£ÄâËüÃÇ(ͨ³£½èÖúAutoValueµÈ¹¤¾ß):

Àý12-22.ʹÓÃhelper·½·¨¹²ÏíÖµ

# A helper method wraps a constructor by defining arbitrary defaults for
# each of its parameters.
def newContact(
 firstName="Grace", lastName="Hopper", phoneNumber="555-123-4567"):
 return Contact(firstName, lastName, phoneNumber)
# Tests call the helper, specifying values for only the parameters that they
# care about.
def test_fullNameShouldCombineFirstAndLastNames(self):
 def contact = newContact(firstName="Ada", lastName="Lovelace")
 self.assertEqual(contact.fullName(), "Ada Lovelace")
// Languages like Java that don¡¯t support named parameters can emulate them
// by returning a mutable "builder" object that represents the value under
// construction.
private static Contact.Builder newContact() {
 return Contact.newBuilder()
 .setFirstName("Grace")
 .setLastName("Hopper")
 .setPhoneNumber("555-123-4567");
}
// Tests then call methods on the builder to overwrite only the parameters
// that they care about, then call build() to get a real value out of the
// builder.
@Test
public void fullNameShouldCombineFirstAndLastNames() {
 Contact contact = newContact()
 .setFirstName("Ada")
 .setLastName("Lovelace")
 .build();
 assertThat(contact.getFullName()).isEqualTo("Ada Lovelace");
}
origin
Using helper methods to construct these values allows each test to create the exact values it needs without having to worry about specifying irrelevant information or conflicting with other tests.

ʹÓðïÖúÆ÷·½·¨À´¹¹ÔìÕâЩֵÔÊÐíÿ¸ö²âÊÔ´´½¨ËüÐèÒªµÄÈ·ÇÐÖµ,¶ø²»±Øµ£ÐÄÖ¸¶¨²»Ïà¹ØµÄÐÅÏ¢»òÓëÆäËû²âÊÔ³åÍ»¡£

¹²ÏíÉèÖà Shared Setup

origin
A related way that tests shared code is via setup/initialization logic. Many test frameworks allow engineers to define methods to execute before each test in a suite is run. Used appropriately, these methods can make tests clearer and more concise by obviating the repetition of tedious and irrelevant initialization logic. Used inappropriately, these methods can harm a test¡¯s completeness by hiding important details in a separate initialization method.

The best use case for setup methods is to construct the object under tests and its collaborators. This is useful when the majority of tests don¡¯t care about the specific arguments used to construct those objects and can let them stay in their default states. The same idea also applies to stubbing return values for test doubles, which is a concept that we explore in more detail in Chapter 13.

²âÊÔ¹²Ïí´úÂëµÄÒ»¸öÏà¹Ø·½·¨ÊÇͨ¹ýÉèÖÃ/³õʼ»¯Âß¼­¡£Ðí¶à²âÊÔ¿ò¼ÜÔÊÐí¹¤³ÌʦÔÚÔËÐÐÌ×¼þÖеÄÿ¸ö²âÊÔ֮ǰ¶¨ÒåÒªÖ´Ðеķ½·¨¡£Èç¹ûʹÓõõ±,ÕâЩ·½·¨¿ÉÒÔ±ÜÃâÖظ´·±ËöºÍÎ޹صijõʼ»¯Âß¼­,´Ó¶øʹ²âÊÔ¸ü¼ÓÇåÎúºÍ¼ò½à¡£Èç¹ûʹÓò»µ±,ÕâЩ·½·¨»áÔÚµ¥¶ÀµÄ³õʼ»¯·½·¨ÖÐÒþ²ØÖØÒªµÄϸ½Ú,´Ó¶øË𺦲âÊÔµÄÍêÕûÐÔ¡£

setup·½·¨µÄ×î¼ÑÓÃÀýÊÇÔÚ²âÊÔºÍËüµÄºÏ×÷ÕßϹ¹Ôì¶ÔÏó¡£µ±´ó¶àÊý²âÊÔ²»¹ØÐÄÓÃÓÚ¹¹ÔìÕâЩ¶ÔÏóµÄÌض¨²ÎÊý,²¢ÇÒ¿ÉÒÔÈÃËüÃDZ£³ÖĬÈÏ״̬ʱ,ÕâÊǺÜÓÐÓõġ£Í¬ÑùµÄ˼ÏëÒ²ÊÊÓÃÓÚ²âÊÔË«¾«¶È¸¡µãÊýµÄ´æ¸ù·µ»ØÖµ,Õâ¸ö¸ÅÄîÎÒÃǽ«ÔÚµÚ13ÕÂÖÐÏêϸÌÖÂÛ¡£

origin
One risk in using setup methods is that they can lead to unclear tests if those tests begin to depend on the particular values used in setup. For example, the test in Example 12-23 seems incomplete because a reader of the test needs to go hunting to discover where the string ¡°Donald Knuth¡± came from.

Example 12-23. Dependencies on values in setup methods

ʹÓÃsetup·½·¨µÄÒ»¸ö·çÏÕÊÇ,Èç¹ûÕâЩ²âÊÔ¿ªÊ¼ÒÀÀµÓÚsetupÖÐʹÓõÄÌض¨Öµ,Ôò¿ÉÄܵ¼Ö²»Çå³þµÄ²âÊÔ¡£ÀýÈç,ʾÀý12-23ÖеIJâÊÔËƺõ²»ÍêÕû,ÒòΪ²âÊÔͬѧÐèÒª²éÕÒ×Ö·û´®¡°Donald Knuth¡±À´×ÔÄÄÀï¡£

Àý12-23¡£¶Ôsetup·½·¨ÖеÄÖµµÄÒÀÀµ¹Øϵ

private NameService nameService;
private UserStore userStore;
@Before
public void setUp() {
 nameService = new NameService();
 nameService.set("user1", "Donald Knuth");
 userStore = new UserStore(nameService);
}
// [... hundreds of lines of tests ...]
@Test
public void shouldReturnNameFromService() {
 UserDetails user = userStore.get("user1");
 assertThat(user.getName()).isEqualTo("Donald Knuth");
}
origin
Tests like these that explicitly care about particular values should state those values directly, overriding the default defined in the setup method if need be. The resulting test contains slightly more repetition, as shown in Example 12-24, but the result is far more descriptive and meaningful.

Example 12-24. Overriding values in setup mMethods

ÏñÕâÑùÃ÷È·¹ØÐÄÌض¨ÖµµÄ²âÊÔÓ¦¸ÃÖ±½ÓÉùÃ÷ÕâЩֵ,Èç¹ûÐèÒªÖØдÔÚsetup·½·¨Öж¨ÒåµÄĬÈÏÖµ¡£½á¹û²âÊÔ°üº¬Á˸ü¶àµÄÖظ´,ÈçÀý12-24Ëùʾ,µ«Êǽá¹û¸ü¾ßÃèÊöÐÔ,Ò²¸üÓÐÒâÒå¡£

Àý12 - 24¡£ÖØдsetup mMethodsÖеÄÖµ

private NameService nameService;
private UserStore userStore;
@Before
public void setUp() {
 nameService = new NameService();
 nameService.set("user1", "Donald Knuth");
 userStore = new UserStore(nameService);
}
@Test
public void shouldReturnNameFromService() {
 nameService.set("user1", "Margaret Hamilton");
 UserDetails user = userStore.get("user1");
 assertThat(user.getName()).isEqualTo("Margaret Hamilton");
}

¹²ÏíHelperºÍÑéÖ¤ Shared Helpers and Validation

origin
The last common way that code is shared across tests is via ¡°helper methods¡± called from the body of the test methods. We already discussed how helper methods can be a useful way for concisely constructing test values¡ªthis usage is warranted, but other types of helper methods can be dangerous.

One common type of helper is a method that performs a common set of assertions against a system under test. The extreme example is a validate method called at the end of every test method, which performs a set of fixed checks against the system under test. Such a validation strategy can be a bad habit to get into because tests using this approach are less behavior driven. With such tests, it is much more difficult to determine the intent of any particular test and to infer what exact case the author had in mind when writing it. When bugs are introduced, this strategy can also make them more difficult to localize because they will frequently cause a large number of tests to start failing.

¿ç²âÊÔ¹²Ïí´úÂëµÄ×îºóÒ»ÖÖ³£¼û·½Ê½ÊÇͨ¹ý´Ó²âÊÔ·½·¨Ìåµ÷Óá°ÖúÊÖ·½·¨¡±¡£ÎÒÃÇÒѾ­ÌÖÂÛÁËhelper·½·¨ÈçºÎ³ÉΪ¼ò½à¹¹Ôì²âÊÔÖµµÄÓÐÓ÷½·¨¡ªÕâÖÖÓ÷¨ÊÇÓбØÒªµÄ,µ«ÆäËûÀàÐ͵Ähelper·½·¨¿ÉÄܺÜΣÏÕ¡£

Ò»ÖÖ³£¼ûµÄÖúÊÖÀàÐÍÊÇÕë¶Ô±»²âÊÔϵͳִÐÐÒ»×鹫¹²¶ÏÑԵķ½·¨¡£¼«¶ËµÄÀý×ÓÊÇÔÚÿ¸ö²âÊÔ·½·¨½áÊøʱµ÷ÓÃÒ»¸öÑéÖ¤·½·¨,Ëü¶Ô±»²âÊÔµÄϵͳִÐÐÒ»×é¹Ì¶¨µÄ¼ì²é¡£ÕâÑùµÄÑéÖ¤²ßÂÔ¿ÉÄÜÊÇÒ»¸ö»µÏ°¹ß,ÒòΪʹÓÃÕâÖÖ·½·¨µÄ²âÊÔ½ÏÉÙÊÜÐÐΪÇý¶¯¡£ÓÐÁËÕâÑùµÄ²âÊÔ,¾ÍºÜÄÑÈ·¶¨ÈκÎÌض¨²âÊÔµÄÒâͼ,Ò²ºÜÄÑÍƶϳö×÷ÕßÔÚ±àд²âÊÔʱµÄÈ·ÇÐÇé¿ö¡£µ±ÒýÈë´íÎóʱ,ÕâÖÖ²ßÂÔÒ²»áʹ±¾µØ»¯´íÎó±äµÃ¸ü¼ÓÀ§ÄÑ,ÒòΪËüÃǾ­³£»áµ¼Ö´óÁ¿²âÊÔ¿ªÊ¼Ê§°Ü¡£

origin

More focused validation methods can still be useful, however. The best validation helper methods assert a single conceptual fact about their inputs, in contrast to general-purpose validation methods that cover a range of conditions. Such methods can be particularly helpful when the condition that they are validating is conceptually simple but requires looping or conditional logic to implement that would reduce clarity were it included in the body of a test method. For example, the helper method in Example 12-25 might be useful in a test covering several different cases around account access.

Example 12-25. A conceptually simple test

È»¶ø,¸ü¼¯ÖеÄÑéÖ¤·½·¨ÈÔÈ»ÊÇÓÐÓõġ£Ó븲¸ÇһϵÁÐÌõ¼þµÄͨÓÃÑéÖ¤·½·¨²»Í¬,×îºÃµÄÑéÖ¤ÖúÊÖ·½·¨¶ÏÑÔ¹ØÓÚÆäÊäÈëµÄÒ»¸ö¸ÅÄîÐÔÊÂʵ¡£µ±ÒªÑéÖ¤µÄÌõ¼þÔÚ¸ÅÄîÉϺܼòµ¥,µ«ÐèҪѭ»·»òÌõ¼þÂß¼­À´ÊµÏÖ(Èç¹û°üº¬ÔÚ²âÊÔ·½·¨µÄÖ÷ÌåÖÐ,»á½µµÍÇåÎú¶È)ʱ,ÕâÖÖ·½·¨ÓÈÆäÓÐÓá£ÀýÈç,Àý12-25ÖеÄhelper·½·¨¿ÉÄÜÔÚÒ»¸ö²âÊÔÖÐÓÐÓÃ,¸Ã²âÊÔº­¸ÇÁ˹ØÓÚÕÊ»§·ÃÎʵļ¸ÖÖ²»Í¬Çé¿ö¡£

Àý12-25¡£Ò»¸ö¸ÅÄî¼òµ¥µÄ²âÊÔ

private void assertUserHasAccessToAccount(User user, Account account) {
 for (long userId : account.getUsersWithAccess()) {
		if (user.getId() == userId) {
			return;
		}
	}
 fail(user.getName() + " cannot access " + account.getName());
}

¶¨Òå²âÊÔ»ù´¡ÉèÊ© Defining Test Infrastructure

origin
The techniques we¡¯ve discussed so far cover sharing code across methods in a single test class or suite. Sometimes, it can also be valuable to share code across multiple test suites. We refer to this sort of code as test infrastructure. Though it is usually more valuable in integration or end-to-end tests, carefully designed test infrastructure can make unit tests much easier to write in some circumstances.

Custom test infrastructure must be approached more carefully than the code sharing that happens within a single test suite. In many ways, test infrastructure code is more similar to production code than it is to other test code given that it can have many callers that depend on it and can be difficult to change without introducing breakages. Most engineers aren¡¯t expected to make changes to the common test infrastructure while testing their own features. Test infrastructure needs to be treated as its own separate product, and accordingly, test infrastructure must always have its own tests.

Of course, most of the test infrastructure that most engineers use comes in the form of well-known third-party libraries like JUnit. A huge number of such libraries are available, and standardizing on them within an organization should happen as early and universally as possible. For example, Google many years ago mandated Mockito as the only mocking framework that should be used in new Java tests and banned new tests from using other mocking frameworks. This edict produced some grumbling at the time from people comfortable with other frameworks, but today, it¡¯s universally seen as a good move that made our tests easier to understand and work with.

µ½Ä¿Ç°ÎªÖ¹,ÎÒÃÇÌÖÂ۵ļ¼Êõº­¸ÇÁ˵¥¸ö²âÊÔÀà»òÌ×¼þÖз½·¨Ö®¼äµÄ¹²Ïí´úÂë¡£ÓÐʱ,¿ç¶à¸ö²âÊÔÌ×¼þ¹²Ïí´úÂëÒ²ÊÇÓмÛÖµµÄ¡£ÎÒÃǽ«ÕâÀà´úÂë³ÆΪ²âÊÔ»ù´¡ÉèÊ©¡£ËäÈ»Ëüͨ³£ÔÚ¼¯³É»ò¶Ëµ½¶Ë²âÊÔÖиüÓмÛÖµ,µ«¾«ÐÄÉè¼ÆµÄ²âÊÔ»ù´¡ÉèÊ©¿ÉÒÔʹµ¥Ôª²âÊÔÔÚijЩÇé¿öϸüÈÝÒ×±àд¡£

ÓëÔÚµ¥¸ö²âÊÔÌ×¼þÖз¢ÉúµÄ´úÂë¹²ÏíÏà±È,±ØÐë¸ü¼ÓСÐĵش¦Àí¶¨ÖƲâÊÔ»ù´¡ÉèÊ©¡£ÔÚÐí¶à·½Ãæ,²âÊÔ»ù´¡ÉèÊ©´úÂëÓëÉú²ú´úÂë±ÈÓëÆäËû²âÊÔ´úÂë¸üÏàËÆ,ÒòΪËü¿ÉÒÔÓÐÐí¶àÒÀÀµÓÚËüµÄµ÷ÓÃÕß,²¢ÇÒºÜÄÑÔÚ²»ÒýÈëÖжϵÄÇé¿öϽøÐиü¸Ä¡£´ó¶àÊý¹¤³ÌʦÔÚ²âÊÔËûÃÇ×Ô¼ºµÄÌØÐÔʱ²¢²»ÆÚÍû¶Ô¹«¹²²âÊÔ»ù´¡½á¹¹½øÐиü¸Ä¡£²âÊÔ»ù´¡¼Ü¹¹ÐèÒª±»ÊÓΪÆä¶ÀÁ¢µÄ²úÆ·,Òò´Ë,²âÊÔ»ù´¡¼Ü¹¹±ØÐëʼÖÕÓµÓÐ×Ô¼ºµÄ²âÊÔ¡£

µ±È»,´ó¶àÊý¹¤³ÌʦʹÓõĴó¶àÊý²âÊÔ»ù´¡ÉèÊ©¶¼ÊÇÒÔÖÚËùÖÜÖªµÄµÚÈý·½¿â(ÈçJUnit)µÄÐÎʽ³öÏֵġ£ÓдóÁ¿ÕâÑùµÄ¿â¿É¹©Ê¹ÓÃ,ÔÚÒ»¸ö×éÖ¯ÄÚ¶ÔËüÃǽøÐбê×¼»¯Ó¦¸Ã¾¡ÔçÇÒÆÕ±éµØ½øÐС£ÀýÈç,¹È¸è¶àÄêÇ°¾Í¹æ¶¨MockitoÊÇÐÂJava²âÊÔÖÐÓ¦¸ÃʹÓõÄΨһģÄâ¿ò¼Ü,²¢½ûֹвâÊÔʹÓÃÆäËûÄ£Äâ¿ò¼Ü¡£ÕâÒ»¹æ¶¨ÔÚµ±Ê±ÒýÆðÁËÄÇЩÊìϤÆäËû¿ò¼ÜµÄÈ˵ÄһЩ±§Ô¹,µ«ÊÇÔÚ½ñÌì,Ëü±»ÆÕ±éÈÏΪÊÇÒ»¸öºÜºÃµÄ¾Ù´ë,ʹÎÒÃǵIJâÊÔ¸üÈÝÒ×Àí½âºÍʹÓá£

½áÂÛ Conclusion

origin
Unit tests are one of the most powerful tools that we as software engineers have to make sure that our systems keep working over time in the face of unanticipated changes. But with great power comes great responsibility, and careless use of unit testing can result in a system that requires much more effort to maintain and takes much more effort to change without actually improving our confidence in said system. Unit tests at Google are far from perfect, but we¡¯ve found tests that follow the practices outlined in this chapter to be orders of magnitude more valuable than those that don¡¯t. We hope they¡¯ll help you to improve the quality of your own tests!

µ¥Ôª²âÊÔÊÇ×îÇ¿´óµÄ¹¤¾ßÖ®Ò»,×÷ΪÈí¼þ¹¤³Ìʦ,ÎÒÃDZØÐëÈ·±£ÎÒÃǵÄϵͳÔÚÃæ¶Ô²»¿ÉÔ¤Æڵĸü¸ÄʱÄܹ»³ÖÐø¹¤×÷¡£µ«ÊÇ,¹¦ÄÜԽǿ´ó,ÔðÈξÍÔ½´ó,´ÖÐĵÄʹÓõ¥Ôª²âÊÔ¿ÉÄܵ¼ÖÂϵͳÐèÒª¸¶³ö¸ü¶àµÄŬÁ¦À´Î¬»¤ºÍ¸ü¸Ä,¶øʵ¼ÊÉϲ¢Ã»ÓÐÌá¸ßÎÒÃǶԸÃϵͳµÄÐÅÐÄ¡£¹È¸èµÄµ¥Ôª²âÊÔÔ¶·ÇÍêÃÀ,µ«ÎÒÃÇ·¢ÏÖ,×ñÑ­±¾ÕÂÖиÅÊöµÄʵ¼ùµÄ²âÊԱȲ»×ñÑ­ÕâЩʵ¼ùµÄ²âÊÔ¸üÓмÛÖµ¡£ÎÒÃÇÏ£ÍûËüÃÇÄÜ°ïÖúÄúÌá¸ß×Ô¼º²âÊÔµÄÖÊÁ¿!

TL;DRs

origin
  • Strive for unchanging tests.
  • Test via public APIs.
  • Test state, not interactions.
  • Make your tests complete and concise.
  • Test behaviors, not methods.
  • Structure tests to emphasize behaviors.
  • Name tests after the behavior being tested.
  • Don¡¯t put logic in tests.
  • Write clear failure messages.
  • Follow DAMP over DRY when sharing code for tests.
  • ÕùÈ¡²»±äµÄ²âÊÔ¡£
  • ͨ¹ý¹«¹²api½øÐвâÊÔ¡£
  • ²âÊÔ״̬,¶ø²»Êǽ»»¥¡£
  • ÈÃÄãµÄ²âÊÔÍêÕû¶ø¼ò½à¡£
  • ²âÊÔÐÐΪ,¶ø²»ÊÇ·½·¨¡£
  • ½á¹¹²âÊÔ,Ç¿µ÷ÐÐΪ¡£
  • ÔÚ±»²âÊÔµÄÐÐΪ֮ºóÃüÃû²âÊÔ¡£
  • ²»ÒªÔÚ²âÊÔÖÐʹÓÃÂß¼­¡£
  • дÇå³þʧ°ÜÏûÏ¢¡£
  • ÔÚ¹²Ïí²âÊÔ´úÂëʱ×ñÑ­DAMP¶ø²»ÊÇDRY¡£

  1. Note that this is slightly different from a flaky test, which fails nondeterministically without any change to production code. ?? ??

  2. This is sometimes called the "Use the front door first principle.¡± ?? ??

  3. These are also the same two reasons that a test can be ¡°flaky.¡± Either the system under test has a nondeterministic fault, or the test is flawed such that it sometimes fails when it should pass. ?? ??

  4. See https://testing.googleblog.com/2014/04/testing-on-toilet-test-behaviors-not.html and https://dannorth.net/introducing-bdd. ?? ??

  5. Furthermore, a feature (in the product sense of the word) can be expressed as a collection of behaviors ?? ??

  6. These components are sometimes referred to as ¡°arrange,¡± ¡°act,¡± and ¡°assert.¡± ?? ??

  7. In many cases, it can even be useful to slightly randomize the default values returned for fields that aren¡¯t explicitly set. This helps to ensure that two different instances won¡¯t accidentally compare as equal, and makes it more difficult for engineers to hardcode dependencies on the defaults ?? ??

  ¿ª·¢²âÊÔ ×îÐÂÎÄÕÂ
pytestϵÁСª¡ªallureÖ®Éú³É²âÊÔ±¨¸æ£¨Wind
ij´ó³§Èí¼þ²âÊÔ¸ÚÒ»Ãæ±ÊÊÔÌâ+¶þÃæÎÊ´ðÌâÃæÊÔ
iperf ѧϰ±Ê¼Ç
¹ØÓÚPythonÖÐʹÓÃselenium°Ë´ó¶¨Î»·½·¨
¡¾Èí¼þ²âÊÔ¡¿ÎªÊ²Ã´ÌáÉý²»ÁË£¿8Äê²âÊÔ×ܽáÔÙ
Èí¼þ²âÊÔ¸´Ï°
PHP±Ê¼Ç-SmartyÄ£°åÒýÇæµÄʹÓÃ
C++TestʹÓÃÈëÃÅ
¡¾Java¡¿µ¥Ôª²âÊÔ
Net core 3.x »ñÈ¡¿Í»§¶ËµØÖ·
ÉÏһƪÎÄÕ      ÏÂһƪÎÄÕ      ²é¿´ËùÓÐÎÄÕÂ
¼Ó:2022-03-21 21:22:01  ¸ü:2022-03-21 21:22:33 
 
¿ª·¢: C++֪ʶ¿â Java֪ʶ¿â JavaScript Python PHP֪ʶ¿â È˹¤ÖÇÄÜ Çø¿éÁ´ ´óÊý¾Ý Òƶ¯¿ª·¢ ǶÈëʽ ¿ª·¢¹¤¾ß Êý¾Ý½á¹¹ÓëËã·¨ ¿ª·¢²âÊÔ ÓÎÏ·¿ª·¢ ÍøÂçЭÒé ϵͳÔËά
½Ì³Ì: HTML½Ì³Ì CSS½Ì³Ì JavaScript½Ì³Ì GoÓïÑÔ½Ì³Ì JQuery½Ì³Ì VUE½Ì³Ì VUE3½Ì³Ì Bootstrap½Ì³Ì SQLÊý¾Ý¿â½Ì³Ì CÓïÑÔ½Ì³Ì C++½Ì³Ì Java½Ì³Ì Python½Ì³Ì Python3½Ì³Ì C#½Ì³Ì
ÊýÂë: µçÄÔ ±Ê¼Ç±¾ ÏÔ¿¨ ÏÔʾÆ÷ ¹Ì̬ӲÅÌ Ó²ÅÌ ¶ú»ú ÊÖ»ú iphone vivo oppo СÃ× »ªÎª µ¥·´ ×°»ú ͼÀ­¶¡

360ͼÊé¹Ý ¹ºÎï Èý·á¿Æ¼¼ ÔĶÁÍø ÈÕÀú ÍòÄêÀú 2024Äê11ÈÕÀú -2024/11/18 0:17:36-

ͼƬ×Ô¶¯²¥·ÅÆ÷
¡ýͼƬ×Ô¶¯²¥·ÅÆ÷¡ý
TxTС˵ÔĶÁÆ÷
¡ýÓïÒôÔĶÁ,С˵ÏÂÔØ,¹ÅµäÎÄѧ¡ý
Ò»¼üÇå³ýÀ¬»ø
¡ýÇáÇáÒ»µã,Çå³ýϵͳÀ¬»ø¡ý
ͼƬÅúÁ¿ÏÂÔØÆ÷
¡ýÅúÁ¿ÏÂÔØͼƬ,ÃÀŮͼ¿â¡ý
  ÍøÕ¾ÁªÏµ: qq:121756557 email:121756557@qq.com  ITÊýÂë