【C#|.NET】從細節出發(三) 邏輯層事務和page object模式

一. 業務邏輯層的事務問題css

若是你的程序分層清晰而且系統禁用複雜存儲過程,那麼在DA中的職責比較單一。程序的邏輯經過BLL調用各類不一樣模塊的DA來實現數據操做。若是當須要不一樣模塊在一個事務的時候,問題就產生了。java

若是你在bll引用System.Data...或者你在DA中穿插各類複雜邏輯的時候基本上你的工程已經不能算是好的程序了,web

1. 使用TransactionScopesql

TransactionScope可使代碼塊成爲事務性代碼。可是須要開通MSDTC權限,而且TransactionScope的性能問題也一直存在爭議。chrome

2. 階段性提交設計模式

這個涉及到框架層次的修改,會單獨開篇幅來講api

3. 使用委託 將多模塊提交匯聚在一塊兒 和框架層次的階段性提交異曲同工app

這個就是設計上的問題,將其餘模塊的方法以委託方式傳入DA。舉個例子假如主線爲消費記錄ConsumeRecord,分支線爲消費詳細ConsumeRecordDetail和票據信息TravelTicket。其中TravelTicket徹底爲另外一個服務模塊對本身爲黑盒。ConsumeRecordDetail對本身爲白盒。框架

  DA層性能

     public delegate BizResult<string> ArchiveTravelTicketDelegate( DbTransaction transaction, List<string> travelTicketIDList);

  主線服務BLL中,其中StartArchives爲其餘模塊可是須要包含近事務的方法

        private BizResult<string> InsertConsumeRecord(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails)
        {
            return consumeRecordArchiveTargetDA.DoArchive(consumeRecordEntity, consumeRecordDetails, new TravelTicketArchiveService().StartArchives);
        }

  主線服務DA中

     public BizResult<string> DoArchive(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails, 
ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket) { return ArchiveHandle(consumeRecordEntity, consumeRecordDetails, true, ConsumeRecordHandle, archiveTravelTicket); }
        public BizResult<string> ConsumeRecordHandle(DbTransaction currentTransaction, ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails)
        {
            return ConsumeRecordHandle(currentTransaction, consumeRecordEntity, consumeRecordDetails,
                (x, y) => InsertConsumeRecordLog(currentTransaction, consumeRecordEntity, true, false)
                , InsertConsumeRecord, consumeRecordDetailArchiveDA.InsertConsumeRecordDetail);
        }

  其中InsertConsumeRecordLog,InsertConsumeRecord爲主線本身的方法,ConsumeRecordDetail由於白盒能夠直接使用consumeRecordDetailArchiveDA.InsertConsumeRecordDetail,archiveTravelTicket爲黑盒。

   主線BaseDA中

     public BizResult<string> ArchiveHandle(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails, bool isArchive,
ConsumeRecordHandlerDelegate consumeRecordHandlerDelegate, ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket)
        {
            using (DbConnection dbConnection = DbObject.CreateConnection())
            {
                dbConnection.Open();

                BizResult<string> bizResult;
                using (DbTransaction currentTransaction = dbConnection.BeginTransaction())
                {
                    try
                    {
                        bizResult = consumeRecordHandlerDelegate(currentTransaction, consumeRecordEntity, consumeRecordDetails);
                        if (bizResult.IsSuccessful)
                        {
                            if (archiveTravelTicket != null)
                            {
                                bizResult = archiveTravelTicket(currentTransaction,
                                                                consumeRecordDetails.Select(x => x.TravelMoneyID).
                                                                    Distinct().ToList());
                            }
                            if (bizResult.IsSuccessful)
                            {
                                bizResult.Message = "xxx success";
                                currentTransaction.Commit();
                                return bizResult;
                            }
                            bizResult.Message = "xxx fail";
                        }
                        bizResult.Message = string.Format("{0}:{1}", isArchive, bizResult.Message);
                        currentTransaction.Rollback();
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex);
                        currentTransaction.Rollback();
                        throw;
                    }
                }
                return bizResult;
            }
        }

二. 測試環節的page object模式

  拿自動化測試Selenium爲例,通常而言咱們對於程序都是應付測試用例,針對測試用例寫出一個一個test-method。這樣無可厚非,使用page object模式可讓代碼開起來更專業更精彩。

  在自動化測試中,主要關注UI的測試互動區域。 Page對象只是這些模型做爲測試代碼中的對象能夠減小了重複的代碼量,而且意味着,若是用戶界面的變化,也只須要修改一個地方。和軟件設計模式中dry(don't repeat yourself)相似。而且在page object模式中,PageObjects也能夠實現頁於頁之間的邏輯。總得來講減小了代碼的重複,讓測試更具可讀性和強大的,提升了測試的可維護性,特別是當有頻繁變化的ui變動。

舉個列子(java版本)

public class LoginPageTest {
    private LoginPage loginPage;
    private final String username = "xx@163.com";
    private final String pwd= "Welcome1";

    @Before
    public void setUp() throws Exception {
        String applicationRoot = new File(
                getClass().getProtectionDomain().getCodeSource().getLocation().getPath())
                .getParent();
        System.setProperty("webdriver.chrome.driver", applicationRoot + "/chromedriver");
        WebDriver webDriver = new ChromeDriver();
        loginPage = new LoginPage(webDriver);
        loginPage.init();
    }

    @Test
    public void testLoginWithoutFakeCheckbox()
    {
        loginPage.setUserName(username);
        loginPage.setPassword(pwd);
        loginPage.pressLoginButton();
        assertThat(loginPage.getClassByLoginWithoutFakeCheckbox(), equalTo("checkbox-wrapper not-checked"));
    }


    @Test
    public void testLoginWithWrongUsername()
    {
        loginPage.setUserName("wrongusername");
        loginPage.setPassword(pwd);
        loginPage.pressCheckBox();
        loginPage.pressLoginButton();
        assertThat(loginPage.getClassByLoginWithWrongUserName(), equalTo("wrapper-input-text wrong"));
    }

    @Test
    public void testLoginWithNullPwd()
    {
        loginPage.clearInput();
        loginPage.setUserName(username);
        loginPage.pressCheckBox();
        loginPage.pressLoginButton();
        assertThat(loginPage.getClassByLoginWithNullPwd(), equalTo("wrapper-input-text wrong"));
    }

    @Test
    public void testLoginWithRightWay()
    {
        loginPage.clearInput();
        loginPage.setUserName(username);
        loginPage.setPassword(pwd);
        loginPage.pressCheckBox();
        loginPage.pressLoginButton();
        assertThat(loginPage.getTextByRightWay(), equalTo("選擇性別"));
    }

}

  

public class LoginPage {
    private final WebDriver webDriver;
    private final WebDriverWait wait;
    private JavascriptExecutor js;
    private final String username = "xx@163.com";
    private final String pwd = "Welcome1";
    private WebElement webElementLogin;
    private WebElement webElementPwd;
    private WebElement webElementInput;

    public LoginPage(WebDriver webDriver) {
        this.webDriver = webDriver;
        this.wait = new WebDriverWait(webDriver, 10);
        this.js = (JavascriptExecutor) webDriver;
    }

    public void init() {
        webDriver.get("http://root:root@localhost:8080/apitool/xxx/owF3PjkBFY5_56Q_KYs5fxmLZTKI");
        WebElement webElementName = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("p.text.bold.name")));

        if (webElementName != null) {
            System.out.print(webElementName.getText());
        }
        webElementLogin = webDriver.findElement(By.cssSelector("input.input-text.login-input"));
        webElementLogin.sendKeys(username);
        webElementPwd = webDriver.findElement(By.cssSelector("input.input-text.password-input"));
        webElementPwd.sendKeys(pwd);
    }

    public String getClassByLoginWithoutFakeCheckbox() {
        WebElement webElementCss = wait.until(
                ExpectedConditions.visibilityOfElementLocated(
                        By.xpath("//*[contains(@class,'checkbox-wrapper not-checked')]")));
        WebElement webElementHref = webDriver.findElement(By.xpath("//*[@id=\"login-page\"]/div/section/div/div[5]/div/div"));
        return webElementHref.getAttribute("class");
    }

    public String getClassByLoginWithWrongUserName() {
        webElementInput = webDriver.findElement(By.xpath("//*[@id=\"login-page\"]/div/section/div/div[3]"));
        return webElementInput.getAttribute("class");
    }

    public String getClassByLoginWithNullPwd() {
        webElementInput = webDriver.findElement(By.xpath("//*[@id=\"login-page\"]/div/section/div/div[3]"));
        return webElementInput.getAttribute("class");
    }

    public String getTextByRightWay() {
        GenderViewPage genderViewPage = new GenderViewPage(webDriver);
        return genderViewPage.getGenderText();
    }

    public void pressCheckBox() {
        js.executeScript("$('.fake-checkbox').attr('class','fake-checkbox active')");
    }

    public void clearInput() {
        webElementLogin.clear();
        webElementPwd.clear();
    }

    public void setUserName(String username) {
        webElementLogin.clear();
        webElementLogin.sendKeys(username);

    }

    public void setPassword(String password) {
        webElementPwd.clear();
        webElementPwd.sendKeys(pwd);
    }

    public void pressLoginButton() {
        js.executeScript("$('.login-button').trigger('touchstart')");
    }

    public void closeBrowser()
    {
        webDriver.quit();
    }
}

三. sql update 慎用位運算

update t_User set valye = Flag | 4 where UserID = 1

若是Flag字段中存在負數,在作位運算的時候,更新出很是大的數值,超過2的62次方

 

但願對你們有幫助。

相關文章
相關標籤/搜索