繼性別和生日以後,最後一個信息塊,只是列出測試以下. git
這是一個比較大的問題. 前面,我臨時性的把不一樣地方的驗證去掉了. 代碼原做者也過來, 暢敘了他關於驗證的見解. 他是對的, 這種徹底驗證的方式,根本上說是 DDD的設計思想。不過,想我所說,我知識臨時性的去掉,保證測試的單元性。驗證的功能,由驗證的測試來驅動。而第二點考慮,個人驗證打算放在構造器中,也就是說,若是,有任何錯誤的輸入,連第一道門都進不來。 github
這裏,測試和實現都很簡單,看起來不少,只是一些羅列,不一樣的錯誤場景而已。 c#
[Subject("身份證,有效性")] public class when_create_social_id_with_valid_format { private Because of = () => subject = new SocialID("430103123456780020"); private It should_create_social_properly = () => subject.getCardNumber().ShouldEqual("430103123456780020"); private static SocialID subject; } [Subject("身份證,有效性")] public class when_create_social_id_with_null_string { private Because of = () =>exception= Catch.Exception(()=>new SocialID(null)); private It should_not_allow_to_create = () =>exception.ShouldNotBeNull(); private static SocialID subject; private static Exception exception; } [Subject("身份證,有效性")] public class when_create_social_id_with_empty_string { private Because of = () => exception = Catch.Exception(() => new SocialID(string.Empty)); private It should_not_allow_to_create = () => exception.ShouldNotBeNull(); private static SocialID subject; private static Exception exception; } [Subject("身份證,有效性")] public class when_create_social_id_with_2_length_string { private Because of = () => exception = Catch.Exception(() => new SocialID("12")); private It should_not_allow_to_create = () => exception.ShouldNotBeNull(); private static SocialID subject; private static Exception exception; } [Subject("身份證,有效性")] public class when_create_social_id_with_20_length_string { private Because of = () => exception = Catch.Exception(() => new SocialID("12345678901234567890")); private It should_not_allow_to_create = () => exception.ShouldNotBeNull(); private static SocialID subject; private static Exception exception; } [Subject("身份證,有效性")] public class when_create_social_id_alphet_length_string { private Because of = () => exception = Catch.Exception(() => new SocialID("A23456789012345678")); private It should_not_allow_to_create = () => exception.ShouldNotBeNull(); private static SocialID subject; private static Exception exception; }實現
public SocialID(String cardNumber) { if (string.IsNullOrEmpty(cardNumber)) throw new ApplicationException("Card Number is empty"); if (cardNumber.Length != CARD_NUMBER_LENGTH) throw new ApplicationException("Card Number Length is wrong."); if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber)) throw new ApplicationException("Card Number has wrong charactor(s)."); }
驗證碼是個特殊的有效性檢查,較爲複雜,我這裏,把這部分邏輯代碼提煉出來成爲一個驗證器。 測試
測試極其簡單,和實現幾乎原封不動。 this
測試: spa
public class when_verify_soical_number:Specification<Verifier> { Because of = () => { code = subject.verify("43010319791211453"); }; private It verify_code_should_match = () => code.ShouldEqual('4'); private static char code; }實現
namespace Skight.eLiteWeb.Domain.Specs.Properties { public class Verifier { private static char[] VERIFY_CODE = { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' }; /** * 18位身份證中,各個數字的生成校驗碼時的權值 */ private static int[] VERIFY_CODE_WEIGHT = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 }; private static int CARD_NUMBER_LENGTH = 18; public char verify(string source) { /** * <li>校驗碼(第十八位數):<br/> * <ul> * <li>十七位數字本體碼加權求和公式 S = Sum(Ai * Wi), i = 0...16 ,先對前17位數字的權求和; * Ai:表示第i位置上的身份證號碼數字值 Wi:表示第i位置上的加權因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 * 2;</li> * <li>計算模 Y = mod(S, 11)</li> * <li>經過模獲得對應的校驗碼 Y: 0 1 2 3 4 5 6 7 8 9 10 校驗碼: 1 0 X 9 8 7 6 5 4 3 2</li> * </ul> * * @param cardNumber * @return */ int sum = 0; for (int i = 0; i < CARD_NUMBER_LENGTH - 1; i++) { char ch = source[i]; sum += ((int) (ch - '0'))*VERIFY_CODE_WEIGHT[i]; } return VERIFY_CODE[sum%11]; } } }這時候,身份證構造器的完整實現就變成了
public SocialID(String cardNumber) { if (string.IsNullOrEmpty(cardNumber)) throw new ApplicationException("Card Number is empty"); if (cardNumber.Length != CARD_NUMBER_LENGTH) throw new ApplicationException("Card Number Length is wrong."); if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber)) throw new ApplicationException("Card Number has wrong charactor(s)."); if (cardNumber[CARD_NUMBER_LENGTH - 1] != verifier.verify(cardNumber)) throw new ApplicationException("Card Number verified code is not match."); this.cardNumber = cardNumber; }至此,代碼已經很乾淨了。 是的,還有進一步的改進,如,3個元素(地區,生日,性別)的提煉應該移到構造器中,各個提取的功能就變成了,簡單的數據讀取。Social 的類型,不是class而是struct,由於這是典型的 Value Object。 另外,我把15轉18位的部分也去掉了,這能夠看做一個Utilit,能夠在外部作,不是核心功能。
最後,欣賞一下測試結果: 設計
完整代碼: code
SocialID.cs orm
Verifier.cs blog